一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - C# - C#中TCP粘包問題的解決方法

C#中TCP粘包問題的解決方法

2022-01-17 12:43白云隨風 C#

這篇文章主要為大家詳細介紹了C#中TCP粘包問題的解決方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下

一、tcp粘包產生的原理

1.tcp粘包是指發送方發送的若干包數據到接收方接收時粘成一包,從接收緩沖區看,后一包數據的頭緊接著前一包數據的尾。出現粘包現象的原因是多方面的,它既可能由發送方造成,也可能由接收方造成。

2.發送方引起的粘包是由tcp協議本身造成的,tcp為提高傳輸效率,發送方往往要收集到足夠多的數據后才發送一包數據。若連續幾次發送的數據都很少,通常tcp會根據優化算法把這些數據合成一包后一次發送出去,這樣接收方就收到了粘包數據。接收方引起的粘包是由于接收方用戶進程不及時接收數據,從而導致粘包現象。

3.這是因為接收方先把收到的數據放在系統接收緩沖區,用戶進程從該緩沖區取數據,若下一包數據到達時前一包數據尚未被用戶進程取走,則下一包數據放到系統接收緩沖區時就接到前一包數據之后,而用戶進程根據預先設定的緩沖區大小從系統接收緩沖區取數據,這樣就一次取到了多包數據。、

二、解決原理及代碼實現

1.采用包頭(固定長度,里面存著包體的長度,發送時動態獲取)+包體的傳輸機制。如圖

C#中TCP粘包問題的解決方法

headersize 存放著包體的長度,其headersize本身是定長4字節;

一個完整的數據包(l)=headersize+bodysize;

2.分包算法

  其基本思路是首先將待處理的接收數據流即系統緩沖區數據(長度設為m)強行轉換成預定的結構數據形式,并從中取出結構數據長度字段l,而后根據包頭計算得到第一包數據長度。

       m=系統緩沖區大小;l=用戶發送的數據包=headersize+bodysize;

1)若l<m,則表明數據流包含多包數據,從其頭部截取若干個字節存入臨時緩沖區,剩余部分數據依此繼續循環處理,直至結束。

C#中TCP粘包問題的解決方法

2)若l=m,則表明數據流內容恰好是一完整結構數據(即用戶自定義緩沖區等于系統接收緩沖區大小),直接將其存入臨時緩沖區即可。

C#中TCP粘包問題的解決方法

3)若l>m,則表明數據流內容尚不夠構成一完整結構數據,需留待與下一包數據合并后再行處理。

C#中TCP粘包問題的解決方法

4)下面是代碼代碼實現(hp-socket框架的服務器端來接收數據)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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,具體參考

最新的代碼

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//線程安全的字典
  concurrentdictionary<intptr, byte[]> dic = new concurrentdictionary<intptr, 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#深入解析C#中的交錯數組與隱式類型的數組

    深入解析C#中的交錯數組與隱式類型的數組

    這篇文章主要介紹了深入解析C#中的交錯數組與隱式類型的數組,隱式類型的數組通常與匿名類型以及對象初始值設定項和集合初始值設定項一起使用,需要的...

    C#教程網6172021-11-09
  • C#C#裁剪,縮放,清晰度,水印處理操作示例

    C#裁剪,縮放,清晰度,水印處理操作示例

    這篇文章主要為大家詳細介紹了C#裁剪,縮放,清晰度,水印處理操作示例,具有一定的參考價值,感興趣的小伙伴們可以參考一下...

    吳 劍8332021-12-08
  • C#C# 實現對PPT文檔加密、解密及重置密碼的操作方法

    C# 實現對PPT文檔加密、解密及重置密碼的操作方法

    這篇文章主要介紹了C# 實現對PPT文檔加密、解密及重置密碼的操作方法,非常不錯,具有參考借鑒價值,需要的朋友可以參考下...

    E-iceblue5012022-02-12
  • C#C#設計模式之Visitor訪問者模式解決長隆歡樂世界問題實例

    C#設計模式之Visitor訪問者模式解決長隆歡樂世界問題實例

    這篇文章主要介紹了C#設計模式之Visitor訪問者模式解決長隆歡樂世界問題,簡單描述了訪問者模式的定義并結合具體實例形式分析了C#使用訪問者模式解決長...

    GhostRider9502022-01-21
  • C#Unity3D實現虛擬按鈕控制人物移動效果

    Unity3D實現虛擬按鈕控制人物移動效果

    這篇文章主要為大家詳細介紹了Unity3D實現虛擬按鈕控制人物移動效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一...

    shenqingyu060520232410972022-03-11
  • C#C#通過KD樹進行距離最近點的查找

    C#通過KD樹進行距離最近點的查找

    這篇文章主要為大家詳細介紹了C#通過KD樹進行距離最近點的查找,具有一定的參考價值,感興趣的小伙伴們可以參考一下...

    帆帆帆6112022-01-22
  • C#C#實現XML文件讀取

    C#實現XML文件讀取

    這篇文章主要為大家詳細介紹了C#實現XML文件讀取的相關代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下...

    Just_for_Myself6702022-02-22
  • C#WPF 自定義雷達圖開發實例教程

    WPF 自定義雷達圖開發實例教程

    這篇文章主要介紹了WPF 自定義雷達圖開發實例教程,本文介紹的非常詳細,具有參考借鑒價值,需要的朋友可以參考下...

    WinterFish13112021-12-06
主站蜘蛛池模板: 無码一区中文字幕少妇熟女网站 | 色综合色狠狠天天综合色hd | 国产自拍偷拍自拍 | 日本一区二区精品88 | 久久国产精品二区99 | 午夜片无码区在线观看 | 国产日韩精品一区二区 | 国产在线精品一区二区高清不卡 | 翁公与小莹在客厅激情 | 99久久精品久久久久久清纯 | 亚偷熟乱区视频在线观看 | 欧美在线视频 一区二区 | 舔到喷水 | 村妇超级乱淫伦小说全集 | 亚洲国产区男人本色在线观看欧美 | 免费看60分钟大片视频播放 | 无码专区aaaaaa免费视频 | 国产自在线观看 | 3p文两男一女办公室高h | 校花被老头夺去第一次动图 | 亚洲精品视频在线免费 | 国内老司机精品视频在线播出 | 亚洲国产精品久久久久 | 图片专区小说专区卡通动漫 | 久久国产影院 | 久久无码人妻AV精品一区 | 国产一区二区三区欧美精品 | 免费叼嘿视频 | 99久久精品国内 | 国产精品久久久久网站 | 国产亚洲精品网站 | 99视频免费在线观看 | 欧美日韩在线成人看片a | 激情视频网址 | 日韩一品在线播放视频一品免费 | 国产一区二区免费视频 | 亚洲女性色尼古综合网 | 色综合久久天天综合观看 | 亚洲gogo人体大胆西西安徽 | 国产香蕉国产精品偷在线观看 | 国产高清在线播放刘婷91 |