news 2026/9/2 14:02:38

Qt TCP 心跳到底应该怎么设计?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Qt TCP 心跳到底应该怎么设计?

别让心跳把连接“越检测越死”

做 TCP 长连接时,有一个问题几乎躲不过:

TCP 已经连接了,怎么知道对方还活着?

很多人的第一反应就是:

QTimer timer; connect(&timer, &QTimer::timeout, this, [this]() { socket->write("ping"); });

看起来没问题。
但真正上线以后,你会发现:

  • 网线拔了,connected()可能还是保持着

  • 对端进程崩了,本地不一定马上收到disconnected()

  • Wi-Fi 切换网络,连接可能进入“半死不活”状态

  • 服务端还活着,但业务线程已经卡死,TCP 依然是ESTABLISHED

所以:

TCP 连接成功 ≠ 对端业务正常。

这也是为什么生产环境里的 TCP 长连接,通常都会设计应用层心跳机制


一、TCP 为什么需要心跳?

TCP 本身提供可靠传输,但它并不会主动告诉你:

“对方应用程序现在是不是还正常运行?”

例如:

Client Server │ │ │──── TCP Connect ───────>│ │<─── Connect OK ─────────│ │ │ │ │ │ 网络断开 X │ │ │ │ │ TCP 连接状态可能仍然存在 │

尤其是网络异常时:

客户端 │ │ ESTABLISHED │ X────── 网络已经断了 │ │ │ 但应用层暂时不知道

这时候如果你的程序只是:

if (socket->state() == QAbstractSocket::ConnectedState) { // 认为连接正常 }

就容易踩坑。


二、一个靠谱的心跳应该怎么设计?

推荐使用:

Client Server │ │ │────── HEARTBEAT_REQ ─────────>│ │ │ │<───── HEARTBEAT_ACK ──────────│ │ │ │────── HEARTBEAT_REQ ─────────>│ │ │ │<───── HEARTBEAT_ACK ──────────│

客户端维护三个状态:

lastSendTime lastRecvTime heartbeatTimer

例如:

每 5 秒发送一次心跳 5s │ ├── ping │ ├── 等待 pong │ ├── 5s │ ├── ping │ └── ...

如果连续一段时间没有收到 ACK:

超过 15 秒 ↓ 认为连接异常 ↓ 主动 disconnect ↓ 进入重连

这里有一个非常重要的原则:

不要因为一次心跳超时就马上断开。

网络偶尔抖一下很正常。
通常可以采用:

心跳周期:5 秒 超时时间:15 秒

也就是允许连续丢失几个心跳。


三、不要只发送 Ping,还要判断 Pong

最简单的协议可以设计成:

PING PONG

例如:

socket->write("PING");

服务器收到:

if (data == "PING") { socket->write("PONG"); }

客户端收到:

if (data == "PONG") { lastRecvTime = QDateTime::currentMSecsSinceEpoch(); }

这样才能形成完整闭环:

发送 Ping ↓ Server 收到 ↓ Server 回复 Pong ↓ Client 收到 ↓ 更新 lastRecvTime

四、完整 Qt 客户端示例

下面写一个比较实用的版本。

HeartbeatClient.h

#pragma once #include <QObject> #include <QTcpSocket> #include <QTimer> #include <QElapsedTimer> class HeartbeatClient : public QObject { Q_OBJECT public: explicit HeartbeatClient(QObject *parent = nullptr); void connectToServer(const QString &host, quint16 port); private slots: void onConnected(); void onDisconnected(); void onReadyRead(); void sendHeartbeat(); void checkHeartbeat(); private: QTcpSocket *m_socket = nullptr; QTimer m_heartbeatTimer; QTimer m_checkTimer; qint64 m_lastReceiveTime = 0; QString m_host; quint16 m_port = 0; };

HeartbeatClient.cpp

#include "HeartbeatClient.h" #include <QDateTime> #include <QDebug> HeartbeatClient::HeartbeatClient(QObject *parent) : QObject(parent) { m_socket = new QTcpSocket(this); connect(m_socket, &QTcpSocket::connected, this, &HeartbeatClient::onConnected); connect(m_socket, &QTcpSocket::disconnected, this, &HeartbeatClient::onDisconnected); connect(m_socket, &QTcpSocket::readyRead, this, &HeartbeatClient::onReadyRead); // 每 5 秒发送一次心跳 m_heartbeatTimer.setInterval(5000); connect(&m_heartbeatTimer, &QTimer::timeout, this, &HeartbeatClient::sendHeartbeat); // 每 1 秒检查一次心跳状态 m_checkTimer.setInterval(1000); connect(&m_checkTimer, &QTimer::timeout, this, &HeartbeatClient::checkHeartbeat); } void HeartbeatClient::connectToServer(const QString &host, quint16 port) { m_host = host; m_port = port; m_socket->connectToHost(host, port); }
连接成功
void HeartbeatClient::onConnected() { qDebug() << "TCP connected"; m_lastReceiveTime = QDateTime::currentMSecsSinceEpoch(); m_heartbeatTimer.start(); m_checkTimer.start(); }
断开连接
void HeartbeatClient::onDisconnected() { qDebug() << "TCP disconnected"; m_heartbeatTimer.stop(); m_checkTimer.stop(); }
发送心跳
void HeartbeatClient::sendHeartbeat() { if (m_socket->state() != QAbstractSocket::ConnectedState) return; m_socket->write("PING\n"); m_socket->flush(); qDebug() << "send PING"; }
收到数据
void HeartbeatClient::onReadyRead() { while (m_socket->canReadLine()) { QByteArray data = m_socket->readLine().trimmed(); if (data == "PONG") { m_lastReceiveTime = QDateTime::currentMSecsSinceEpoch(); qDebug() << "receive PONG"; } else { // 处理正常业务数据 qDebug() << "business data:" << data; } } }
超时检测(最关键)
void HeartbeatClient::checkHeartbeat() { if (m_socket->state() != QAbstractSocket::ConnectedState) return; const qint64 now = QDateTime::currentMSecsSinceEpoch(); const qint64 elapsed = now - m_lastReceiveTime; // 15 秒没有收到任何数据 if (elapsed > 15000) { qDebug() << "heartbeat timeout"; m_socket->abort(); // 后续可以进入重连流程 } }

这样一个最基本的 TCP 心跳机制就完成了。


五、服务器端其实更简单

服务端收到PING回复PONG即可:

void ClientConnection::onReadyRead() { while (m_socket->canReadLine()) { QByteArray data = m_socket->readLine().trimmed(); if (data == "PING") { m_socket->write("PONG\n"); } else { handleBusinessMessage(data); } } }

整体流程:

六、这里有一个非常容易犯的错误

很多代码会这么写:

if (socket->bytesAvailable() == 0) { // 对方挂了 }

这是错误的。
没有数据 ≠ TCP 断开。

TCP 是一个字节流协议。
对方可能只是:

5 秒没发送业务数据

并不代表:

对方已经死亡

所以必须使用:

PING → PONG

这种应用层机制主动确认。

九、最后总结一下

Qt TCP 心跳真正要解决的不是:

“每隔几秒发送一个字符串。”

而是:

如何判断一个 TCP 长连接是否仍然具备业务可用性。

推荐的基本方案:

心跳周期:5s 超时时间:15s 协议:PING / PONG

核心逻辑:

每 5 秒 │ ▼ 发送 PING │ ▼ 等待 PONG │ ▼ 更新 lastReceive │ ▼ 是否超过 15 秒? / \ 否 是 │ │ ▼ ▼ 正常 断开连接 │ ▼ 重连

而在真正的工业软件、IM、设备通信、车载系统中,还应该继续考虑:

  • 消息长度协议

  • 心跳与业务消息复用

  • 超时次数

  • 网络抖动

  • 自动重连

  • 指数退避

  • 连接状态机

  • 线程安全

  • 半连接检测

  • 服务端多客户端心跳管理


一句话记住:

TCP 负责“可靠传输”,心跳负责“确认对方还活着”,重连负责“连接死了之后怎么办”。

这三件事情不要混在一起设计,TCP 长连接才会真正稳定。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 14:02:35

游戏本怎么选?从功耗释放到高刷屏再到保修,一步到位不踩坑

每次有朋友让我推荐游戏本&#xff0c;我最先问的不是预算&#xff0c;也不是“你要不要玩3A”&#xff0c;而是“你打算一台电脑用多久&#xff0c;平时最长时间跑什么负载”。 这个问题问完&#xff0c;大部分人的答案大概能分成两类&#xff1a;一类是买回来主要图个新鲜&a…

作者头像 李华
网站建设 2026/9/2 14:02:23

如何免费把微信聊天记录导出成HTML:完整新手指南

如何免费把微信聊天记录导出成HTML&#xff1a;完整新手指南 【免费下载链接】WeChatMsg 提取微信聊天记录&#xff0c;将其导出成HTML、Word、CSV文档永久保存&#xff0c;对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending/we/WeChatMsg …

作者头像 李华
网站建设 2026/9/2 14:01:15

AI编程时代的技术决策框架:从技能、工具到环境的全维度实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/2 14:01:03

好用的AI论文写作工具分享,AI论文写作工具大合集!

大学生写论文&#xff0c;优先选中文适配、学术合规、有免费额度、能降重 / 控 AI 率、自动排版的工具。接下来按场景推荐工具&#xff0c;每款附带核心功能、免费 / 付费情况、适用人群&#xff0c;方便读者直接选型。 一、全流程全能型&#xff08;从开题到答辩一站式&#x…

作者头像 李华
网站建设 2026/9/2 13:59:05

STM32平衡车项目实战:从PID算法到传感器融合的嵌入式控制全解析

简介&#xff1a;本资源是面向嵌入式初学者与机器人爱好者的一站式平衡车开发套件&#xff0c;基于STM32F103 Cortex-M3主控&#xff0c;聚焦自平衡控制原理与工程实现&#xff0c;解决从硬件搭建、传感器融合到闭环算法调试的核心学习难点。压缩包共50.91MB&#xff0c;涵盖原…

作者头像 李华