1. 程式人生 > >tcp網路包“粘包”的介紹和解決方案程式碼示例

tcp網路包“粘包”的介紹和解決方案程式碼示例

        我們知道, 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, 先說到這裡。