什么是粘包和拆包
.TCP 是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的 socket,因此,发送端为了将多个发给接收端的包,更有效的发给对方,使用了优化方法(Nagle 算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样做虽然提高了效率,但是接收端就难于分辨出完整的数据包了,因为面向流的通信是无消息保护边界的
粘包和拆包是网络编程中常遇到的问题,主要是因为TCP协议的Nagle算法和接收方缓冲区的不足导致的。
粘包:发送方发送的若干包数据到接收方接收时粘在一起,导致数据不能正确分割。
拆包:发送方发送的包数据在接收方解析时被拆开,导致数据不能正确解析。
第一种方式发消息的时候 每一条消息的结束时候以特殊符合作为结束标志
TcpClient client = new TcpClient();
client.Connect("端口号", 8080);
NetworkStream stream = client.GetStream();
Send(stream, "星期一");
Send(stream, "星期二");
Send(stream, "星期天放假");
第二种发送数据时候 出现粘包现象,后台处粘包
void Send(NetworkStream stream,string msg)
{
//1定义一个字节数组获取发送数据字节流
byte[] bs = Encoding.UTF8.GetBytes(msg);
//2 创建一个内存流,存在内存当中,可以理解为虚拟的文件流
MemoryStream ms = new MemoryStream();
//3 创建一个二进制读写对象 写入内存流
BinaryWriter bw = new BinaryWriter(ms);
//4写入数据的长度的
bw.Write(bs.Length);
//5 写入数据字节数组
bw.Write(bs);
//6 发送数据 把数据长度和数据内容同时发给服务器
stream.Write(ms.ToArray(), 0, ms.ToArray().Length);
bw.Close();
ms.Close();
}
拆包
public void startRead(TcpClient t1)
{
//接受客户端发来的数据
NetworkStream stream = t1.GetStream();
string ip = t1.Client.RemoteEndPoint.ToString();
byte[] bs = new byte[1024 * 1024];
Task.Run(() =>
{
try
{
while (true)
{
int count= stream.Read(bs, 0, bs.Length);
if (count== 0)
{
throw new Exception("客户端断了");
}
//接受数据
byte[] body = bs.Take(count).ToArray();
string s = Encoding.UTF8.GetString(body);
Console.WriteLine("-------------------------------");
Console.WriteLine("接收到消息为:"+s);
Console.WriteLine("-------------------------------");
if (上一个数据半包 != null) //判断是否有半包 如果有,把半包取出来和当前包合并
{
body = 上一个数据半包.Concat(body).ToArray();//把俩个数组合成一个数组
上一个数据半包 = null; //清空半包
}
//开始拆包 封装一个拆包方法
//参数1 当前要拆的数据
//参数2 第几个位置开始拆
//参数3 当前客户端
ChaiBao(body, 0, t1);
}
}
catch (Exception ex)
{
clientDic.Remove(ip);
}
});
}
byte[] 上一个数据半包 = null;
public void ChaiBao(byte[] bytes ,int startIndex, TcpClient client)
{
if(startIndex+4>bytes.Length)
{
//如果开始位置加上4大于该数据包的长度时候 说明当前包是一个半包
//跳过之前元素,取出剩余的数据
上一个数据半包 = bytes.Skip(startIndex).ToArray();
return;
}
//计算第一个包长度 获取从开始到startIndex之间的长度
int len = BitConverter.ToInt32 (bytes,startIndex);
// 取出对应位置包的总体大小
// 之前的包长度总和
int abc = len + startIndex + 4;
//判断当前包是整包还是半包或者是多包
if (abc == bytes.Length)
{ //如果之前所有包的大小加上当前数据包大小等于全包的长度,证明当前是一个整包
byte[] bs1 = bytes.Skip(startIndex+4).ToArray();
接受到消息的事件?.Invoke(client, bs1);
}else if (abc < bytes.Length)
{
byte[] bs2 = bytes.Skip(startIndex+4).Take(len).ToArray();
接受到消息的事件?.Invoke(client, bs2);
//如果之前包加上当前包小于全包的长度,说明当前包是多包。如果是多包,再进行拆包
ChaiBao(bytes, abc, client);
}
else
{ //目前是一个半包
上一个数据半包 = bytes.Skip (startIndex).ToArray();
}
}