TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
出现粘包现象的原因既可能由发送方造成,也可能由接收方造成。
发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。
接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。
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;
}
}
发送端:
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;
}
}
若传输的数据为不带结构的连续流数据(如文件传输),则不必把粘连的包分开(简称分包),反之则必须分包。
c#中用socket的Available成员来表示是否还有数据需要读取,如果Available>0,表示还有数据需有读取,反之读取完成。
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
{
//检查暂存数据的ArrayList中有没有数据,有就和本次的数据合并
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;
}
//对接收的完整数据进行处理
}
半包顾名思义,就不是一个完整的包,tcp发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
常见的解决方法就是制定规范的数据传输格式。