资讯动态

基于Qt与C++的TCP网络通信框架:从设计到联调全解析

发布时间:2026/8/31 13:20:43 来源:尧图企业网站定制
简介本资源是一套基于Qt框架与C语言实现的完整TCP网络通信示例程序面向计算机科学、信息安全、物联网、人工智能等专业的在校学生及初学者用于支撑毕业设计、课程设计、期末大作业等实践教学场景。项目包含功能完备的客户端与服务端双模块代码经实际编译运行验证稳定可靠可直接部署调试或作为二次开发基础。压缩包共12个文件7KB涵盖4个核心.cpp源文件、2个.ui界面设计文件、2个.h头文件、2个.pro工程配置文件及2份README.md说明文档结构清晰便于理解Qt信号槽机制、QTcpSocket通信流程与多线程交互逻辑。目前已有246人学习下载适合从零掌握TCP Socket编程、Qt GUI集成与网络应用开发全流程的入门到进阶学习者。 很多初学者接触Qt的第一反应是做界面、画控件、做个小工具。但Qt真正拉开和普通C GUI框架差距的恰恰是它内置的、设计得很成熟的网络模块。这篇文章我会把基于Qt和C实现的一套TCP网络通信源码拆开讲包含客户端和服务端从设计思路到关键代码再到联调中真正会遇到的坑全流程过一遍。无论你是做课程设计、上位机开发还是想给个人项目补一个可靠的通信底座这篇文章都适用。我先把话放前面网上Qt TCP通信的demo一抓一大把但大部分只做到了能通——一个服务端收一个客户端发收完就完事。真实项目里你还要面对多客户端管理、粘包半包、断线重连、界面卡顿、线程安全这些实际问题。这套源码把这些问题都考虑进去了所以它不是一个教学玩具而是一个能拿去改造直接用的基础框架。1. 为什么选Qt做TCP通信不只是因为界面方便1.1 纯C写TCP和Qt写TCP的本质差异用纯C写TCP通信流程是固定的socket()创建套接字bind()绑定地址listen()监听accept()接收连接recv()/send()收发数据。这套流程在Linux和Windows上API还不一样——Linux用BSD socketWindows要用Winsock需要先WSAStartup初始化链接ws2_32.lib各种宏定义还要做平台判断。Qt把这一层全部封装掉了。QTcpServer和QTcpSocket这两个类屏蔽了操作系统差异同一套代码在Windows、Linux、macOS上直接编译不需要写任何条件编译。我实际测试过代码完全一样只是.pro文件里不用额外加ws2_32库Qt的network模块已经处理好了。封装只是表象核心差异在于事件循环。Qt网络模块是非阻塞事件驱动的数据到达时通过信号通知你而不是用一个线程阻塞在recv()上等数据。这套机制和纯C里用select、poll、epoll实现的东西是同一层级的但Qt把复杂度藏在了信号槽后面写起来舒服得多。1.2 这个项目要解决什么问题一套可复用的通信基础框架这套源码不是只跑通一个客户端发给服务端的demo它同时解决了几件实际项目中绕不开的事第一服务端多客户端管理。真实场景下服务端不可能只服务一个客户端多个设备同时接入是常态。代码里用QHash维护每个连接的QTcpSocket指针支持任意多个客户端同时在线任何一个断开都不影响其他连接。第二客户端和服务端在同一套代码框架下完整实现。很多教程只写服务端或者只写客户端另外一半让你自己补。这个项目把两端都做了而且配套联调过协议一致不会出现服务端发的是GBK客户端按UTF-8解析这种低级错位。第三消息按行解析解决粘包半包问题。不要小看这一点这是TCP网络编程里最容易被新手忽略的硬骨头。代码里用的是自定义分隔符协议——每条消息以\n结尾接收端攒够一条完整的行才对外发出信号。这样发送方无论怎么粘包、半包接收方都能正确还原后面我会专门讲这块。第四界面线程和网络线程解耦。网络数据到达是异步的界面的刷新必须在主线程代码里用信号槽的队列连接保证跨线程安全更新不会出现界面直接崩溃或者数据错乱。2. 服务端实现监听、连接管理、收发与断线回收2.1 从QTcpServer建立监听的完整闭环服务端的第一步是建立监听。QTcpServer的使用模式基本是固定的// 服务端核心类继承QObject class TcpServer : public QObject { Q_OBJECT public: explicit TcpServer(QObject *parent nullptr); bool startListen(quint16 port); void stopListen(); void sendToClient(const QString clientId, const QByteArray data); void broadcast(const QByteArray data); signals: void clientConnected(const QString clientId); void clientDisconnected(const QString clientId); void dataReceived(const QString clientId, const QByteArray data); void logMessage(const QString msg); private slots: void onNewConnection(); void onClientReadyRead(); void onClientDisconnected(); private: QTcpServer *m_server; QHashQString, QTcpSocket* m_clients; // 管理所有连接 };关键在startListenbool TcpServer::startListen(quint16 port) { if (m_server-isListening()) { return true; } bool ok m_server-listen(QHostAddress::Any, port); if (ok) { emit logMessage(QString(服务端已启动监听端口: %1).arg(port)); } else { emit logMessage(QString(监听失败: %1).arg(m_server-errorString())); } return ok; }这里有个细节值得展开QHostAddress::Any监听的是本机所有网卡地址。如果只想允许本机访问比如调试时可以用QHostAddress::LocalHost也就是127.0.0.1。如果想让局域网内其他机器也能连上来必须用QHostAddress::Any。这个选择会影响能不能被外部访问很多人在这一步就栽了——本机能连换台机器就超时多半是绑定的地址不对。2.2 多客户端连接管理与身份标识当有新客户端连接时QTcpServer自动触发newConnection()信号void TcpServer::onNewConnection() { while (m_server-hasPendingConnections()) { QTcpSocket *socket m_server-nextPendingConnection(); // 生成一个随机ID作为该连接的标识 QString clientId QString(client_%1).arg(QUuid::createUuid().toString(QUuid::WithoutBraces).left(8)); m_clients.insert(clientId, socket); connect(socket, QTcpSocket::readyRead, this, TcpServer::onClientReadyRead); connect(socket, QTcpSocket::disconnected, this, TcpServer::onClientDisconnected); emit clientConnected(clientId); emit logMessage(QString(新客户端接入: %1当前在线数: %2).arg(clientId).arg(m_clients.size())); } }这里用while (hasPendingConnections())循环而不是if是因为newConnection信号一次可能携带多个待处理的连接。如果只用if高并发下会有连接被遗留在队列里客户端那边表现为连上了但服务端没反应。这个问题很隐蔽触发条件不稳定我调试时卡了挺久才意识到是少了一层循环。每个连接分配唯一的clientId后续收发、断开都靠这个ID准确定位到具体连接。如果不需要身份管理直接用socket指针做key也是一样的但ID更利于日志排查——你不容易记住一堆十六进制指针地址但你能记住client_ab12cd34是哪台设备。2.3 接收数据与按行解析的容错设计数据接收是网络编程最核心的地方。代码没有直接用socket-readAll()然后发信号而是先做了一层缓冲区累积void TcpServer::onClientReadyRead() { QTcpSocket *socket qobject_castQTcpSocket*(sender()); if (!socket) return; QString clientId m_clients.key(socket); if (clientId.isEmpty()) return; // 累积到缓冲区 m_buffer[clientId].append(socket-readAll()); // 按行解析 while (true) { int index m_buffer[clientId].indexOf(\n); if (index 0) break; QByteArray line m_buffer[clientId].left(index).trimmed(); m_buffer[clientId].remove(0, index 1); if (!line.isEmpty()) { emit dataReceived(clientId, line); } } }这套逻辑就是在处理经典的粘包和半包问题。粘包发送方连续发两条消息接收方可能一次性收到两条拼在一起的数据。按行切分就能把拼在一起的两条消息拆开。半包发送方发一条长消息底层TCP协议可能把它拆成多次才到达。缓冲区累积等\n出现才处理就不会把一条不完整的消息当成完整消息。为什么选\n做分隔符因为它简单、可靠、跨平台无歧义。还有很多协议会使用固定长度的包头比如4字节长度字段正文适合二进制数据。文本协议优先用分隔符你要是跑通了这套源码再去做JSON字符串传输直接把QByteArray换成JSON序列化后的字节流就行解析逻辑不用动。2.4 断线检测与资源回收断线处理是很多demo完全没写的部分。TCP连接断开时有两种情况对方正常关闭协议栈会返回FIN包Qt触发disconnected()信号对方异常掉线断电、拔网线、程序崩溃TCP协议栈要等超时才能发现这时候disconnected()不会立即触发。这套代码处理的是正常关闭异常掉线要配合心跳机制做超时检测后面我再讲进阶方案。void TcpServer::onClientDisconnected() { QTcpSocket *socket qobject_castQTcpSocket*(sender()); if (!socket) return; QString clientId m_clients.key(socket); if (!clientId.isEmpty()) { m_clients.remove(clientId); m_buffer.remove(clientId); emit clientDisconnected(clientId); emit logMessage(QString(客户端断开: %1当前在线数: %2).arg(clientId).arg(m_clients.size())); } socket-deleteLater(); }这里有一个必须养成的习惯socket-deleteLater()而不是delete socket。因为当前正处在socket某个信号的槽函数中直接delete会让Qt的事件循环在处理返回时访问已经释放的对象轻则崩溃重则随机崩溃。deleteLater()会在该轮事件处理结束后再清理对象安全得多。另外m_buffer这个缓冲区如果不清除断开一个客户端后它的遗留数据会永远占着内存。虽然量小但这个习惯应该保持——网络编程容不得任何内存泄漏或者无界增长。3. 客户端实现连接管理、消息封装与UI交互3.1 客户端连接管理连接、重连和状态机客户端相对简单但状态管理同样要仔细。客户端需要维护一个清晰的状态流转未连接 → 连接中 → 已连接 → 断开→ 重连。代码里用一个枚举enum class ClientState { Disconnected, Connecting, Connected };连接动作本身是异步的要点在这里void TcpClient::connectToServer(const QString host, quint16 port) { if (m_socket-state() QAbstractSocket::ConnectedState) { emit logMessage(当前已经连接无需重复连接); return; } m_socket-connectToHost(host, port); // 注意connectToHost是异步的不能立刻判断成功失败 }connectToHost是异步的调用后立即返回真正的连接结果通过信号通知。所以客户端一定要监听两个信号connect(m_socket, QTcpSocket::connected, this, TcpClient::onConnected); connect(m_socket, QTcpSocket::errorOccurred, this, TcpClient::onError);errorOccurred信号非常重要比如目标端口没开、网络不通、防火墙拦截都会触发。开发者需要根据不同的错误码给用户提示void TcpClient::onError(QAbstractSocket::SocketError error) { switch (error) { case QAbstractSocket::ConnectionRefusedError: emit logMessage(连接被拒绝请确认服务端已启动); break; case QAbstractSocket::HostNotFoundError: emit logMessage(找不到主机请检查IP地址); break; case QAbstractSocket::NetworkError: emit logMessage(网络不可达请检查网络连接); break; default: emit logMessage(QString(连接出错: %1).arg(m_socket-errorString())); break; } }这段一定要有。不加这个用户在界面上点击连接后如果服务端没启动界面毫无反应用户会以为程序坏了。连接失败的反馈比连接成功的反馈更重要。3.2 消息协议的发送端实现保证完整性和一致性发送端要处理的事情比接收端少但有一个容易忽略的点发送的数据必须带有和接收端约定的分隔符。源码里在发送处统一封装void TcpClient::sendMessage(const QString message) { if (m_socket-state() ! QAbstractSocket::ConnectedState) { emit logMessage(未连接服务端无法发送消息); return; } QByteArray data message.toUtf8(); if (!data.endsWith(\n)) { data.append(\n); // 统一补充结束符 } qint64 written m_socket-write(data); if (written 0) { emit logMessage(QString(发送失败: %1).arg(m_socket-errorString())); return; } emit logMessage(QString(发送成功共%1字节).arg(written)); }这里的关键点每次都检查是否以\n结尾避免调用方忘了加分隔符导致接收端长久等待一条不完整消息。write()只是把数据写入系统发送缓冲区不代表消息已经到达对端。TCP是流式协议write成功只表示数据进入了内核缓冲区。如果要知道对端确实收到了需要应用层协议配合ACK确认。written返回值也需要留意。write返回值是写入缓冲区的字节数正常情况下应该等于data的长度如果小于data的长度说明缓冲区满或者有异常。这个极端情况在局域网联调时很少见但在高负载场景会触发代码里做了基本判断够用就行。3.3 UI线程与网络线程的关系为什么你的界面不会卡死这套源码的客户端还带一个简单的UI界面包含IP输入框、端口输入框、连接按钮、消息输入框、发送按钮、日志显示区。UI部分用QWidget搭建核心是理解和网络模块的关系。QTcpSocket和QTcpServer都运行在创建它们的线程里。如果直接在UI线程创建socket那么调用connectToHost不会阻塞界面因为网络I/O本身不阻塞——阻塞的是等待数据的过程而Qt的非阻塞模型通过事件循环解决了这个问题。数据到达时底层会在事件循环中触发readyRead信号槽函数在UI线程执行整个过程界面都不会卡。那为什么还要提线程问题因为如果你在子线程里创建了socket信号的接收方在主线程的UI对象里Qt会自动使用队列连接QueuedConnection信号槽的执行会跨线程切换线程安全由Qt保证。但如果你在子线程里直接调用UI对象的成员函数、直接操作UI控件那就会出问题。所以代码的原则是在哪个线程创建socket就在那个线程做网络操作界面更新一律通过信号槽。这个设计直接解决了数据收发量大时界面卡顿这个常见问题。数据来了槽函数只负责把内容追加到日志区解析和校验都放在信号触发前主流程不会被拖慢。4. 联调踩坑实录Qt网络编程最容易翻车的几个细节4.1 粘包半包的坑到底长什么样我见过不少初学者用readAll()接收数据然后显示出来看起来也没问题。但只要数据稍微大一点或者频率高一点问题就暴露了。场景一客户端连续执行两次sendMessage(hello)和sendMessage(world)接收端如果用readAll()可能一次收到的是helloworld或者hello\nworld也可能连续两次都是hello\n再world\n。结果完全取决于网络栈的调度时机。场景二发送一个很大的字符串比如几万字符接收端readAll()只拿到一半剩下的还要等下一次readyRead信号。如果你只处理了一半就当一条完整消息数据就丢了。这套源码的按行解析完美解决了这两个问题。你要记住的判断标准是读到多少数据处理多少和攒到一条完整消息再处理是两种完全不同的设计哲学后者才是能做产品的做法。4.2readyRead信号并不代表一整条消息到了而只是有数据到了这是Qt网络编程最容易误解的地方。新手容易把readyRead理解为消息到达然后在这个信号对应的槽里用readAll()读一次就以为读到的是一次完整的发送。实际上TCP是流协议没有消息边界。操作系统何时把数据交给Qt的缓冲区取决于TCP分片、Nagle算法、接收缓冲区水印等多个因素。readyRead只表示缓冲区有数据可以读不代表数据是完整的。一次发送可能触发多次readyRead多次发送也可能合并为一次readyRead。所以任何读取逻辑都必须放在缓冲区累积按边界分割的模式下代码里的m_buffer正是为此设计的m_buffer.append(socket-readAll()); // 全部读入临时缓存 // 从缓存中按\n分割出完整消息这套模式在几乎所有Qt网络项目中都适用是Qt网络开发的基本功。4.3 本地测试为什么代码没问题却连不上联调时最常遇到的现象就是客户端连接服务端报连接被拒绝或者超时。一步步排查的顺序应该是服务端是否真的在监听看日志确认服务端已启动监听端口: xxx打印出来了。端口号是否写对服务端监听的是9000客户端连的也是9000这个要核对。IP地址是否写对本机联调可以填127.0.0.1跨机器联调要填服务端机器的局域网IP。防火墙是否拦截Windows防火墙首次运行会弹提示如果点了取消外部机器就无法访问这个端口。可以先从控制面板关闭防火墙测试通了再重新开启并添加例外规则。这个顺序很重要不要上来就怀疑代码。90%的连接不上是环境问题不是代码问题。我自己调试时有一次卡了半小时最后发现是服务端和客户端跑在同一台机器上客户端填的是局域网IP而不是127.0.0.1导致路由走了网卡又绕回来被防火墙挡了。换成127.0.0.1立刻就好。4.4 编码问题Qt字符串和网络字节流的转换Qt的QString内部是UTF-16编码而网络传输的是字节流。代码里统一用toUtf8()转出、从QByteArray用QString::fromUtf8()转回这样无论如何不会乱码。如果对端不是Qt程序而是C#、Python、Java写的服务端一定要约定编码格式。全链路统一UTF-8是最稳妥的。如果在中文Windows上某个环节用了GBK就会出现中文乱码问题——这种问题排查起来极其恶心因为代码逻辑全对就是显示不对。我建议从第一天开始就固定使用toUtf8()/fromUtf8()不要混用。4.5 Nagle算法和延迟确认导致的消息发送延迟还有一个比较深层的问题当发送方连续、小批量地发送数据时TCP协议栈为了提升网络利用率默认开启Nagle算法会合并小的数据包一起发送。接收方为了减少网络包数量默认开启延迟确认会等一小段时间再返回ACK。这两个机制配合可能出现发送方发出去了服务端十几毫秒甚至几十毫秒后才收到的现象。这个项目里因为收发频率不高不需要特别处理。但如果你做的是实时性要求较高的场景比如游戏同步、实时控制可以考虑在socket上禁用Nagle算法m_socket-setSocketOption(QAbstractSocket::LowDelayOption, 1);这就是TCP_NODELAY选项禁用Nagle算法让每个小数据包都立即发送换实时性牺牲一点带宽。这个选项按需开启不要盲目使用。5. 跑通全套源码的步骤与验证方法5.1 环境准备版本和模块我用的是Qt 5.15.2LTS版本编译器MSVC2019 64位Qt Creator 作为IDE。Qt 6 也完全兼容网络模块的API基本没有变化。在.pro文件里需要确认包含network模块QT core gui network greaterThan(QT_MAJOR_VERSION, 4): QT widgets只需要这两行不需要额外链接任何socket库。如果你用CMake对应的是find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Network) target_link_libraries(your_target PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network)5.2 编译和运行流程整体目录结构TcpDemo/ ├── TcpServer/ │ ├── TcpServer.pro │ ├── main.cpp │ ├── mainwindow.h/cpp │ └── tcpserver.h/cpp ├── TcpClient/ │ ├── TcpClient.pro │ ├── main.cpp │ ├── mainwindow.h/cpp │ └── tcpclient.h/cpp先用Qt Creator打开TcpServer项目编译运行看到日志区输出服务端已启动监听端口: 9000。然后再打开TcpClient项目编译运行在界面上填写127.0.0.1和9000点击连接。连接成功后服务端日志区会显示新客户端接入。然后在客户端输入框输入任意文字点击发送。服务端日志区会显示收到的内容同时在服务端也可以对指定客户端发送消息源码中提供了发送按钮选择目标客户端ID然后发送客户端日志区会同步显示。5.3 多客户端并发验证这套源码支持多客户端所以你可以同时启动两个客户端实例如果你在同一台机器上跑直接复制一份编译出的exe再运行一次即可都连接同一个服务端。然后轮流在两个客户端发消息观察服务端的日志——每个客户端都会有独立的clientId日志会区分开。再做一个测试直接关掉其中一个客户端程序模拟异常掉线观察服务端。正常关闭时服务端会立即收到disconnected信号日志区显示该客户端断开。如果你用的是任务管理器强制结束进程服务端不会立刻感知而是要等TCP超时。这就是我前面说为什么在真实项目中要做心跳检测的原因——异常掉线不能靠TCP自带的断开感知。5.4 数据收发性能验证如果你想看更大数据量下的表现可以在客户端写一个循环发送1万条消息观察服务端的日志是否完整、是否按条数正确接收。这个测试能直接验证按行解析的可靠性。有一点需要提醒如果发送频率特别高emit logMessage可能成为性能瓶颈因为日志组件每插入一行都要刷新一次界面。源码里日志区设置了最大行数限制超过后丢弃旧行这样即使数据量大UI也不会无限增长导致内存膨胀。6. 后续还能怎么扩展从基础通信到可用系统的进化路径6.1 自定义消息类型从文本到指令目前的Demo发送的纯文本。你可以在此基础上定义一个消息结构体比如[消息类型]:[内容]发送时用统一格式login:username、msg:内容、heartbeat:ping。接收端解析时按类型分发处理。这是最轻量级的应用层协议多用在课程设计和工具类软件中。比如你做一个简单的聊天室消息类型可以定义成login登录、chat广播聊天、whisper私聊、logout下线。服务端维护一个在线用户列表收到chat类型就转发给所有在线客户端收到whisper就只转发给目标ID的客户端。这个扩展很简单但要提前设计好协议格式别写到一半再来改协议否则客户端和服务端就要同时改容易出不一致。6.2 心跳机制应对拔网线和断电我之前说了异常掉线检测的问题心跳是标准的解法。原理很简单客户端每隔一段时间比如5秒发送一个heartbeat:ping服务端收到后更新该客户端的最后活跃时间并回复heartbeat:pong。服务端在一个定时器里定期检查所有客户端的最后活跃时间如果超过某个阈值比如15秒没有收到心跳就认为该客户端已经掉线主动关闭连接并清理资源。代码里加的话服务端要加一个QTimerm_heartbeatTimer new QTimer(this); m_heartbeatTimer-setInterval(5000); connect(m_heartbeatTimer, QTimer::timeout, this, TcpServer::checkHeartbeat); m_heartbeatTimer-start();checkHeartbeat里遍历所有客户端检查最后活跃时间超时了就调用socket-disconnectFromHost()或者abort()。心跳间隔的选取有讲究太短会增加网络负担太长会导致掉线感知慢。一般取心跳间隔: 超时阈值 1:3的经验值比如心跳5秒一次15秒没收到就判定掉线。6.3 断线重连让客户端更健壮客户端掉线后自动重连也是工程必备。实现思路客户端在检测到连接断开后启动一个重连定时器比如3秒后重试连接重连失败则再次等待3秒直到连接成功或用户手动取消。注意重连间隔不是越短越好。如果服务端正在重启客户端每秒重连一次会让日志刷屏还可能触发服务端的半连接队列溢出。建议用指数退避第一次3秒第二次6秒第三次12秒最大到30秒封顶。这个在Redis等成熟中间件的客户端里都是标配我们也应该学习。6.4 数据加密从明文到TLS如果这个通信框架要用于生产环境数据内容需要加密。Qt提供了QSslSocket用法和QTcpSocket几乎一样只是多了一步配置证书QSslSocket *sslSocket new QSslSocket(this); sslSocket-setLocalCertificate(server.crt); sslSocket-setPrivateKey(server.key); sslSocket-startServerEncryption(); // 服务端本地开发调试用明文就够了但要部署到公网TLS是底线。Qt network模块直接支持成本不高。6.5 线程池模式应对高并发如果连接数非常多比如几百上千个客户端单线程事件循环仍然能工作但每个连接的收包处理如果耗时过长比如要写数据库、做复杂计算就会拖慢整个事件循环。更高级的架构是主线程只负责accept连接然后把socket扔给工作线程池去处理收发。Qt里可以用QtConcurrent或者自定义的QThreadPool实现。但这套方案不是免费的——跨线程处理socket很麻烦需要小心管理生命周期。在绝大多数场景下单线程事件循环高效槽函数处理已经足够了不要一开始就上线程池等确实遇到性能瓶颈再说。7. 几个必须养成的代码习惯最后分享几个这套代码里我刻意坚持的好习惯都是实战中踩出来的经验不是空话。第一日志是网络程序的第一调试工具。这套源码里所有关键节点都有logMessage输出监听成功、新客户端接入、数据收到、数据发送、客户端断开。联调时出了问题第一排查依据就是日志。不要嫌日志多真正上线的时候你会发现日志永远不够多。建议加上时间戳QString timestamp QDateTime::currentDateTime().toString(hh:mm:ss.zzz); emit logMessage(QString([%1] %2).arg(timestamp, msg));加上毫秒时间戳能精确判断数据到达的时序排查粘包和延迟问题时非常有用。第二缓冲区必须设置上限。如果客户端连上后一直不停发数据但服务端处理速度跟不上缓冲区就会无限增长最终内存耗尽。这片代码里虽然没显式处理但你是做长期项目的人要记住任何缓存都应该是有限的超过阈值就要丢弃或者断开异常连接。真实产品里对客户端发来过快、过大的数据都要有防御性策略。第三永远不要阻塞事件循环。在槽函数里做耗时操作比如大量数据处理、磁盘I/O、数据库操作时事件循环会被阻塞导致其他客户端的信号无法及时处理界面也会卡住。如果确实有耗时操作用QThread或者QtConcurrent::run把它扔到后台线程。第四服务端和客户端的协议解析逻辑要保持一致。很多项目出问题不是某一端写错了而是两端的协议理解有偏差。比如服务端按\n分割客户端发送时却只发了\r\n这时候按\n分割也能分出来但会残留\r。源码里用trimmed()把两端的空白字符都清理掉了这种防御性编程能少很多麻烦。这套基于Qt和C的TCP网络通信框架覆盖了从监听、连接管理、收发、断线到协议的完整链路。你拿到源码后先把它跑起来然后尝试改一改改端口、改分隔符、加一个心跳定时器、换一种消息格式。改的过程中你会更深刻地理解TCP通信的底层机制。如果在这个过程中遇到问题记住先看日志再抓包Wireshark是网络排查神器最后再怀疑代码。这是所有网络编程问题排查的通用顺序。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价