## 今天学什么
今天学习Qt中TCP粘包处理方法-长度头的代码写法,以及心跳机制。
## TCP粘包处理(长度头代码实现)
定义:每个数据包开头加4字节uint32_t大端数字,代表后面正文的字节长度。
打包步骤:
拆包步骤:
① 打包
QByteArray packMsg(const QByteArray& data)
{
QByteArray res;
uint32_t len = data.size();
uint32_t netLen = qToBigEndian(len);
res.append((char*)&netLen, 4);
res.append(data);
return res;
}
② 拆包(在readyRead里循环解析)
bool unpackMsg(QByteArray& buf, QByteArray& outMsg) //不要用const
{
// 不足4字节,连长度头都收不到
if(buf.size() < 4)
return false;
// 读取头部4字节,解析出正文长度
uint32_t netLen = qFromBigEndian<uint32_t>(buf.data());
// 整条包总长度 = 4字节头 + 正文长度
uint32_t totalLen = netLen + 4;
// 缓冲区数据不够一整条消息,直接返回
if(buf.size() < totalLen)
return false;
// 截取正文,放到输出参数outMsg
outMsg = buf.mid(4, netLen);
// 删掉已经解析完的数据,剩余半包留在buf,下次继续解析
buf = buf.mid(totalLen);
return true;
}
③ 使用
socket->write(packMsg(msg));
while(unpackMsg(*socbuf,m))
{
ui->textEdit->append(m);
}
## 心跳机制
定义:客户端定时向服务端发送专用小包,用来上报自己在线。
作用:区分「客户端闲置没发消息」和「断网/程序崩溃失联」。
工作逻辑:
- 客户端定时发心跳包;
- 服务端收到心跳就刷新存活计时;
- 连续一段时间收不到心跳,判定失联,主动关闭连接。
单个客户端使用示例:
客户端步骤:
服务端步骤
核心代码示例:
① 服务端
// 头文件
QTcpSocket *soc = nullptr;
QTimer *heartCheckTimer;
qint64 lastHeartTime = 0;
bool hasClient = false;
// 构造
heartCheckTimer = new QTimer(this);
heartCheckTimer->start(1000);
// 心跳检测轮询
connect(heartCheckTimer, &QTimer::timeout, this, [this](){
if(!hasClient) return;
auto now = QDateTime::currentMSecsSinceEpoch();
if(now – lastHeartTime > 9000)
{
soc->close();
soc = nullptr;
hasClient = false;
qDebug() << "心跳超时,断开";
}
});
// 收到心跳包
connect(soc, &QTcpSocket::readyRead, this, [this](){
auto recv = soc->readAll();
if(recv == "send ok")
{
lastHeartTime = QDateTime::currentMSecsSinceEpoch();
soc->write("get over");
}
});
// 新客户端接入时
soc = 新拿到的socket;
hasClient = true;
lastHeartTime = QDateTime::currentMSecsSinceEpoch();
② 客户端
// 头文件
QTcpSocket *socket;
QTimer *HeartTimer;
// 构造函数
HeartTimer = new QTimer(this);
// 定时发心跳
connect(HeartTimer, &QTimer::timeout, this, [this](){
socket->write("send ok");
});
// 连上之后启动心跳定时器
connect(socket, &QTcpSocket::connected, this, [this](){
HeartTimer->start(3000);
});
// 断开就停心跳
connect(socket, &QTcpSocket::disconnected, this, [this](){
HeartTimer->stop();
});
## 结合心跳机制和长度头方法改写昨天的代码
代码问题复盘:
① 运行以后的换行问题:当输入长段中文+英文字母,会出现在英文衔接处换行问题;原因:Qt文本默认不在英文单词中间截断;当行尾剩余宽度放不下整串英文时,布局引擎选择在中英交界这个合法断行位置换行;暂时无法解决,跳过。
② 服务端心跳计时必须加客户端连接判断,不然运行定时器时间到了,没有监听和客户端连接直接闪退。
③ 心跳一直自动发包会占用很多内存吗?不会,数据很小。


