首页后端开发ASP.NETC#中关于TCP粘包出现的问题解决的示例

C#中关于TCP粘包出现的问题解决的示例

时间2024-01-30 12:44:03发布访客分类ASP.NET浏览997
导读:收集整理的这篇文章主要介绍了C#中关于TCP粘包出现的问题解决的示例,觉得挺不错的,现在分享给大家,也给大家做个参考。这篇文章主要为大家详细介绍了C#中TCP粘包问题的解决方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下一、TCP粘包...
收集整理的这篇文章主要介绍了C#中关于TCP粘包出现的问题解决的示例,觉得挺不错的,现在分享给大家,也给大家做个参考。这篇文章主要为大家详细介绍了C#中TCP粘包问题的解决方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

一、TCP粘包产生的原理

1.TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。出现粘包现象的原因是多方面的,它既可能由发送方造成,也可能由接收方造成。

2.发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。

3.这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。、

二、解决原理及代码实现

1.采用包头(固定长度,里面存着包体的长度,发送时动态获取)+包体的传输机制。如图

HeaderSize 存放着包体的长度,其HeaderSize本身是定长4字节;

一个完整的数据包(L)=HeaderSize+BodySize;

2.分包算法

  其基本思路是首先将待处理的接收数据流即系统缓冲区数据(长度设为M)强行转换成预定的结构数据形式,并从中取出结构数据长度字段L,而后根据包头计算得到第一包数据长度。

M=系统缓冲区大小;L=用户发送的数据包=HeaderSize+BodySize;

1)若Lm,则表明数据流包含多包数据,从其头部截取若干个字节存入临时缓冲区,剩余部分数据依此继续循环处理,直至结束。

2)若L=M,则表明数据流内容恰好是一完整结构数据(即用户自定义缓冲区等于系统接收缓冲区大小),直接将其存入临时缓冲区即可。

3)若L> M,则表明数据流内容尚不够构成一完整结构数据,需留待与下一包数据合并后再行处理。

4)下面是代码代码实现(HP-sOCKET框架的服务器端来接收数据)

int headSize = 4;
    //包头长度 固定4  byte[] surplusbuffer = null;
    //不完整的数据包,即用户自定义缓冲区  /// summary>
      /// 接收客户端发来的数据  /// /summary>
      /// param name="connId">
    每个客户的会话ID/param>
      /// param name="bytes">
    缓冲区数据/param>
      /// returns>
    /returns>
  PRivate HandleResult OnReceive(IntPtr connId, byte[] bytes)   {
       //bytes 为系统缓冲区数据   //bytesRead为系统缓冲区长度   int bytesRead = bytes.Length;
       if (bytesRead >
 0)   {
        if (surplusBuffer == null)//判断是不是第一次接收,为空说是第一次     surplusBuffer = bytes;
    //把系统缓冲区数据放在自定义缓冲区里面    else     surplusBuffer = surplusBuffer.Concat(bytes).ToArray();
    //拼接上一次剩余的包    //已经完成读取每个数据包长度    int haveRead = 0;
        //这里totalLen的长度有可能大于缓冲区大小的(因为 这里的surplusBuffer 是系统缓冲区+不完整的数据包)    int totalLen = surplusBuffer.Length;
    while (haveRead = totalLen)    {
     //如果在N此拆解后剩余的数据包连一个包头的长度都不够     //说明是上次读取N个完整数据包后,剩下的最后一个非完整的数据包     if (totalLen - haveRead  headSize)     {
          byte[] byteSub = new byte[totalLen - haveRead];
          //把剩下不够一个完整的数据包存起来      Buffer.BlockCopy(surplusBuffer, haveRead, byteSub, 0, totalLen - haveRead);
          surplusBuffer = byteSub;
          totalLen = 0;
          break;
     }
         //如果够了一个完整包,则读取包头的数据     byte[] headByte = new byte[headSize];
         Buffer.BlockCopy(surplusBuffer, haveRead, headByte, 0, headSize);
    //从缓冲区里读取包头的字节     int bodySize = BITConverter.ToInt32(headByte, 0);
    //从包头里面分析出包体的长度     //这里的 haveRead=等于N个数据包的长度 从0开始;0,1,2,3....N     //如果自定义缓冲区拆解N个包后的长度 大于 总长度,说最后一段数据不够一个完整的包了,拆出来保存     if (haveRead + headSize + bodySize >
 totalLen)     {
          byte[] byteSub = new byte[totalLen - haveRead];
          Buffer.BlockCopy(surplusBuffer, haveRead, byteSub, 0, totalLen - haveRead);
          surplusBuffer = byteSub;
          break;
     }
     else     {
          //挨个分解每个包,解析成实际文字      String strc = Encoding.UTF8.GetString(surplusBuffer, haveRead + headSize, bodySize);
          //AddMsg(string.Format(" >
     [OnReceive] ->
 {
0}
    ", strc));
          //依次累加当前的数据包的长度      haveRead = haveRead + headSize + bodySize;
      if (headSize + bodySize == bytesRead)//如果当前接收的数据包长度正好等于缓冲区长度,则待拼接的不规则数据长度归0      {
           surplusBuffer = null;
    //设置空 回到原始状态       totalLen = 0;
//清0      }
     }
    }
   }
       return HandleResult.Ok;
  }
    

值此完成拆包解析文字工作。但实际上还没完成,如果这段代码是客户端接收来自服务器的数据的话就没问题了。

仔细看IntPtr connId 每个连接的会话ID

private HandleResult OnReceive(IntPtr connId, byte[] bytes){
}
    

但是服务器端还要分辨出 每个数据包是哪个会话产生的,因为服务器端是多线程,多用户的模式,第一个数据包和第二个可能来自不同会话的数据,所以上面的代码只适用于单会话模式。

下面我要解决这个问题。

采用c#安全的ConcurrentDictionary,

最新的代码

//线程安全的字典  ConcurrentDictionaryIntPtr, byte[]>
     dic = new ConcurrentDictionaryIntPtr, byte[]>
    ();
      int headSize = 4;
    //包头长度 固定4  /// summary>
      /// 接收客户端发来的数据  /// /summary>
      /// param name="connId">
    每个客户的会话ID/param>
      /// param name="bytes">
    缓冲区数据/param>
      /// returns>
    /returns>
  private HandleResult OnReceive(IntPtr connId, byte[] bytes)   {
       //bytes 为系统缓冲区数据   //bytesRead为系统缓冲区长度   int bytesRead = bytes.Length;
       if (bytesRead >
 0)   {
        byte[] surplusBuffer = null;
    if (dic.TryGetValue(connId, out surplusBuffer))    {
         byte[] curBuffer = surplusBuffer.Concat(bytes).ToArray();
    //拼接上一次剩余的包     //更新会话ID 的最新字节     dic.TryUpdate(connId, curBuffer, surplusBuffer);
         surplusBuffer = curBuffer;
//同步    }
    else    {
         //添加会话ID的bytes     dic.TryAdd(connId, bytes);
         surplusBuffer = bytes;
//同步    }
        //已经完成读取每个数据包长度    int haveRead = 0;
        //这里totalLen的长度有可能大于缓冲区大小的(因为 这里的surplusBuffer 是系统缓冲区+不完整的数据包)    int totalLen = surplusBuffer.Length;
    while (haveRead = totalLen)    {
     //如果在N此拆解后剩余的数据包连一个包头的长度都不够     //说明是上次读取N个完整数据包后,剩下的最后一个非完整的数据包     if (totalLen - haveRead  headSize)     {
          byte[] byteSub = new byte[totalLen - haveRead];
          //把剩下不够一个完整的数据包存起来      Buffer.BlockCopy(surplusBuffer, haveRead, byteSub, 0, totalLen - haveRead);
          dic.TryUpdate(connId, byteSub, surplusBuffer);
          surplusBuffer = byteSub;
          totalLen = 0;
          break;
     }
         //如果够了一个完整包,则读取包头的数据     byte[] headByte = new byte[headSize];
         Buffer.BlockCopy(surplusBuffer, haveRead, headByte, 0, headSize);
    //从缓冲区里读取包头的字节     int bodySize = BitConverter.ToInt32(headByte, 0);
    //从包头里面分析出包体的长度     //这里的 haveRead=等于N个数据包的长度 从0开始;0,1,2,3....N     //如果自定义缓冲区拆解N个包后的长度 大于 总长度,说最后一段数据不够一个完整的包了,拆出来保存     if (haveRead + headSize + bodySize >
 totalLen)     {
          byte[] byteSub = new byte[totalLen - haveRead];
          Buffer.BlockCopy(surplusBuffer, haveRead, byteSub, 0, totalLen - haveRead);
          dic.TryUpdate(connId, byteSub, surplusBuffer);
          surplusBuffer = byteSub;
          break;
     }
     else     {
          //挨个分解每个包,解析成实际文字      String strc = Encoding.UTF8.GetString(surplusBuffer, haveRead + headSize, bodySize);
          AddMsg(string.Format(" >
 {
0}
    [OnReceive] ->
 {
1}
    ", connId, strc));
          //依次累加当前的数据包的长度      haveRead = haveRead + headSize + bodySize;
      if (headSize + bodySize == bytesRead)//如果当前接收的数据包长度正好等于缓冲区长度,则待拼接的不规则数据长度归0      {
           byte[] xBTye=null;
           dic.TryRemove(connId, out xbtye);
           surplusBuffer = null;
    //设置空 回到原始状态       totalLen = 0;
//清0      }
     }
    }
   }
       return HandleResult.Ok;
  }
    

这样就解决了,多客户端会话造成的接收混乱。至此所有工作完成。以上代码就是为了参考学习,如果实在不想这么麻烦。可以直接使用HP-socket通信框架的PACK模型,里面自动实现了解决粘包的问题。

以上就是C#中关于TCP粘包出现的问题解决的示例的详细内容,更多请关注其它相关文章!

声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!

.netcsharp

若转载请注明出处: C#中关于TCP粘包出现的问题解决的示例
本文地址: https://pptw.com/jishu/592536.html
C#中INI配置文件的图文代码详解 javascript怎么隐藏文字

游客 回复需填写必要信息