TCP通信中的丢包粘包问题一直是一个麻烦,特别是通信数据量大而且频繁的场景。不同的通信协议解决方案也有所不同,本文的通信协议为报文设置起始符+结束符。
示例程序设置起始符为"<@#", 结束符为"@#>"。核心思路就是先用一个变量把每次收到的报文都拼起来,根据起始符和结束符截取包,截取包后剩下的报文用来继续拼接后面的报文。由于缓冲区大小的原因可能会出现报文中间截断,但是TCP是可靠性传输,所以不会出现顺序错乱的问题。关键代码如下:
void WidTcpServer::SlotReadReady()
{
//解包
auto funUnPakege = [this](const QByteArray& byteReceive)
{
QByteArray strBegin = "<@#"; //起始符
QByteArray strEnd = "@#>"; //结束符
int nMinSize = strBegin.size() + strEnd.size(); //完整的包最小长度
std::lock_guard<std::mutex> lockTcp(m_mutexTcp); //加锁
m_strReceive += byteReceive;
while (m_strReceive.size() > nMinSize) //剩余长度大于一个包的长度就继续查找
{
int nBegin = m_strReceive.indexOf(strBegin); //找到第一个起始符
if (nBegin < 0)
break;
int nEnd = 0, nPos = 0;
while (nEnd >= 0) //找到第一个起始符后面的第一个结束符
{
nEnd = m_strReceive.indexOf(strEnd, nPos);
if (nEnd >= nBegin)
break;
nPos = nEnd + 1;
}
if (nEnd <= nBegin)
break;
QByteArray bytePakage = m_strReceive.mid(nBegin + strBegin.size(), nEnd - nBegin - strBegin.size()); //解析到一个完整的包
ShowPakage(bytePakage);
m_strReceive = m_strReceive.right(m_strReceive.size() - nEnd - strEnd.size()); //截取当前包后面的字符串,用作下一次拼接包
}
emit SigShowMessage(QString("Current data:%1").arg(m_strReceive.data()), Qt::blue);
};
QByteArray byteReceive = m_pTcpSocket->readAll();
std::thread threadTcpData(funUnPakege, byteReceive);
threadTcpData.detach();
}
程序测试:紫色为接收到的包内容,蓝色为当前截取包后剩余的报文内容。
1、两个包一起发,收到两个包
2、发一个半,再发后面半个,也收到两个包
3、中间夹杂其他无效的报文,结果收到两个包并且过滤掉无效报文内容
本工程源码百度网盘 请输入提取码,提取码5032