一 : 什么時候需要考慮粘包問題?
1:如果利用tcp每次發(fā)送數(shù)據(jù),就與對方建立連接,然后雙方發(fā)送完一段數(shù)據(jù)后,就關(guān)閉連接,這樣就不會出現(xiàn)粘包問題(因為只有一種包結(jié)構(gòu),類似于http協(xié)議)。關(guān)閉連接主要要雙方都發(fā)送close連接(參考tcp關(guān)閉協(xié)議)。如:A需要發(fā)送一段字符串給B,那么A與B建立連接,然后發(fā)送雙方都默認好的協(xié)議字符如"hello give me sth abour yourself",然后B收到報文后,就將緩沖區(qū)數(shù)據(jù)接收,然后關(guān)閉連接,這樣粘包問題不用考慮到,因為大家都知道是發(fā)送一段字符。
2:如果發(fā)送數(shù)據(jù)無結(jié)構(gòu),如文件傳輸,這樣發(fā)送方只管發(fā)送,接收方只管接收存儲就ok,也不用考慮粘包
3:如果雙方建立連接,需要在連接后一段時間內(nèi)發(fā)送不同結(jié)構(gòu)數(shù)據(jù),如連接后,有好幾種結(jié)構(gòu):
1)"hello give me sth abour yourself"
2)"Don't give me sth abour yourself"
那這樣的話,如果發(fā)送方連續(xù)發(fā)送這個兩個包出去,接收方一次接收可能會是"hello give me sth abour yourselfDon't give me sth abour yourself" 這樣接收方就傻了,到底是要干嘛?不知道,因為協(xié)議沒有規(guī)定這么詭異的字符串,所以要處理把它分包,怎么分也需要雙方組織一個比較好的包結(jié)構(gòu),所以一般可能會在頭加一個數(shù)據(jù)長度之類的包,以確保接收。
二 :粘包出現(xiàn)原因
在流傳輸中出現(xiàn),UDP不會出現(xiàn)粘包,因為它有消息邊界(參考Windows 網(wǎng)絡(luò)編程)
1 發(fā)送端需要等緩沖區(qū)滿才發(fā)送出去,造成粘包
2 接收方不及時接收緩沖區(qū)的包,造成多個包接收
三:怎樣封包和拆包?
封包:
封包就是給一段數(shù)據(jù)加上包頭,這樣一來數(shù)據(jù)包就分為包頭和包體兩部分內(nèi)容了(以后講過濾非法包時封包會加入"包尾"內(nèi)容)。
包頭其實上是個大小固定的結(jié)構(gòu)體,其中有個結(jié)構(gòu)體成員變量表示包體的長度,這是個很重要的變量,其他的結(jié)構(gòu)體成員可根據(jù)需要自己定義。根據(jù)包頭長度固定以及包頭中含有包體長度的變量就能正確的拆分出一個完整的數(shù)據(jù)包。
拆包:
對于拆包目前我最常用的是以下兩種方式:
1、動態(tài)緩沖區(qū)暫存方式。之所以說緩沖區(qū)是動態(tài)的是因為當需要緩沖的數(shù)據(jù)長度超出緩沖區(qū)的長度時會增大緩沖區(qū)長度。
大概過程描述如下:
A 為每一個連接動態(tài)分配一個緩沖區(qū),同時把此緩沖區(qū)和 SOCKET 關(guān)聯(lián),常用的是通過結(jié)構(gòu)體關(guān)聯(lián)。
B 當接收到數(shù)據(jù)時首先把此段數(shù)據(jù)存放在緩沖區(qū)中。
C 判斷緩存區(qū)中的數(shù)據(jù)長度是否夠一個包頭的長度,如不夠,則不進行拆包操作。
D 根據(jù)包頭數(shù)據(jù)解析出里面代表包體長度的變量。
E 判斷緩存區(qū)中除包頭外的數(shù)據(jù)長度是否夠一個包體的長度,如不夠,則不進行拆包操作。
F 取出整個數(shù)據(jù)包,這里的"取"的意思是不光從緩沖區(qū)中拷貝出數(shù)據(jù)包,而且要把此數(shù)據(jù)包從緩存區(qū)中刪除掉。刪除的辦法就是把此包后面的數(shù)據(jù)移動到緩沖區(qū)的起始地址。
這種方法有兩個缺點:
1)為每個連接動態(tài)分配一個緩沖區(qū)增大了內(nèi)存的使用;
2)有三個地方需要拷貝數(shù)據(jù),一個地方是把數(shù)據(jù)存放在緩沖區(qū),一個地方是把完整的數(shù)據(jù)包從緩沖區(qū)取出來,一個地方是把數(shù)據(jù)包從緩沖區(qū)中刪除。這種拆包的改進方法會解決和完善部分缺點。
下面給出相關(guān)代碼.??
先看包頭結(jié)構(gòu)定義
#pragma pack(push,1) //開始定義數(shù)據(jù)包, 采用字節(jié)對齊方式
/----------------------包頭---------------------/
typedef struct tagPACKAGEHEAD
{
BYTE Version;
WORD Command;
WORD nDataLen;//包體的長度
}PACKAGE_HEAD;
#pragma pack(pop) //結(jié)束定義數(shù)據(jù)包, 恢復原來對齊方式
然后看存放數(shù)據(jù)和“取"數(shù)據(jù)函數(shù)
/* Description:添加數(shù)據(jù)到緩存
Input:pBuff[in]-待添加的數(shù)據(jù);
nLen[in]-待添加數(shù)據(jù)長度
Return: 如果當前緩沖區(qū)沒有足夠的空間存放pBuff則返回FALSE;否則返回TRUE。*/
bool CDataBufferPool::AddBuff( char *pBuff, int nLen )
{
m_cs.Lock();///臨界區(qū)鎖
if ( nLen < 0 )
{
m_cs.Unlock();
return false;
}
if(nLen <= GetFreeSize())///判斷剩余空間是否足夠存放nLen長的數(shù)據(jù)
{
memcpy(m_pBuff + m_nOffset, pBuff, nLen);
m_nOffset += nLen;
}
else///若不夠則擴充原有的空間
{
char *p = m_pBuff;
m_nSize += nLen*2;//每次增長2*nLen
m_pBuff = new char[m_nSize];
memcpy(m_pBuff,p,m_nOffset);
delete []p;
memcpy(m_pBuff + m_nOffset, pBuff, nLen);
m_nOffset += nLen;
m_cs.Unlock();
return false;
}
m_cs.Unlock();
return true;
}
拆包(獲取一個完整的包)
/*Description:獲取一個完整的包
Input:Buf[out]-獲取到的數(shù)據(jù);
nLen[out]-獲取到的數(shù)據(jù)長度
Return: 1、當前緩沖區(qū)不夠一個包頭的數(shù)據(jù) 2、當前緩沖區(qū)不夠一個包體的數(shù)據(jù)*/
int CDataBufferPool::GetFullPacket(char *Buf, int& nLen)
{
m_cs.Lock();
if(m_nOffset < m_PacketHeadLen)//當前緩沖區(qū)不夠一個包頭的數(shù)據(jù)
{
m_cs.Unlock();
return 1;
}
PACKAGE_HEAD *p = (PACKAGE_HEAD *)m_pBuff;
if((m_nOffset - m_PacketHeadLen) < (int)p->nDataLen)//當前緩沖區(qū)不夠一個包體的數(shù)據(jù)
{
m_cs.Unlock();
return 2;
}
//判斷包的合法性
/* int IsIntegrallity = ValidatePackIntegrality(p);
if( IsIntegrallity != 0 )
{
m_cs.Unlock();
return IsIntegrallity;
}
*/
nLen = m_PacketHeadLen+p->nDataLen;
memcpy( Buf, m_pBuff, nLen );
m_nOffset -= nLen;
memcpy( m_pBuff, m_pBuff+nLen, m_nOffset );
m_cs.Unlock();
return 0;
}