粘包和分包
了解粘包和分包的问题为什么会存在?
粘包和分包是利用socket在TCP协议下内部的优化机制。是它内部的一个传输机制导致的。
粘包:多条数据包之间粘在一起变成一个包。(因为发送消息比较频繁;每个包的消息很小;TCP想解决频繁发送造成浪费的性能问题)⚠️服务器端接收的消息可能不是一条,可能是多条组合成的一条。
分包:一个很长的包在传输中分成了多条数据包(因为数据量很大;如果发送失败的话需要重新发送很占用时间;大包比较占用网速,运速会比较慢)⚠️如果说一个包被分成了10次,那服务器端会调用10次接收方法,Receive接收到的消息是分割了的数据。
演示一个从客户端向服务器端发送消息粘包分包的情况。
1.粘包
//客户端--粘包问题 using System.Net.Sockets; void Start() { for(int i=0;i<=100;i++) { //只把数据发送到服务器端,如果这里有很频繁的消息发送数据量又很小,这个时候它会出现粘包的问题,客户端发送100次服务器端就会接收100次输出100次数据,那么看它实际输出次数为10次。 clientSocket.Send(Encoding.UTF8.GetBytes(i.ToString())); } }
包虽然会粘在一起,但是到达的先后顺序是不会变的。
2.分包
分包问题不在于发送的有多快,而在于一个包有多大。
//客户端--分包问题 using System.Net.Sockets; string msg=@"打哈鬼谷够阿荣黄瓜啊uu很尬人hi阿法hi哈偶覅潍坊hi啊u反反复复个会阿嘎hi胡帕覅u噶恒瑞i哦爬爬服荣黄瓜啊哈会阿法hi哈偶覅潍坊hi啊u反反复复个会阿嘎hi胡帕覅u噶"; clientSocket.Send(Encoding.UTF8.GetBytes(msg));
即使接收数组给的足够大,只要数据够大,也会分包。
clientSocket.BeginReceive(dataBuffer,0,102400,SocketFlags.None,ReceiveCallback,clientSocket); static byte[] dataBuffer=new byte[102400];
处理粘包分包问题?
在接收到的字节消息数组(byte[])前+接收数据长度(整型),读取数据长度的字节。
字节消息数组的长度是不固定的,但是数据长度得时固定的。如果数据长度也不固定,那我们根本不知道接收到的这个消息占用多少字节。
如果读取到的数据不够长就先不处理等下次再接收到之后再判断是否够长。
在服务器端解析数据:
在服务器端处理数据的接收,要考虑接收的是否是一条完整的消息。有可能是接收到了多条消息,要分割,分割出来然后一条一条进行处理;也可能只包含0.5条消息。先读取前缀,然后再读取。
class Message { private byte[] data=new byte[1024];//存储数据,长度让他最大长度消息小于1024就好了。每次消息都会放在data里面。0.5条是全部的话就先不处理,等下次。读数据,从这里面解析。 private int startIndex=0;//存取了多少个字节的数据在数组里面,从这个数据的0号位置开始存取,如果数据长度是10,那下次再接收就是从10开始。 //读取到count个字节之后再给她增加 public void AddCount(int count) { startIndex+=count; } public byte[] Data { get{return data;} } public int StartIndex { get{return StartIndex;} } public int RemainSize() { get{return data.Length-startIndex;}//这个得到的就是剩余的空间 } //解析数据或者叫作读取数据 public void ReadMessage() { while(true) { if(startIndex<=4)return; int count=BitConverter.ToInt32(data,0);//这样就从我们的数组里面读取了一个长度 //接下来读取剩余数据,从4的位置向后读取count个字节(完成一条消息的解析 )) if((startIndex-4)>=count) { Console.WriteLine(startIndex); Console.WriteLine(count); string s=Encoding.UTF8.GetString(data,4,count); Console.WriteLine("解析出来一条数据"+s); Array.Copy(data,count+4,data,0,startIndex-4-count);//把长度和数据调换一下位置 startIndex-=(count+4); } else { break; } } } }
//服务器端解析消息 static Message msg =new Message(); static void AcceptCallBack(IAsyncResult ar) { clientSocket.BeginReceive(msg.Data,msg.StartIndex,msg.RemainSize,SocketFlags.None,ReceiveCallBack,clientSocket); } static void ReceiveCallback() { try { msg.AddCount(count);//读取完多少字节之后我们要更新他的Index //string msgStr=Encoding.UTF8.GetString(dataBuffer,0.count); //Console.WriteLine("从客户端接收到数据"+msgStr); //循环解析 msg.ReadMessage(); clientSocket.BeginReceive(msg.Data,msg.StartIndex,msg.RemainSize,SocketFlags.None,ReceiveCallBack,clientSocket); } catch(Exception e) { } }