TCP粘包現(xiàn)象
TCP
粘包通俗來(lái)講,就是發(fā)送方發(fā)送的多個(gè)數(shù)據(jù)包,到接收方后粘連在一起,導(dǎo)致數(shù)據(jù)包不能完整的體現(xiàn)發(fā)送的數(shù)據(jù)。
TCP粘包原因分析
導(dǎo)致
TCP
粘包的原因,可能是發(fā)送方的原因,也有可能是接受方的原因。
發(fā)送方
由于
TCP
需要盡可能高效和可靠,所以
TCP
協(xié)議默認(rèn)采用
Nagle
算法,以合并相連的小數(shù)據(jù)包,再一次性發(fā)送,以達(dá)到提升網(wǎng)絡(luò)傳輸效率的目的。但是接收方并不知曉發(fā)送方合并數(shù)據(jù)包,而且數(shù)據(jù)包的合并在
TCP
協(xié)議中是沒(méi)有分界線的,所以這就會(huì)導(dǎo)致接收方不能還原其本來(lái)的數(shù)據(jù)包。
接收方
TCP
是基于
“
流
”
的。網(wǎng)絡(luò)傳輸數(shù)據(jù)的速度可能會(huì)快過(guò)接收方處理數(shù)據(jù)的速度,這時(shí)候就會(huì)導(dǎo)致,接收方在讀取緩沖區(qū)時(shí),緩沖區(qū)存在多個(gè)數(shù)據(jù)包。在
TCP
協(xié)議中接收方是一次讀取緩沖區(qū)中的所有內(nèi)容,所以不能反映原本的數(shù)據(jù)信息。
解決TCP粘包
分析了產(chǎn)生
TCP
粘包的原因之后,針對(duì)發(fā)生的原因,針對(duì)性的采取解決方法。
禁用
Negle
算法
因?yàn)?
TCP
協(xié)議采用
Negle
算法,導(dǎo)致粘包。所以可以禁用
Nagle
算法。
const char chOpt = 1;int nErr = setsockopt(m_socket, IPPROTO_TCP, TCP_NODELAY, &chOpt, sizeof(char));
if(nErr == -1)
{
TRACE( "setsockopt() error\n", WSAGetLastError());
return ;
}
這種方法雖然能一定程度上解決
TCP
粘包,但是并不能完全解決問(wèn)題。因?yàn)榻邮辗揭彩强赡茉斐烧嘲脑?,這種方法只是發(fā)送方有效。而且禁用
Nagle
算法,一定程度上使
TCP
傳輸效率降低了。所以,這并不是一種理想的方法。
PUSH
標(biāo)志
PUSH
是
TCP
報(bào)頭中的一個(gè)標(biāo)志位,發(fā)送方在發(fā)送數(shù)據(jù)的時(shí)候可以設(shè)置這個(gè)標(biāo)志位。該標(biāo)志通知接收方將接收到的數(shù)據(jù)全部提交給接收進(jìn)程。這里所說(shuō)的數(shù)據(jù)包括與此
PUSH
包一起傳輸?shù)臄?shù)據(jù)以及之前就為該進(jìn)程傳輸過(guò)來(lái)的數(shù)據(jù)。
當(dāng)
Server
端收到這些數(shù)據(jù)后,它需要立刻將這些數(shù)據(jù)提交給應(yīng)用層進(jìn)程,而不再等待是否還有額外的數(shù)據(jù)到達(dá)。
設(shè)置
PUSH
標(biāo)志也不能完全解決
TCP
粘包,只是降低了接收方粘包的可能性。實(shí)際上現(xiàn)在的
TCP
協(xié)議棧基本上都可以自行處理這個(gè)問(wèn)題,而不是交給應(yīng)用層處理。所以設(shè)置
PUSH
標(biāo)志,也不是一種理想的方法。
自定協(xié)議
自定協(xié)議,將數(shù)據(jù)包分為了封包和解包兩個(gè)過(guò)程。在發(fā)送方發(fā)送數(shù)據(jù)時(shí),對(duì)發(fā)送的數(shù)據(jù)進(jìn)行封包操作。在接收方接收到數(shù)據(jù)時(shí)對(duì)接收的數(shù)據(jù)包需要進(jìn)行解包操作。
自定協(xié)議時(shí),封包就是為發(fā)送的數(shù)據(jù)增加包頭,包頭包含數(shù)據(jù)的大小的信息,數(shù)據(jù)就跟隨在包頭之后。當(dāng)然包頭也可以有其他的信息,比如一些做校驗(yàn)的信息。這里主要討論
TCP
粘包的問(wèn)題,所以不考慮其他的。
發(fā)送方封包
PACKAGE_HEAD pPackageHead; //PACKAGE_HEAD
包頭結(jié)構(gòu)體
char PackageHead[1024];
int headLen = sizeof(PACKAGE_HEAD);
int packgeContextLen = strlen(packageContext); //packageContext
發(fā)送的數(shù)據(jù)
pPackageHead->nDataLen = packgeContextLen; //
包的大小
char *packge = (char*)malloc(headLen + packgeContextLen); //
包的內(nèi)存分配
memset(packge, 0, headLen + packgeContextLen);
char *packgeCpy = (char*)memcpy(packge, (char*)&pPackageHead, headLen);//
拷貝包頭
packgeCpy += headLen;
packge = (char*)memcpy(packgeCpy, (char*)&packageContext, packgeContextLen);//
拷貝包內(nèi)容
int ret = 0;
ret = send(m_hSocket, packge, headLen + packgeContextLen, 0); //
發(fā)送包
if (ret == SOCKET_ERROR || ret == 0)
{
return ret;
}
接收方解包
char PackageHead[1024];char PackageContext[1024*20];
int len;
PACKAGE_HEAD *pPackageHead; //PACKAGE_HEAD
包頭結(jié)構(gòu)體
while( m_bClose == false )
{
memset(PackageHead, 0, sizeof(PACKAGE_HEAD));
len = ReceiveSize(m_TcpSock, (char*)PackageHead, sizeof(PACKAGE_HEAD)); //
接收包頭
if( len == SOCKET_ERROR )
{
break;
}
if(len == 0)
{
break;
}
pPackageHead = (PACKAGE_HEAD *)PackageHead;
memset(PackageContext,0,sizeof(PackageContext));
if(pPackageHead->nDataLen>0) //
根據(jù)包頭中的數(shù)據(jù)長(zhǎng)度,接收數(shù)據(jù)
{
len = ReceiveSize(m_TcpSock, (char*)PackageContext,pPackageHead->nDataLen);
}
}
接收指定長(zhǎng)度的數(shù)據(jù)函數(shù)
//
接收指定長(zhǎng)度的數(shù)據(jù)
int ReceiveSize(SOCKET m_hSocket, char* strData, int gLen){
if(strData == NULL)
return ERR_BADPARAM;
char *p = strData;
int len = gLen;
int ret = 0;
int returnlen = 0;
while( len > 0)
{
ret = recv( m_hSocket, p+(iLen-len), iLen-returnlen, 0);
if (ret == SOCKET_ERROR || ret == 0)
{
return ret;
}
len -= ret;
returnlen += ret;
}
return returnlen;
}
這樣就可以達(dá)到解決
TCP
粘包的問(wèn)題。在實(shí)際使用中包頭還帶有更多的信息,而且包尾可能還會(huì)帶上分隔符,在
redis
、
FTP
中就是這樣處理的。
UDP不存在粘包
由于
UDP
不是面向
‘
流
’
的,而且
UDP
是具有消息邊界的。也就是說(shuō)
UDP
的發(fā)送的每一個(gè)數(shù)據(jù)包都是獨(dú)立的。所以
UDP
并不存在粘包的問(wèn)題。
原文來(lái)自:
cnblogs