TCP粘包是指發(fā)送方發(fā)送的若干包數(shù)據(jù)到接收方接收時粘成一包,從接收緩沖區(qū)看,后一包數(shù)據(jù)的頭緊接著前一包數(shù)據(jù)的尾。
出現(xiàn)粘包現(xiàn)象的原因既可能由發(fā)送方造成,也可能由接收方造成。
發(fā)送方引起的粘包是由TCP協(xié)議本身造成的,TCP為提高傳輸效率,發(fā)送方往往要收集到足夠多的數(shù)據(jù)后才發(fā)送一包數(shù)據(jù)。若連續(xù)幾次發(fā)送的數(shù)據(jù)都很少,通常TCP會根據(jù)優(yōu)化算法把這些數(shù)據(jù)合成一包后一次發(fā)送出去,這樣接收方就收到了粘包數(shù)據(jù)。
接收方引起的粘包是由于接收方用戶進程不及時接收數(shù)據(jù),從而導致粘包現(xiàn)象。這是因為接收方先把收到的數(shù)據(jù)放在系統(tǒng)接收緩沖區(qū),用戶進程從該緩沖區(qū)取數(shù)據(jù),若下一包數(shù)據(jù)到達時前一包數(shù)據(jù)尚未被用戶進程取走,則下一包數(shù)據(jù)放到系統(tǒng)接收緩沖區(qū)時就接到前一包數(shù)據(jù)之后,而用戶進程根據(jù)預先設定的緩沖區(qū)大小從系統(tǒng)接收緩沖區(qū)取數(shù)據(jù),這樣就一次取到了多包數(shù)據(jù)。
c++的解決方法如下:接收端
int iRecvSize = PackteSize + 10;
int iRet;
int idx = 0;
while (iRecvSize > 0)
{
iRet = recv(AcceptSocket, recvbuf+idx, iRecvSize, 0);
if (iRet > 0)
{
idx += iRet;
iRecvSize -= iRet;
}
else if (iRet == 0)
{
break;
}
else if ( iRet == SOCKET_ERROR)
{
break;
}
}
發(fā)送端:
int iSendSize = PacketSize + 10;
int iSent;
int idx = 0;
while (iSendSize > 0)
{
iSent = send(m_socket,sendbuffer+idx,iSendSize,0);
if (iSent > 0)
{
idx += iSent;
iSendSize -= iSent;
}
else if (iSent == 0)
{
break;
}
else if (iSent == SOCKET_ERROR)
{
wprintf(L"send failed with error: %d\n", WSAGetLastError());
//closesocket(m_socket);
//WSACleanup();
break;
}
}
若傳輸?shù)臄?shù)據(jù)為不帶結(jié)構(gòu)的連續(xù)流數(shù)據(jù)(如文件傳輸),則不必把粘連的包分開(簡稱分包),反之則必須分包。
c#中用socket的Available成員來表示是否還有數(shù)據(jù)需要讀取,如果Available>0,表示還有數(shù)據(jù)需有讀取,反之讀取完成。
public class ConnectInfo
{
public ArrayList tmpList { get; set; }
public SocketAsyncEventArgs SendArg { get; set; }
public SocketAsyncEventArgs ReceiveArg { get; set; }
public Socket ServerSocket { get; set; }
public User user = new User();
}
if (client.Available > 0)
{
Console.WriteLine("粘包處理");
for (int i = 0; i < rec; i++)
info.tmpList.Add(datas[i]);
Array.Clear(datas, 0, datas.Length);
datas = new byte[client.Available];
e.SetBuffer(datas, 0, datas.Length);
client.ReceiveAsync(e);
}
else
{
//檢查暫存數(shù)據(jù)的ArrayList中有沒有數(shù)據(jù),有就和本次的數(shù)據(jù)合并
if (info.tmpList.Count > 0)
{
for (int i = 0; i < rec; i++)
info.tmpList.Add(datas[i]);
datas = info.tmpList.ToArray(typeof(byte)) as byte[];
rec = datas.Length;
}
//對接收的完整數(shù)據(jù)進行處理
}
半包顧名思義,就不是一個完整的包,tcp發(fā)出一個段后,它啟動一個定時器,等待目的端確認收到這個報文段。如果不能及時收到一個確認,將重發(fā)這個報文段。
常見的解決方法就是制定規(guī)范的數(shù)據(jù)傳輸格式。