tcp網路包“粘包”的介紹和解決方案程式碼示例
阿新 • • 發佈:2019-02-13
我們知道, tcp是流式傳輸的。
還是說人話吧: 客戶端第一次tcp傳送"123"給服務端, 第二次tcp傳送“456”給服務端, 假設服務端沒有及時取, 那麼在服務端的核心緩衝區中就是“123456”, 根本無法分割, 這就是所謂的tcp粘包。 當然, 這只是粘包的原因之一, 還有其他原因也可能導致粘包, 先不一一說了。
服務端接收到黏在一起的包, 怎麼將第一次請求和第二次請求區分開來呢? 我們來簡單聊聊, 並寫個簡單程式碼示意一下。
看一下上面的例子, 無法拆分的根本原因是沒有分割識別符號。 如果人為定義分隔符(比如下劃線), 那就成了“123_456”, 這樣是否就OK呢? 很顯然, 如果客戶端本身要傳的字元中包含下劃線, 那麼就會與使用者分隔符的下劃線衝突, 無法分割, 進入一種剪不斷理還亂的狀態, 那怎麼辦?
可以考慮在每個包前面加一個固定的頭(可以用一個整數), 這個頭中裝有包的長度資訊, 服務端先收頭(整數), 然後從頭中讀出這個包的長度資訊, 這樣就實現了分割。
思路就是這樣, 程式碼寫起來也很簡單,看下:
結果:#include <iostream> #include <string> // linux下不需要這行, 但Windows下需要 using namespace std; int main() { string strBuf; { string s = "123"; int len = s.size(); string tmpStr((char *)&len, sizeof(int)); strBuf += tmpStr; // 頭 strBuf += s; // 身 } { string s = "45678"; int len = s.size(); string tmpStr((char *)&len, sizeof(int)); strBuf += tmpStr; // 頭 strBuf += s; // 身 } // --------------------------------------------- char szBuf[102400] = {0}; // 為了簡便示意,這裡假設strBuf的大小不會超過這個長度 unsigned int totalLen = strBuf.size(); for(unsigned int i = 0; i < strBuf.size(); i++) { szBuf[i] = strBuf[i]; } if(totalLen <= sizeof(int)) { printf("recv error\n"); return -1; } int len1 = 0; { memcpy((char *)&len1, szBuf, sizeof(int)); cout << len1 << endl; if(sizeof(int) + len1 > totalLen) { printf("recv error 1\n"); return -2; } cout << strBuf.substr(sizeof(int), len1) << endl; } if(sizeof(int) + len1 == totalLen) { printf("ok, done!\n"); return 0; } int len2 = 0; { memcpy((char *)&len2, szBuf + sizeof(int) + len1, sizeof(int)); if(sizeof(int) + len1 + sizeof(int) + len2 > totalLen) { printf("recv error 2\n"); return -3; } cout << len2 << endl; cout << strBuf.substr(sizeof(int) + len1 + sizeof(int), len2) << endl; } if(sizeof(int) + len1 + sizeof(int) + len2 == totalLen) { printf("ok, done!!!\n"); return 0; } printf("end\n"); return 0; }
3
123
5
45678
ok, done!!!
在實際開發中, 也經常用類似的方法來判斷收包是否完整。 要注意, 粘包只存在於tcp中, udp不會有粘包, 想想為什麼?
OK, 先說到這裡。