最近在工作中遇到了要自己写Socket服务器和客户端的问题,我解决了关于TCP的粘包问题。那么为什么会引起TCP粘包呢原因有以下两点
1、TCP是基于字节流的,虽然应用层和传输层之间的数据交互是大小不等的数据块,但是TCP把这些数据块仅仅看成一连串无结构的字节流,没有边界;
2、在TCP的首部没有表示数据长度的字段,基于上面两点,在使用TCP传输数据时,才有粘包或者拆包现象发生的可能
假如没有做处理的话,发过来的数据包会有三种情况
1.没有粘包(这个问题不讨论,是正常情况)
2.两个或多个整包粘在一起
3.一个或多个包+下个包的部分数据
我的处理方式是发送包的时候在包的头上加上包头(包头上可以加一些自定义字符,如加密信息等,这次我们讲的是粘包问题所以不加那些东西),包头上标记了我这次发送的数据有多长。这样会有四种情况会出现
1.没有粘包(这个问题不讨论,是正常情况)
2.两个或多个整包粘在一起
3.一个或多个包+下个包的部分或者整个包头
4.一个或者多个包+下个包的包头+数据发送过来
下面是解决粘包的代码,我把要讲的都加载注释里面了
using System;
public class SocketBuffer
{
//定义消息头
private byte[] headBytes;
//包头长度
private byte headLength = 4;
//缓存的包数据(不全的包会缓存在这个里面,等待下次发送)
private byte[] allReceiveData;
//当前接受到数据的长度
private int currentReceiveLength;
//总的数据长度
private int allDataLength;
public SocketBuffer(byte headLength,CallBackReciveOver OvercallBack)
{
this.headLength = headLength;
headBytes = new byte[headLength];
this.callBackReceiveOver = OvercallBack;
}
/// <summary>
/// 解包(改函数是递归函数,自己内部在调用)
/// </summary>
/// <param name="receive">收到的数据</param>
/// <param name="realLength">当前包的真实长度</param>
public void ReceiveByte(byte[] receive,int realLength)
{
if (realLength == 0)
return;
//当前接收的数据小于头的长度(将所有数据存入缓存中)
if(currentReceiveLength<headBytes.Length)
{
ReciveHead(receive, realLength);
}
else
{
//接收的总长度
int tempLength = currentReceiveLength+ realLength;
if(tempLength==allDataLength)
{
//刚好相等(发送过来的是整个包,不用解包)
ReceiveOneAll(receive,realLength);
}
else if(tempLength>allDataLength) //接收的数据比这个消息长(多个包粘连>1个包长度)
{
ReceiveLarger(receive,realLength);
}
else //接收的数据比当前整包要短(存入缓存中)
{
ReceiveSmaller(receive, realLength);
}
}
}
private void ReceiveLarger(byte[] receiveByte,int realLength)
{
int tempLength = allDataLength - currentReceiveLength;
Buffer.BlockCopy(receiveByte, 0, allReceiveData, currentReceiveLength,tempLength);
currentReceiveLength += tempLength;
ReceiveOneMessageOver();
int remainLength = realLength - tempLength;
byte[] remainByte = new byte[remainLength];
Buffer.BlockCopy(receiveByte, tempLength,remainByte, 0, remainLength);
//看成从Socket里取出来放入处理
ReceiveByte(remainByte,remainLength);
}
private void ReceiveSmaller(byte[] receiveByte, int realLength)
{
Buffer.BlockCopy(receiveByte, 0,allReceiveData, currentReceiveLength,realLength);
currentReceiveLength += realLength;
}
private void ReceiveOneAll(byte[] receiveByte, int realLength)
{
Buffer.BlockCopy(receiveByte, 0, allReceiveData, currentReceiveLength, realLength);
currentReceiveLength += realLength;
ReceiveOneMessageOver();
}
private void ReciveHead(byte[] receiveByte, int realLength)
{
//差多少个字节才能组成一个头
int tempReal = headBytes.Length - currentReceiveLength;
//现在接收的和已经接收的总长度是多少
int tempLength = currentReceiveLength + realLength;
//总长度还小于头
if(tempLength<headBytes.Length)
{
Buffer.BlockCopy(receiveByte, 0, headBytes, currentReceiveLength,realLength);
currentReceiveLength += realLength;
}
else //大于等于头
{
Buffer.BlockCopy(receiveByte, 0,headBytes, currentReceiveLength,tempReal);
currentReceiveLength += tempReal;
//头部已经凑齐
//取出四个字节转换成int
byte[] datalength = new byte[] {headBytes[0], headBytes[1],headBytes[2], headBytes[3]};
int bodyLength =int.Parse(Encoding.Default.GetString(datalength));
allDataLength = bodyLength +headLength;
allReceiveData = new byte[allDataLength];
if(datalength!=null)
{
datalength = null;
}
//已经包含了头部了
Buffer.BlockCopy(headBytes, 0,allReceiveData, 0, headLength);
int tempRemin = realLength - tempReal;
//表示receiveByte是否还有数据
if(tempRemin>0)
{
byte[] tempByte = new byte[tempRemin];
//表示将剩下的字节送入tempByte里去
Buffer.BlockCopy(receiveByte,tempReal,tempByte , 0, tempRemin);
ReceiveByte(tempByte,tempRemin);
}
else
{
//只有消息头的情况
ReceiveOneMessageOver();
}
}
}
#region ReceiveOverCallback
public delegate void CallBackReciveOver(byte[] allData);
CallBackReciveOver callBackReceiveOver;
//回调给上层处理好的数据(上层需要把包头文件再进行处理)
private void ReceiveOneMessageOver()
{
if (callBackReceiveOver != null)
{
callBackReceiveOver(allReceiveData);
}
currentReceiveLength = 0;
allDataLength = 0;
allReceiveData = null;
}
#endregion
}
下面是制作数据包的代码,请参考。
public byte[] MakePackage(byte[] data)
{
int length = data.Length;
byte[] bytes = BitConverter.GetBytes(length);
byte[] package= new byte[data.Length + bytes.Length];
Array.Copy(bytes,0,package,0,bytes.Length);
Array.Copy(data,0, package, bytes.Length, data.Length);
return package;
}