基于Qt与C++的TCP网络通信框架:连接、粘包与断线重连实战
2026/8/31 15:18:56 网站建设 项目流程

简介:本资源是一套基于Qt框架与C++语言实现的完整TCP网络通信示例工程,面向计算机科学、信息安全、物联网、人工智能等专业的在校学生及初学者,用于掌握Socket编程核心原理与Qt网络模块实际应用。项目包含功能完备的服务端与客户端双工程,已通过本地多轮通信测试,稳定可靠,可直接编译运行,适用于毕业设计、课程设计、期末大作业及项目原型演示等实践场景。压缩包共12个文件,涵盖4个核心CPP源码(含主窗口逻辑与网络收发处理)、2个UI界面文件(Qt Designer可视化布局)、2个头文件(类声明与信号槽定义)、2个PRO工程配置文件(分别管理客户端与服务端构建)以及2份README说明文档,结构清晰、模块职责分明,总大小仅7KB,轻量易读。目前已有246人学习下载,读者可快速理解TCP连接建立、数据收发、线程安全处理及Qt信号驱动通信流程,并基于此拓展多客户端支持、消息协议封装或GUI增强功能。 平时做上位机、物联网网关或者一些桌面工具,TCP网络通信几乎是个绕不开的坎。前几天我把一套基于Qt和C++的TCP网络通信源码整理成了压缩包,客户端和服务端都齐了,打算当作基础组件复用,顺便也给后来的人留一份可以“抄作业”的参考。这套代码不算复杂,但覆盖了TCP通信里最常用到的几个点:连接建立、双向收发、断线重连、粘包处理,以及网络层和UI层的结合方式。适合刚接触Qt网络编程的人用来理解整套流程,也适合老手在需要快速搭一个本地联调环境时直接拿过去改。

源码里不只有界面按钮和收发逻辑,还把服务端多客户端管理、心跳包、数据分包处理这些实战中离不开的东西都做了进去。网上很多Demo只能做到“连上之后互发一条消息”,真正拿到项目里用的时候完全不够看。所以我在整理这套源码的时候,刻意把“能跑”和“能用在项目里”之间的距离拉近了一点。下文会从设计思路、核心原理、实操步骤、问题排查几个维度展开,内容偏实践,代码可以直接参考,但也有理论解释,方便你把底层逻辑捋清楚。

1. 项目整体设计与思路拆解

1.1 这套源码到底解决什么问题

先说背景。我之前有段时间需要给一套设备管理工具做远程指令收发,PC端要同时连接好几台设备,每台设备可能随时上线、掉线,还要定时回传状态。如果用现成的网络调试助手去联调,只能一对一,根本管不过来。于是我就基于Qt的Network模块写了一套基础通信组件,也就是这套源码的雏形。

这套源码包含两个独立程序:TcpServer和TcpClient。服务端可以同时监听多个客户端连接,并支持给单个客户端或全部客户端发送消息;客户端可以指定IP和端口去连接服务端,连接成功后能收发消息,还能在连接断开后自动重连。两个程序都有简单的日志区域,收发内容和连接状态一目了然。它解决的问题很明确:用最简洁的代码把TCP通信的“骨架”搭出来,让你不用从零开始处理socket、线程和界面刷新这些问题。

对于新手来说,这套代码是一个很好的学习模板,因为核心类只有几个,理解起来不费劲。对于老手来说,它是一个可以快速改造成业务组件的基础框架,服务端换成实际业务逻辑、客户端塞进现有工程,都能少走很多弯路。

1.2 为什么选择Qt和C++,而不是原生socket或脚本语言

在设计这套源码之前,我其实犹豫过要不要直接用原生的socket API,比如Windows上的Winsock或者Linux的POSIX socket。原因很直接:原生socket的API在不同平台上差异很大,Windows要WSAStartup初始化,Linux又不需要;收发数据时要自己管理缓冲区、处理EINTR之类的信号干扰;如果要加个界面,还得考虑怎么把网络事件塞进消息循环。这些工作不是不能做,但会分散你对“业务逻辑”的注意力。

Qt的Network模块把这一切封装好了。QTcpSocket和QTcpServer两个类屏蔽了平台差异,事件循环和信号槽机制天然适合处理异步网络通信。当网络事件发生时,比如连接建立、数据到达、连接断开,Qt会通过信号通知你,你不用自己去轮询或开多线程阻塞等待。这一点非常关键,因为TCP通信本身就是异步的,你永远不知道对方什么时候会发数据过来,也不可能专门开着一条线程去等数据。

另外,C++的性能和部署优势也是我选择它的原因。对于桌面工具来说,C++编译出来的程序不需要额外装Python环境,直接复制到目标机器就能跑。Qt的依赖库可以用windeployqt等工具打包,整体体量控制得当。而且项目如果要扩展到底层硬件通信、文件解析、数据库操作这些模块,C++的生态完全接得上。

这套源码里其实也用到了Qt的一个隐藏优势:跨平台。我平时在Windows上写代码,但同一个工程可以在Linux和macOS上直接编译,UI逻辑几乎不用改。对于经常需要给不同系统做工具的人来说,省下的事情远比你想象得多。

1.3 源码目录结构与模块划分

解压后的目录结构大致如下:

TcpDemo/ ├── TcpServer/ │ ├── TcpServer.pro │ ├── main.cpp │ ├── MainWindow.h │ ├── MainWindow.cpp │ ├── TcpServerManager.h │ └── TcpServerManager.cpp ├── TcpClient/ │ ├── TcpClient.pro │ ├── main.cpp │ ├── MainWindow.h │ ├── MainWindow.cpp │ ├── TcpClientManager.h │ └── TcpClientManager.cpp └── README.md

我把客户端和服务端拆成两个独立工程,而不是塞在同一个工程里用参数区分。原因很简单:这两个程序的逻辑、界面、运行生命周期都不一样,分开编译调试更清晰。你在Qt Creator里可以直接分别打开TcpServer.pro和TcpClient.pro,互不干扰。

每个工程里我都做了“界面层”和“网络层”的分离。MainWindow只负责创建控件、显示日志、接收用户输入;真正干活的网络通信逻辑放在了TcpServerManager或TcpClientManager里面。这样做的好处是,以后如果你不需要界面,只想要一个纯后台的通信服务,可以直接把Manager类提出来,丢掉MainWindow用,完全不影响功能。如果你的业务逻辑很复杂,也只需要在Manager类里面追加处理,不用去动UI。

2. 核心原理拆解:TCP通信在Qt里的落地方式

2.1 TCP三次握手和四次挥手在代码里如何体现

在讲代码之前,有必要先把TCP的生命周期捋一遍,因为你会在Qt的各个信号里看到它的影子。TCP是面向连接的协议,通信双方在正式传输数据之前要先“握手”,传输结束后要“挥手”。这个过程不是理论上的专属名词,而是代码里每个回调函数的来源。

TCP三次握手,简单说就是客户端发SYN包,服务端回SYN+ACK包,客户端再回ACK包,连接建立。在Qt里,你调用QTcpSocket的connectToHost之后,底层就会自动完成这一系列包的交互。三次握手成功后,QTcpSocket会发出connected信号;相应的,服务端那边,QTcpServer会发出newConnection信号,你在这个信号处理函数里取出一个新的QTcpSocket对象,然后这个socket就代表着一条已经建立好的TCP连接。

我刚开始学的时候有个误区,以为newConnection信号触发时,连接就已经“完全OK”了。实际上,从TCP协议角度看,三次握手确实已经完成,但应用层可能还有自己的鉴权,比如客户端连接后要先发一条特定的认证消息,服务端验证通过后才认为这个设备有效。所以我在源码注释里特别强调:你需要把“TCP连接建立”和“业务连接就绪”分开看待,不要在newConnection里立刻处理业务数据,先做必要的初始化。

TCP四次挥手也是类似。主动断开的一方调用disconnectFromHost或者直接close,接着会有FIN、ACK、FIN、ACK的交互。在Qt里表现出来的是两个信号:disconnected和stateChanged。实际开发中,你不一定需要关心底层到底是哪一方先发的FIN,只需要知道当对端断开连接时,QTcpSocket的disconnected会被触发。服务端那边通常在disconnected信号里把这个socket从客户端列表移除,同时更新UI日志。

2.2 QTcpServer监听和QTcpSocket连接管理的细节

服务端要监听到客户端的连接请求,关键代码就两行:创建QTcpServer对象,然后调用listen指定端口。这里有几个细节值得注意。第一个是监听地址,通常用QHostAddress::Any,意思是监听本机所有网卡地址,不管是局域网IP还是回环地址,都能连进来。如果你只填了QHostAddress::LocalHost,那就只能本机自己连自己,其他机器连不进来,这个坑我踩过好几次。

第二个细节是newConnection信号的触发时机。当有客户端connectToHost过来时,监听socket会重新变得可读,Qt底层检测到之后会发newConnection信号。在这个信号的槽函数里,你需要调用nextPendingConnection()来取出那个已经完成三次握手的QTcpSocket对象。注意,每调用一次nextPendingConnection()只会取回一个socket,如果有多个客户端同时连接,newConnection信号会多次触发,你得循环处理。我写代码习惯用while循环把所有pending连接都取出来,避免丢失连接。

管理这些连接我建议用QList<QTcpSocket*>或者QHash<QTcpSocket*, ClientInfo>来保存。因为服务端常常需要知道每个客户端的状态,比如IP、端口、上线时间、最后活跃时间,你不可能每次都从socket对象里临时去查。我在这套源码里用的是QList,加上一个简单的结构体保存客户端信息,当客户端断开时,在disconnected信号里找到对应的socket并删除。特别注意要调用deleteLater而不是直接delete,因为socket可能还在事件循环里挂着一堆待处理的信号,直接delete会造成野指针崩溃。

2.3 数据收发、粘包和拆包处理

TCP本身是字节流协议,不存在“消息边界”。你调用sendData发送一次数据,对端在readyRead信号里读到的不一定就是完整的一条消息;反过来,你连续发送两条消息,对端也可能一次性把两条都读出来,这就是常说的粘包和半包问题。

这个问题在初学阶段几乎必踩。我第一次写TCP收发时,直接在readyRead里调用socket->readAll(),然后把读到的内容当作一条完整消息去处理。结果在局域网里偶尔正常,数据一多就出乱子。后来我才意识到,必须自己定义应用层的消息格式。

这套源码里我采用了一种最简单的封包方式:每个消息包由“4字节的消息长度头”和“消息体”组成。比如发送字符串“hello”,我会先拼一个int型的包体长度5,再拼接“hello”这5个字节,然后一次性写入socket。接收方呢,在readyRead里先把数据缓存起来,分三步处理:先检查缓冲区里够不够4字节,不够就继续等;够了就读取长度字段,再检查缓冲区里够不够一整包的数据,不够继续等;够了就按长度取出整包,剩下没读完的数据留在缓冲区,供下一次readyRead继续处理。

有朋友可能会问,直接用QDataStream是不是更简单?QDataStream确实可以自动写入字节序和长度信息,但它有自己的格式,如果另一端不是Qt程序,比如一个用C语言写的单片机,解的协议就非常痛苦。所以我更推荐自己控制协议格式。用int写长度前要注意字节序问题,网络传输通常用大端序,Qt内部如果是小端机,要做一次转换。我在这套源码里直接使用了Qt提供的qToBigEndian来处理,避免在两台不同架构的设备上联调时出现长度解析错误。

2.4 心跳包和异常断开处理

TCP协议本身没有“定时通知对方我还活着”的机制。拔网线、断电、程序崩溃,这些情况下TCP连接并不会立刻断开,有时候要等很久才能发现。这在项目里是一个非常严重的问题:如果服务端一直以为客户端还在线,就会往一条死链路上发数据,然后反复超时重传,既浪费资源又会阻塞后续处理。

解决思路是应用层心跳机制。我在这套源码里加了心跳逻辑:客户端启动一个QTimer,每隔一定时间(比如5秒)发送一个Ping包;服务端收到Ping包后回一个Pong包,同时更新该客户端的最后活跃时间。如果服务端连续一段时间没有收到某个客户端的任何数据,就判定它已经失联,主动调用disconnectFromHost;客户端也一样,如果长时间没收到服务端的任何数据,就主动断开连接并进入重连流程。

心跳包在协议上要和业务数据区分开,我封包格式里除了长度头,还加了一个消息类型字段,比如0x01表示普通消息,0x02表示心跳请求,0x03表示心跳响应。这样接收方在解析数据时先看类型,如果是心跳包就直接忽略业务处理,只刷新活跃时间,避免把心跳当成业务数据弹到界面上干扰日志。

3. 实操过程:从零跑通客户端和服务端

3.1 环境准备和工程配置

这套源码我是在Qt 5.15.2上开发验证的,编译器用的MSVC2019 64位。其实Qt 5.12到Qt 6.x都可以编译,只要稍微注意几个接口差异。如果你之前没有配置过Qt环境,建议先装一个Qt 5.15.2或者Qt 6.5 LTS版本,选择组件时勾上Qt Widgets和Qt Network。编译器方面,Windows上MinGW和MSVC都行,但我个人更推荐MSVC,因为后续如果要接一些第三方动态库,MSVC兼容性会好一些。

工程使用qmake管理,TcpServer.pro内容大致如下:

QT += core gui network widgets TARGET = TcpServer TEMPLATE = app SOURCES += \ main.cpp \ MainWindow.cpp \ TcpServerManager.cpp HEADERS += \ MainWindow.h \ TcpServerManager.h

TcpClient.pro基本一样,只是目标名称改成TcpClient。注意在.pro里加network模块,这一步漏了的话编译会报错,因为QTcpServer和QTcpSocket类根本找不到。另外,如果你用Qt6,core、gui、widgets这几个模块不需要显式写network吗?也是需要的。版本不同,pro文件里可能还要加一句greaterThan(QT_MAJOR_VERSION, 4): QT += widgets,这是老项目兼容Qt5以前版本的写法,现在保留也无妨。

3.2 服务端实现的具体步骤

服务端的核心类我命名为TcpServerManager,先看头文件:

class TcpServerManager : public QObject { Q_OBJECT public: explicit TcpServerManager(QObject *parent = nullptr); void startServer(quint16 port); void stopServer(); void sendDataToClient(const QByteArray &data, QTcpSocket *target); void broadcastData(const QByteArray &data); signals: void logMessage(const QString &msg); void clientCountChanged(int count); private slots: void onNewConnection(); void onReadyRead(); void onClientDisconnected(); private: QTcpServer *m_server; QList<QTcpSocket*> m_clients; };

startServer的实现:

void TcpServerManager::startServer(quint16 port) { m_server = new QTcpServer(this); connect(m_server, &QTcpServer::newConnection, this, &TcpServerManager::onNewConnection); if (!m_server->listen(QHostAddress::Any, port)) { emit logMessage(QString("监听失败: %1").arg(m_server->errorString())); return; } emit logMessage(QString("服务端启动成功,端口: %1").arg(port)); }

onNewConnection里我处理所有新连接:

void TcpServerManager::onNewConnection() { while (m_server->hasPendingConnections()) { QTcpSocket *socket = m_server->nextPendingConnection(); connect(socket, &QTcpSocket::readyRead, this, &TcpServerManager::onReadyRead); connect(socket, &QTcpSocket::disconnected, this, &TcpServerManager::onClientDisconnected); m_clients.append(socket); emit logMessage(QString("新客户端接入: %1:%2") .arg(socket->peerAddress().toString()) .arg(socket->peerPort())); emit clientCountChanged(m_clients.count()); } }

readyRead里除了读取数据,还要做封包解析。我这里没有直接用readAll,而是用一个缓冲区积累数据。核心的拆包逻辑就是前面提到的那三步,代码结构大概是这样:

void TcpServerManager::onReadyRead() { QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender()); if (!socket) return; m_buffer.append(socket->readAll()); while (m_buffer.size() >= 4) { int blockSize = qFromBigEndian<int>( reinterpret_cast<const uchar*>(m_buffer.constData())); if (m_buffer.size() < blockSize + 4) { // 数据还不够,等下一轮 break; } QByteArray payload = m_buffer.mid(4, blockSize); m_buffer.remove(0, blockSize + 4); emit logMessage(QString("收到数据: %1").arg(QString::fromUtf8(payload))); broadcastData(payload); } }

这里有一个经验:解析完一条消息后,缓冲区里可能还残留半条消息,所以要用while循环挨个拆包,直到缓冲区里不足一个完整包头或者不足一整包才退出。m_buffer是类的成员变量,这样同一个socket会在多次readyRead之间共享数据缓冲,不会丢字节。

3.3 客户端实现的具体步骤

客户端这边的TcpClientManager核心逻辑更偏向于“连接动作”的管理。创建socket之后的代码如下:

void TcpClientManager::connectToServer(const QString &ip, quint16 port) { if (m_socket == nullptr) { m_socket = new QTcpSocket(this); connect(m_socket, &QTcpSocket::connected, this, &TcpClientManager::onConnected); connect(m_socket, &QTcpSocket::readyRead, this, &TcpClientManager::onReadyRead); connect(m_socket, &QTcpSocket::disconnected, this, &TcpClientManager::onDisconnected); connect(m_socket, &QTcpSocket::errorOccurred, this, &TcpClientManager::onError); } m_socket->abort(); m_socket->connectToHost(ip, port); }

connectToHost会立即返回,连接结果通过信号通知,所以不要在调用之后立刻校验状态。有人会用waitForConnected(3000)这种阻塞函数来等待连接结果,这样写起来简单,但会卡住UI线程,推荐只在纯命令行程序或线程里用。我在客户端代码里用的是异步信号,界面不会卡。

客户端断开后重连逻辑我是在disconnected信号里启动一个QTimer单发定时器,比如3秒后重新connectToHost一次,同时限制最大重试次数,避免在服务端没启动的情况下无限刷日志。重连间隔和次数做成可配置的,方便不同场景调整。

3.4 本地联调、抓包验证和常见坑位

写完之后,我习惯先在同一个电脑上跑两个实例:先启动TcpServer,监听8899端口,再启动TcpClient,连接127.0.0.1:8899。正常情况下,服务端日志会打印“新客户端接入”,客户端日志会打印“连接成功”。然后从客户端发一条“hello server”,服务端能收到并广播回去,客户端再从服务端收到这条数据,整个回路就跑通了。

跑通之后,我会用WireShark看一次抓包,过滤条件写成tcp.port == 8899,然后重新连接。你能非常清晰地看到三条握手包:第一条是客户端发出的SYN,第二条是服务端返回的SYN+ACK,第三条是客户端回的ACK。然后断开连接时能看到FIN包和ACK包的交换过程。这个检查不只是为了新奇,而是为了确认底层通信确实走了TCP协议栈,数据没被其他工具拦截或改写。

防火墙是最常见的“连不上”原因。Windows上如果服务端运行时会弹出防火墙授权窗口,一定要点允许;如果在别的机器上连接,服务端机器的防火墙也要放行对应的TCP入站端口。我遇到过一次很隐蔽的情况:防火墙规则里确实放行了,但只放行了“专用网络”,测试机器连的是“公用网络”,结果还是被拦。所以排查时别只盯着程序,先把网络配置看全。

联调过程中还有一个非常容易犯的错:服务端代码里用了QHostAddress::LocalHost监听,结果在另一台机器上怎么都连不上。我建议默认就用QHostAddress::Any,然后通过日志把实际监听地址打出来,省得怀疑人生。

4. 常见问题与排查技巧实录

4.1 端口被占用,服务端启动失败

报错通常是“The bound address is already in use”或者“Address already in use”。原因很简单:上一个服务端进程还在运行,或者程序退出时socket没有正确关闭。排查步骤:先用netstat查一下端口占用情况。Windows命令是netstat -ano | findstr 8899,会列出占用进程的PID,再去任务管理器确认是不是你上次启动的程序没退出。

解决办法有两个。一是改端口,换个不冲突的;二是socket设置SO_REUSEADDR。Qt里的写法是:

socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);

等等,这不对,SO_REUSEADDR在Qt里对应的是QAbstractSocket::ReuseAddressHint,可以这样设置:

m_server->setSocketOption(QAbstractSocket::ReuseAddressHint, 1);

不过需要注意,SO_REUSEADDR并不能让两个程序同时监听同一个端口,TCP层面这是一个基本限制。如果上一个进程还在TIME_WAIT状态,这个选项能帮你快速重启复用端口,否则你还是得等系统把端口释放掉。开发调试时我经常在Qt Creator里按停止按钮,发现端口没释放,等一下或者换端口就对了。

4.2 客户端能ping通对方,但TCP连接不稳定

这个问题分两类。一类是连接完全失败,另一类是连接建立后过一会儿自动断开。如果是服务端防火墙没放行,连接会超时;如果服务端监听的地址不对,比如监听的是127.0.0.1,从局域网其他机器连就显得“通了但拒绝”。检查服务端日志,打印出实际监听的地址,很多时候一下就能定位。

另一类是连接后自动断开,往往和服务端心跳处理有关。如果你在代码里加了“超时未收到数据就断开连接”的逻辑,而客户端恰好没有做任何信息交互,那么服务端会把客户端当成死连接踢掉。所以心跳包并不能只在客户端发,服务端也应该定期检查活跃时间,把没有业务数据但心跳正常的连接保留下来。我在这套源码里心跳周期是5秒,超时时间设成15秒,这个比例你可以根据自己的场景调节。

4.3 粘包拆包问题导致数据乱掉

如果你在界面上看到收到的数据有时候一条变两条,或者两条数据拼在一起,多半就是没做拆包。我见过不少人图省事,直接用readAll,或者用QByteArray::indexOf找换行符来切分数据。这在只有一条消息的场景下没问题,但只要并发量上来,或者消息体里本身包含换行符,这种方案就崩了。

用过固定头部+长度字段的方案之后,基本能彻底解决这个问题。但还要注意一种情况:对端发送消息时,如果一条消息分了好几次write,虽然底层最终是一个字节流,但你的接收缓冲区里可能会出现“第一条的一半+第二条的全部+第三条的三分之一”这种排列。所以拆包逻辑必须放在循环里,把缓冲区内所有完整包全部拆完再退出,不能拆到一半就等下次readyRead。源码里的while循环就是这个意思。

4.4 界面卡死或刷新不及时

很多初学者在网络代码里直接调用QThread::sleep或者在readyRead里做文件解析、数据库写入,然后界面就卡成PPT。根因是网络事件和UI事件跑在同一个事件循环线程里,你把UI线程堵住了,界面自然无法响应。

解决思路是把耗时操作挪到子线程里。但这里要说一个QTcpSocket的坑:它本身不是线程安全的,一个socket必须在创建它的线程里使用,不能把一个socket对象从主线程搬到子线程去收发数据。我的做法是:主线程只负责接收readyRead信号,把原始数据通过信号槽机制发给一个QThread worker处理;worker处理完之后再用信号把结果发回主线程刷新UI。信号槽跨线程默认使用队列连接,会自动排队,不会阻塞发送方。

如果你不想引入线程,也可以退一步,把耗时操作拆小,比如在readyRead里只做协议解析,解析完把任务交给线程池,避免大块连续阻塞。源码里我保留了worker线程的示例,方便你扩展。

4.5 Qt版本差异带来的编译问题

我在整理源码的时候特意在Qt 5.15和Qt 6.5各编译了一次,发现几个常见的坑。第一个是errorOccurred信号,Qt 5.15里QTcpSocket有error信号也有errorOccurred信号,Qt 6里去掉了error信号的旧用法,统一使用errorOccurred。如果你的编译器版本比较老,connect里写上error会报错或者行为不同。

第二个是QTextCodec::setCodecForLocale在Qt6里被移除了,如果工程里有中文乱码问题,建议直接使用QString::fromUtf8处理UTF-8编码的数据。第三个是Qt6里不再把Network模块隐式加进来,必须在.pro或CMakeLists.txt里显式find_package(Qt6 REQUIRED COMPONENTS Network)。这些差异不大,但遇到一个就能卡你半小时,所以我在README里专门加了一个版本兼容说明。

4.6 常见故障速查表

症状可能原因处理办法
监听失败,提示端口占用端口被其他进程占用或TIME_WAIT未释放换端口,或设置ReuseAddressHint,等待端口释放
客户端连接超时防火墙拦截、网络不通、IP错误检查防火墙入站规则,确认服务端监听地址,ping测试
连接成功后立刻被动断开服务端心跳超时主动断开、客户端未发数据调整心跳周期,确认服务端活跃检查逻辑
收到的数据出现粘连或半包未做应用层拆包,readAll直接处理加4字节长度头,循环拆包,用缓冲区跨readyRead积累数据
界面无响应在UI线程做了耗时操作或用waitForConnected阻塞把耗时逻辑移到子线程,用异步信号接收连接结果
Qt6编译失败缺少network模块或error信号变化显式导入Network模块,改用errorOccurred
能连本机不能连局域网监听了LocalHost,或者防火墙网络配置文件限制监听QHostAddress::Any,放行所有网络类型

这套源码整理的时候,我特意把常见问题写进了注释里,方便你以后维护。TCP网络通信乍一看是个硬骨头,但只要把三条核心链路理顺:连接生命周期、数据收发边界、异常断开处理,大部分需求都能稳稳覆盖。我在实际项目里拿这套代码改过好几个场景,比如给设备做远程指令通道、在电脑之间做文件传输、做简易聊天工具,每次改动其实都是在这几个核心节点上打补丁。最后再提醒一句,写完代码一定要做一次拔网线或断WiFi的测试,很多隐藏问题不是第一次运行就暴露的,而是出现在网络不稳定的时侯。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询