解決Socket通訊中,常常遇到的問題——數據粘包的兩種方法

    數據粘包問題的出現,是由於在客戶端/服務器端都會有一個比較大的數據緩衝區,來存放接收的數據,爲了保證可以完整的接收到數據,所以緩衝區都會設置的比較大。在收發數據頻繁時,因爲tcp傳輸消息的無邊界,會致使客戶端/服務器端不知道接收到的消息究竟是第幾條消息,所以,會致使相似一次性接收幾條消息的狀況,從而亂碼。數組

    在每次發送消息之間,加入空循環,從而能夠將消息隔離開來,可是這個方法會嚴重影響程序的運行效率。服務器

    方法一:數據粘包問題的出現是由於緩衝區過大,所以採用發送/接收變長消息的方法,在發送/接收消息時,將消息的長度做爲消息的一部分發送出去,從而接收方能夠根據傳來的長度信息,制定相應長度的緩衝區;tcp

    方法二:將發送的每條消息的首尾都加上特殊標記符,前加"<"   後加">"。這裏我採起的是先將要發送的全部消息,首尾加上特殊標記後,都先放在一個字符串string中,而後一次性的發送給接收方,接受以後,再根據標記符< >,將一條條消息擇(zhái)出來。函數

    代碼以下:spa

    發送消息端,即服務器端:code

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 using System.Net;
  6 using System.Net.Sockets;
  7 using System.Collections;
  8 using System.Diagnostics;
  9 
 10 namespace _07發送變長消息的方法
 11 {
 12     class Program
 13     {
 14         static void Main(string[] args)
 15         {
 16             #region 先將發送的消息帶上首尾標記< > 再將要發送的全部消息,放在一個字符串中 而後一次性發送給客戶端
 17             DateTime start = DateTime.Now;//記錄程序的運行時間
 18             string str = null;
 19             double[] test = { 10.3, 15.2, 25.3, 13.338, 25.41, 0.99, 2017, 20000, 1, 3, 2, 5, 8, 90, 87, 33, 55, 99, 100 };
 20             IPEndPoint IPep2 = new IPEndPoint(IPAddress.Any, 127);
 21             Socket newsock2 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 22             newsock2.Bind(IPep2);
 23             newsock2.Listen(10);
 24             Console.WriteLine("等待新客戶端的鏈接");
 25             Socket client2 = newsock2.Accept();//等待客戶端的鏈接
 26             IPEndPoint clientep2 = (IPEndPoint)client2.RemoteEndPoint;
 27             Console.WriteLine("與{0}鏈接在{1}端口", clientep2.Address, clientep2.Port);
 28             string welcome2 = "welcome to the new server";
 29             byte[] data;
 30             str += SpecialMessage(welcome2);//將要發送的消息,都放在字符串str中
 31 
 32             //發送數組的長度信息 給字符串str
 33             str += SpecialMessage(test.Length.ToString());
 34 
 35             //用循環的形式 依次將數組中的元素給str
 36             string[] strvalue = new string[test.Length];
 37             for (int j = 0; j < test.Length; j++)
 38             {
 39                 strvalue[j] = test[j].ToString();//將實際速度集合轉換爲string數組
 40                 str += SpecialMessage(strvalue[j]);
 41             }
 42 
 43             //將全部發送的信息,都放在了str中,而後一次性的發送過去 注意都是有首尾標記的消息< >
 44             data = System.Text.Encoding.ASCII.GetBytes(str);
 45             client2.Send(data);
 46 
 47             DateTime end = DateTime.Now;
 48             TimeSpan span = end - start;
 49             double seconds = span.TotalSeconds;
 50             Console.WriteLine("程序運行的時間是{0}s", seconds);
 51 
 52 
 53             Console.WriteLine("傳送數據成功!");
 54             client2.Close();//釋放資源
 55             newsock2.Close();
 56             Console.ReadKey();
 57             #endregion
 58 
 59 
 60             #region 採用變長的消息 即發送時先告訴客戶端 消息的長度是多少
 61             //DateTime start = DateTime.Now;
 62             //double[] test = { 10.3, 15.2, 25.3, 13.338, 25.41, 0.99, 2017, 20000, 1, 3, 2, 5, 8, 90, 87, 33, 55, 99, 100 };
 63             //IPEndPoint IPep2 = new IPEndPoint(IPAddress.Any, 127);
 64             //Socket newsock2 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 65             //newsock2.Bind(IPep2);
 66             //newsock2.Listen(10);
 67             //Console.WriteLine("等待新客戶端的鏈接");
 68             //Socket client2 = newsock2.Accept();//等待客戶端的鏈接
 69             //IPEndPoint clientep2 = (IPEndPoint)client2.RemoteEndPoint;
 70             //Console.WriteLine("與{0}鏈接在{1}端口", clientep2.Address, clientep2.Port);
 71             //string welcome2 = "welcome to the new server";
 72             //byte[] date = Encoding.ASCII.GetBytes(welcome2);//字符串轉換爲字節數組  傳送給客戶端
 73             //SendVarMessage(client2, date);
 74 
 75 
 76             ////發送數組的長度信息 給客戶端
 77             //date = Encoding.ASCII.GetBytes(test.Length.ToString());
 78             //SendVarMessage(client2, date);
 79 
 80             ////用循環的形式 依次將數組中的元素髮送給客戶端
 81             //string[] strvalue = new string[test.Length];
 82             //for (int j = 0; j < test.Length; j++)
 83             //{
 84             //    strvalue[j] = test[j].ToString();//將實際速度集合轉換爲string數組
 85             //    date = Encoding.ASCII.GetBytes(strvalue[j]);//string數組中的每一個元素一次轉換爲字節 發送給客戶端
 86             //    SendVarMessage(client2, date);
 87             //}
 88 
 89             //DateTime end = DateTime.Now;
 90             //TimeSpan span = end - start;
 91             //double seconds = span.TotalSeconds;
 92             //Console.WriteLine("程序運行的時間是{0}s", seconds);
 93 
 94             //Console.WriteLine("傳送數據成功!");
 95             //client2.Close();//釋放資源
 96             //newsock2.Close();
 97             //Console.ReadKey();
 98             #endregion
 99         }
100 
101         /// <summary>
102         /// 發送變長消息方法
103         /// </summary>
104         /// <param name="s"></param>
105         /// <param name="msg"></param>
106         /// <returns>不須要返回值</returns>
107         private static void SendVarMessage(Socket s, byte[] msg)
108         {
109             int offset = 0;
110             int sent;
111             int size = msg.Length;
112             int dataleft = size;
113             byte[] msgsize = new byte[2];
114 
115             //將消息的尺寸從整型轉換成能夠發送的字節型
116             //由於int型是佔4個字節 因此msgsize是4個字節 後邊是空字節
117             msgsize = BitConverter.GetBytes(size);
118 
119             //發送消息的長度信息
120             //以前老是亂碼出錯 客戶端接收到的歡迎消息前兩個字節是空 後邊的兩個字符er傳送到第二次接收的字節數組中
121             //所以將er字符轉換爲int出錯  這是由於以前在Send代碼中,是將msgsize整個字節數組發送給客戶端 因此致使第3 4個空格也發送
122             //致使發送的信息混亂 這兩個空格使發送的信息都日後挪了兩個位置  從而亂碼
123             sent = s.Send(msgsize, 0, 2, SocketFlags.None);
124             while (dataleft > 0)
125             {
126                 int sent2 = s.Send(msg, offset, dataleft, SocketFlags.None);
127                 //設置偏移量
128                 offset += sent2;
129                 dataleft -= sent2;
130             }
131         }
132 
133 
134 
135         /// <summary>
136         /// 給發送的消息 加特殊結束標記 頭尾分別加"<" ">"
137         /// </summary>
138         /// <param name="s"></param>
139         /// <param name="msg"></param>
140         private static string SpecialMessage(string str)
141         {
142             string strNew = "<";
143             strNew += str;
144             strNew += ">";
145             return strNew;
146         }
147     }
148 }

 

 

接收消息端,即客戶端:server

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 using System.Net;
  6 using System.Net.Sockets;
  7 using System.Collections;
  8 
  9 namespace _08接收變長消息的方法
 10 {
 11     class Program
 12     {
 13         static void Main(string[] args)
 14         {
 15             #region 接收帶有首尾特殊標記符的消息
 16             DateTime start = DateTime.Now;
 17             byte[] data = new byte[1024];
 18             IPEndPoint IPep2 = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 127);
 19             Socket server2 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 20             try
 21             {
 22                 server2.Connect(IPep2);
 23                 Console.WriteLine("鏈接成功");
 24             }
 25             catch (SocketException e)
 26             {
 27                 Console.WriteLine("鏈接服務器失敗");
 28                 Console.WriteLine(e.ToString());
 29                 Console.ReadKey();
 30                 return;
 31             }
 32             finally
 33             { }
 34             //接收來自服務器的數據
 35             server2.Receive(data);
 36 
 37             //一次性接收了服務器端發來的數據 利用ReceiveSpecialMessage方法接收並根據特殊標記
 38             //將消息一條一條的放在字符串數組strs中
 39             string[] strs = ReceiveSpecialMessage(data);
 40             string stringData = strs[0];
 41             Console.WriteLine(stringData);//接收並顯示歡迎消息    成功!
 42 
 43             string length = strs[1];
 44             Console.WriteLine("共接收到{0}個數據", length);
 45             int n = Convert.ToInt32(length);
 46 
 47             //依次接收來自服務器端傳送的實際速度值 成功!
 48             string[] speed = new string[n];
 49             for (int i = 0; i < n; i++)
 50             {
 51                 speed[i] = strs[i + 2];
 52                 Console.WriteLine("第{0}次的實際速度是{1}", i + 1, speed[i]);
 53             }
 54 
 55             DateTime end = DateTime.Now;
 56             TimeSpan span = end - start;
 57             double seconds = span.TotalSeconds;
 58             Console.WriteLine("程序運行的時間是{0}s", seconds);
 59             server2.Shutdown(SocketShutdown.Both);
 60             server2.Close();
 61             Console.ReadKey();
 62             #endregion
 63 
 64             #region 接收變長的消息 無特殊標記符
 65             //DateTime start = DateTime.Now;
 66             //byte[] data = new byte[1024];
 67             //IPEndPoint IPep2 = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 127);
 68             //Socket server2 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 69             //try
 70             //{
 71             //    server2.Connect(IPep2);
 72             //    Console.WriteLine("鏈接服務器成功");
 73             //}
 74             //catch (SocketException e)
 75             //{
 76             //    Console.WriteLine("鏈接服務器失敗");
 77             //    Console.WriteLine(e.ToString());
 78             //    Console.ReadKey();
 79             //    return;
 80             //}
 81             //finally
 82             //{ }
 83             ////接收來自服務器的數據
 84             //data = ReceiveVarMessage(server2);
 85             //string stringData = Encoding.ASCII.GetString(data, 0, data.Length);
 86             //Console.WriteLine(stringData);//接收並顯示歡迎消息    成功!
 87 
 88             //data = ReceiveVarMessage(server2);
 89             //string length = Encoding.ASCII.GetString(data);
 90             //Console.WriteLine("共接收到{0}個數據", length);
 91             //int n = Convert.ToInt32(length);
 92 
 93             ////依次接收來自服務器端傳送的實際速度值 成功!
 94             //string[] speed = new string[n];
 95             //for (int i = 0; i < n; i++)
 96             //{
 97             //    data = ReceiveVarMessage(server2);
 98             //    speed[i] = Encoding.ASCII.GetString(data, 0, data.Length);
 99             //    Console.WriteLine("第{0}次的實際速度是{1}", i + 1, speed[i]);
100             //}
101 
102             //DateTime end = DateTime.Now;
103             //TimeSpan span = end - start;
104             //double seconds = span.TotalSeconds;
105             //Console.WriteLine("程序運行的時間是{0}s", seconds);
106 
107             //server2.Shutdown(SocketShutdown.Both);
108             //server2.Close();
109             //Console.ReadKey();
110             #endregion
111         }//Main函數
112 
113 
114         /// <summary>
115         /// 接收變長消息方法
116         /// </summary>
117         /// <param name="s"></param>
118         /// <returns>接收到的信息</returns>
119         private static byte[] ReceiveVarMessage(Socket s)//方法的返回值是字節數組 byte[] 存放的是接受到的信息
120         {
121             int offset = 0;
122             int recv;
123             byte[] msgsize = new byte[2];
124 
125             //接收2個字節大小的長度信息
126             recv = s.Receive(msgsize, 0, 2, 0);
127 
128             //將字節數組的消息長度轉換爲整型
129             int size = BitConverter.ToInt16(msgsize, 0);
130             int dataleft = size;
131             byte[] msg = new byte[size];
132             while (dataleft > 0)
133             {
134                 //接收數據
135                 recv = s.Receive(msg, offset, dataleft, 0);
136                 if (recv == 0)
137                 {
138                     break;
139                 }
140                 offset += recv;
141                 dataleft -= recv;
142             }
143             return msg;
144         }
145 
146 
147         /// <summary>
148         /// 接收結尾帶有特殊標記的消息   哈哈
149         /// </summary>
150         /// <param name="s"></param>
151         /// <returns></returns>
152         private static string[] ReceiveSpecialMessage(byte[] data)
153         {
154             string str = Encoding.ASCII.GetString(data);
155             int i = 0;
156             int j = 1;
157             List<int> list_i = new List<int>();
158             List<int> list_j = new List<int>();
159             while (i < str.Length)  //找到特殊標記符 < 所在位置
160             {
161                 if (str[i].ToString() == "<")
162                 {
163                     list_i.Add(i);
164                     i++;
165                 }
166                 else
167                 {
168                     i++;
169                     continue;
170                 }
171             }
172             while (j < str.Length)  //找到特殊標記符 > 所在的位置
173             {
174 
175                 if (str[j].ToString() == ">")
176                 {
177                     list_j.Add(j);
178                     j++;
179                 }
180                 else
181                 {
182                     j++;
183                     continue;
184                 }
185             }
186             string[] strs = new string[list_i.Count];
187             for (int i1 = 0; i1 < strs.Length; i1++)
188             {
189                 strs[i1] = str.Substring(list_i[i1] + 1, list_j[i1] - list_i[i1] - 1); //截取 < > 之間的字符串,是咱們要的消息
190             }
191             return strs;
192         }
193     }
194 }

 

經驗證,兩種方法都沒有問題,而且其運行時間,也沒有太大差異(在發送較小的數據量下)。blog

相關文章
相關標籤/搜索