01:指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。出现粘包现象的原因是多方面的,它既可能由发送方造成,也可能由接收方造成。发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。分包是指在出现粘包的时候我们的接收方要进行分包处理。
粘包问题显示

for (int i = 0; i < 200; i++)
{
byte[] data = System.Text.Encoding.UTF8.GetBytes(i.ToString());
//异步发送消息
clientSocket.BeginSend(data, 0, data.Length,SocketFlags.None,null,null);

粘包和分包_数据


02:解决思路

在数据包中的固定位置封装数据包的长度信息(或可计算数据包总长度的信息),服务器接收到数据后,先是解析包长度,然后根据包长度截取数据包(此种方式常出现于自定义协议中),但是有个小问题就是如果客户端第一个数据包数据长度封装的有错误,那么很可能就会导致后面接收到的所有数据包都解析出错(由于TCP建立连接后流式传输机制),只有客户端关闭连接后重新打开才可以消除此问题,我在处理这个问题的时候对数据长度做了校验,会适时的对接收到的有问题的包进行人为的丢弃处理(客户端有自动重发机制,故而在应用层不会导致数据的不完整性)

粘包和分包_数据_02


粘包和分包_数据_03


03:字符串和值类型转换成字节数据

粘包和分包_客户端_04


04:

//以字节数组的形式返回指定的 32 位有符号整数值
int a = 1000000;
byte[] data=BitConverter.GetBytes(a);
foreach (var item in data)
{
Console.WriteLine(item);
}

//返回由字节数组中指定位置的四个字节转换来的 32 位有符号整数。
byte[] data1=new byte[4]{1,1,0,0};
int count=BitConverter.ToInt32(data1,0);
Console.WriteLine(count);

粘包和分包_客户端_05

05:在客户端发送数据的时候加上数据长度

b6 = b4.Concat(b5).ToArray();//合并两个数组总结 这种linq方法适用于所有数组
public static class Message
{
//包装消息
public static Byte[] GetBytes(string str)
{
byte[] data1=System.Text.Encoding.UTF8.GetBytes(str);
int length = data1.Length; //数据的长度
byte[] data2 = BitConverter.GetBytes(length); //长度转换为四个字节的数组
return data2.Concat(data1).ToArray();
}
}

06:解析客户端发送过来的数据

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SocketServer
{
/// <summary>
/// 处理接收过来的数据
/// </summary>
class Message
{
private byte[] data = new byte[1024];
private int startIndex = 0;//我们存取了多少个字节的数据在数组里面

public byte[] Data
{
get { return data; }
}
public int StartIndex
{
get { return startIndex; }
}

//更新获取的数据长度
public void AddCount(int count)
{
startIndex += count;
}
public int RemainSize //剩余空间
{
get { return data.Length - startIndex; }
}

/// <summary>
/// 解析数据或者叫做读取数据
/// </summary>
public void ReadMessage()
{
while (true)
{
if (startIndex <= 4)
{
return;
}
// 读取前四个字节的数据(数组前四个字节保存的是传过来消息的元素个数)
int length = BitConverter.ToInt32(data, 0);//数据的长度
if ((startIndex - 4) >= length) //数据读取完整
{
string message = System.Text.Encoding.UTF8.GetString(data, 4, length);
Console.WriteLine("收到了客户端的消息:"+message);
//归零
Array.Copy(data,length+4,data,0,startIndex-4-length);
startIndex -= (4 + length);
}
else
{
break;
}
}

}
}
}

粘包和分包_数组_06


粘包和分包_数据_07


粘包和分包_数据_08