C# Socket关于TCP粘包的处理方法

最近在工作中遇到了要自己写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;
  }
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值