一.为什么会出现粘包?
如果利用tcp每次发送数据,就与对方建立连接,然后双方发送完一段数据后,就关闭连接,这样就不会出现粘包问题(因为只有一种包结构)。关闭连接主要要双方都发送close连接。如:A需要发送一段字符串给B,那么A与B建立连接,然后发送双方都默认好的协议字符如”hello give me sth abour yourself”,然后B收到报文后,就将缓冲区数据接收,然后关闭连接,这样粘包问题不用考虑到,因为大家都知道是发送一段字符。
但是如果双方建立连接,需要在连接后一段时间内发送不同结构数据,如连接后,有好几种不同的结构类型:
①”hello give me abour your message”
②”Don’t give me abour your message”
这样接收方就l楞了,到底应该怎么分了?因为没有协议规定怎么拆分这段字符串,所以要处理好分包,需要双方组织一个比较好的包结构,包头一般存放包的大小以及地址等,包体就是要存放需解析的数据。
二.如何解决这种问题?
常规又实用的方法就是对数据包进行封包和拆包的操作
封包
封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了,如:
typedef struct
{
char c1;
char c2;
unsigned char flag;
unsigned short cellcount;
unsigned short size;
unsigned short address;
}Head_t;
typedef struct
{
unsigned char type;
unsigned short id;
unsigned short value;
}Cell5_t;
typedef struct
{
Head_t head;
Cell5_t cell;
}Packet_all_st;
包头其实上是个大小固定的结构体,其中有个结构体成员变量表示包体的长度,这是个很重要的变量,其他的结构体成员可根据需要自己定义。根据包头长度固定以及包头中含有包体长度的变量就能正确的拆分出一个完整的数据包。
具体操作如下:
//在槽函数里调用sender 返回信号来源的对象
QTcpSocket *l_retSocket = qobject_cast(sender());
if (l_retSocket == m_dataSocket)
{
QByteArray l_buff;
l_buff += l_retSocket->readAll();
int len = l_buff.size();
//解包
if (l_buff.size() < sizeof(Head_t))
{
return false;
}
// const char *l_data = l_buff.constData();
const char *l_data = l_buff;
memcpy(&this->m_head, l_buff, sizeof(Head_t));
m_head.cellcount = ntohs(m_head.cellcount);
m_head.size = ntohs(m_head.size);
m_head.address = ntohs(m_head.address);
if (l_buff.size() < sizeof(Packet_all_st))
{
return false;
}
memcpy(&this->m_cell, l_data + sizeof(Head_t), sizeof(Cell5_t));
m_cell.id = ntohs(m_cell.id);
//单字节内存中的比特不受字节序影响
m_cell.type = m_cell.type;
m_cell.value = ntohs(m_cell.value);
拆包
利用底层的缓冲区来进行拆包,由于TCP也维护了一个缓冲区,所以我们完全可以利用TCP的缓冲区来缓存我们的数据,这样一来就不需要为每一个连接分配一个缓冲区了,对于利用缓冲区来拆包,也就是循环不停的接收包的数据,直到收够为止,这就是一个完整的TCP包。
Head_t head;
memset(&head, sizeof(head), 0);
head.c1 = HEAD_C1;
head.c2 = HEAD_C2;
head.flag = FROM_MASTER;
head.cellcount = htons(cellcount);
head.address = htons(address);
head.size = htons(cells_size + sizeof(head));
memcpy(msendData, &this->m_head, sizeof(Head_t));
memcpy(msendData + sizeof(Head_t), &this->m_cell, sizeof(Cell5_t));
//发送数据
m_dataSocket->write(msendData, sizeof(Head_t) + sizeof(Cell5_t));