一 : 什么时候需要考虑粘包问题?
1:如果利用tcp每次发送数据,就与对方建立连接,然后双方发送完一段数据后,就关闭连接,这样就不会出现粘包问题(因为只有一种包结构,类似于http协议)。关闭连接主要要双方都发送close连接(参考tcp关闭协议)。如:A需要发送一段字符串给B,那么A与B建立连接,然后发送双方都默认好的协议字符如"hello give me sth abour yourself",然后B收到报文后,就将缓冲区数据接收,然后关闭连接,这样粘包问题不用考虑到,因为大家都知道是发送一段字符。
2:如果发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接收存储就ok,也不用考虑粘包
3:如果双方建立连接,需要在连接后一段时间内发送不同结构数据,如连接后,有好几种结构:
1)"hello give me sth abour yourself"
2)"Don't give me sth abour yourself"
那这样的话,如果发送方连续发送这个两个包出去,接收方一次接收可能会是"hello give me sth abour yourselfDon't give me sth abour yourself" 这样接收方就傻了,到底是要干嘛?不知道,因为协议没有规定这么诡异的字符串,所以要处理把它分包,怎么分也需要双方组织一个比较好的包结构,所以一般可能会在头加一个数据长度之类的包,以确保接收。
二 :粘包出现原因
在流传输中出现,UDP不会出现粘包,因为它有消息边界(参考Windows 网络编程)
1 发送端需要等缓冲区满才发送出去,造成粘包
2 接收方不及时接收缓冲区的包,造成多个包接收
三:怎样封包和拆包?
封包:
封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了(以后讲过滤非法包时封包会加入"包尾"内容)。
包头其实上是个大小固定的结构体,其中有个结构体成员变量表示包体的长度,这是个很重要的变量,其他的结构体成员可根据需要自己定义。根据包头长度固定以及包头中含有包体长度的变量就能正确的拆分出一个完整的数据包。
拆包:
对于拆包目前我最常用的是以下两种方式:
1、动态缓冲区暂存方式。之所以说缓冲区是动态的是因为当需要缓冲的数据长度超出缓冲区的长度时会增大缓冲区长度。
大概过程描述如下:
A 为每一个连接动态分配一个缓冲区,同时把此缓冲区和 SOCKET 关联,常用的是通过结构体关联。
B 当接收到数据时首先把此段数据存放在缓冲区中。
C 判断缓存区中的数据长度是否够一个包头的长度,如不够,则不进行拆包操作。
D 根据包头数据解析出里面代表包体长度的变量。
E 判断缓存区中除包头外的数据长度是否够一个包体的长度,如不够,则不进行拆包操作。
F 取出整个数据包,这里的"取"的意思是不光从缓冲区中拷贝出数据包,而且要把此数据包从缓存区中删除掉。删除的办法就是把此包后面的数据移动到缓冲区的起始地址。
这种方法有两个缺点:
1)为每个连接动态分配一个缓冲区增大了内存的使用;
2)有三个地方需要拷贝数据,一个地方是把数据存放在缓冲区,一个地方是把完整的数据包从缓冲区取出来,一个地方是把数据包从缓冲区中删除。这种拆包的改进方法会解决和完善部分缺点。
下面给出相关代码.😁
先看包头结构定义
#pragma pack(push,1) //开始定义数据包, 采用字节对齐方式
/----------------------包头---------------------/
typedef struct tagPACKAGEHEAD
{
BYTE Version;
WORD Command;
WORD nDataLen;//包体的长度
}PACKAGE_HEAD;
#pragma pack(pop) //结束定义数据包, 恢复原来对齐方式
然后看存放数据和“取"数据函数
/* Description:添加数据到缓存
Input:pBuff[in]-待添加的数据;
nLen[in]-待添加数据长度
Return: 如果当前缓冲区没有足够的空间存放pBuff则返回FALSE;否则返回TRUE。*/
bool CDataBufferPool::AddBuff( char *pBuff, int nLen )
{
m_cs.Lock();///临界区锁
if ( nLen < 0 )
{
m_cs.Unlock();
return false;
}
if(nLen <= GetFreeSize())///判断剩余空间是否足够存放nLen长的数据
{
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]-获取到的数据;
nLen[out]-获取到的数据长度
Return: 1、当前缓冲区不够一个包头的数据 2、当前缓冲区不够一个包体的数据*/
int CDataBufferPool::GetFullPacket(char *Buf, int& nLen)
{
m_cs.Lock();
if(m_nOffset < m_PacketHeadLen)//当前缓冲区不够一个包头的数据
{
m_cs.Unlock();
return 1;
}
PACKAGE_HEAD *p = (PACKAGE_HEAD *)m_pBuff;
if((m_nOffset - m_PacketHeadLen) < (int)p->nDataLen)//当前缓冲区不够一个包体的数据
{
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;
}