數據粘包問題的出現,是由於在客戶端/服務器端都會有一個比較大的數據緩衝區,來存放接收的數據,爲了保證可以完整的接收到數據,所以緩衝區都會設置的比較大。在收發數據頻繁時,因爲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