Skip to content
正常

Network Tool 成品导览

sourceapp/02-network-tools/network-tool/ related:app 栏网络工具类整机成品

Network Tool 是 app 栏「网络调试助手」这一类的整机成品。和 widget 栏讲单控件不同,app 栏讲究把整台机器装起来——一个窗口里同时塞进 TCP Server / TCP Client / UDP 三种模式,能听、能连、能发、能收,多客户端并发不乱。它的价值不在某个算法多巧,而在把 Qt Network 那几个核心类(QTcpServer / QTcpSocket / QUdpSocket)和 Qt Widgets 的整机装配(QMainWindow + 配置面板 + 收发区 + 状态栏)织成一台能用的机器,而且把网络调试里最容易翻车的几个雷——端口 0 假设、readyRead 分片、UDP 用 readAll、客户端断开悬空指针、协议运行中切换——都防住了。

本篇是「成品导览」

想直接用成品 → 看这里(架构 / 决策 / 踩坑 / 怎么读)。 想自己从零搓出来 → 转 手搓手册

1. 它做什么

一个能用的 TCP/UDP 网络调试助手:

  • 选协议:顶部 QComboBoxTCP Server / TCP Client / UDP,配置控件跟着协议自动显隐(Server 只要本机端口,Client 只要目标 IP+端口,UDP 两者都要——既 bind 本机端口收、又 writeDatagram 目标发)
  • TCP Server:填本机端口点 StartQTcpServer::listen 起监听;客户端连进来自动入列 QList<QTcpSocket*>,状态栏实时显示客户端数;可向所有客户端广播发送
  • TCP Client:填目标 IP+端口点 StartconnectToHost 异步连接,connected/disconnected 信号驱动状态,断开后发送前置拒绝
  • UDP:填本机端口点 StartQUdpSocket::bind 收数据报,hasPendingDatagrams + receiveDatagram 循环消费),另填目标 IP+端口用于发送;发送要明确目标,目标留空则回环自测
  • 收发:中间只读日志区带时间戳 + 方向 收 / 发 / i 信息 / ! 警告),底部 QLineEdit 输入回车即发,状态栏累计 RX/TX 字节
  • :同一按钮切 Stop,按当前协议 close + deleteLater 全部资源,断开信号先 disconnect 防回调已删对象

跑起来看一眼:

bash
cmake -B build -S app && cmake --build build
./build/02-network-tools/network-tool/demo/network-tool_demo

2. 架构总览

类关系

整机就一个核心类 NetworkToolWindow(QMainWindow),它按当前协议持有三种运行态资源之一:TCP Server 模式握 QTcpServer + QList<QTcpSocket*>;TCP Client 模式握单个 QTcpSocket;UDP 模式握 QUdpSocket。三套资源互斥,切换协议前必须先 Stop 旧实例。配置面板驱动协议选择与显隐,接收区/发送区/状态栏是各协议共用的 UI 层。

文件职责

文件职责
demo/network_tool_window.h主窗口接口:协议三态 + 三类 socket 资源 + 收发/状态栏装配;头注释讲清三条协议用法与五条关键设计(端口 0 / readyRead 分片 / UDP 收 / 错误反馈 / 断开拒绝 write)
demo/network_tool_window.cpp主窗口实现:协议装配、Start/Stop 两态、TCP 多客户端管理、UDP 数据报循环、异步收发累积、teardown 顺序清理
demo/main.cpp入口:QApplication + 主窗口 show
demo/CMakeLists.txt工程配置——find_package(Qt6 ... COMPONENTS Network) + 链接 Qt6::Network(关键:不链 Network 整个编译都过不了)

起一个 TCP Server 接客户端并收数据怎么流转

重点:端口 0 让系统分配,真正监听端口用 serverPort() 取回,绝不假设用户填的就是 listen 成功的;readyRead 异步,本次信号只保证「至少一字节可读」,可能只是片段——while bytesAvailable 循环读尽本次可读。这两条是 TCP Server 模式的两条命脉。

3. 关键设计决策

① 三种协议三态资源互斥,Start/Stop 用单按钮两态切换,状态以「资源是否存在」为准而非按钮文案。 TCP Server / TCP Client / UDP 三种模式各握自己的 socket 资源,同一时刻只能活一套。Start 按钮点一次起服务、再点一次停——但运行态判定不读按钮文字(按钮文字可能被别处改、状态脱钩),而是 tcp_server_ != nullptr || tcp_client_ != nullptr || udp_socket_ != nullptr 三个指针任一非空就算运行中。(network_tool_window.cpp:190-191)

② 端口 0 = 系统分配,真正端口用 serverPort()/localPort() 取回,绝不假设用户填的就是 listen 成功的。 端口填 0 是合法用法(让系统挑一个空闲端口),但用户填的 0 和「实际监听端口」是两回事。代码里只要成功 listen/bind,状态栏和日志一律用 tcp_server_->serverPort()network_tool_window.cpp:227)/ udp_socket_->localPort()network_tool_window.cpp:269)取真实端口反馈,避免「我填了 0 怎么状态栏也是 0」的错觉。

③ TCP 收用 while bytesAvailable 循环 readAll,UDP 收用 hasPendingDatagrams + receiveDatagram,各走各的,且都不假设一次到齐。readyRead 只保证「至少一字节可读」,一条消息可能分几次信号到达——TCP 收在 onTcpReadyReadwhile (socket->bytesAvailable() > 0) 累积 readAllnetwork_tool_window.cpp:340-347)。UDP 完全是另一套:数据报是离散的,绝不能用 readAll(会粘包/丢边界),必须 while hasPendingDatagrams()receiveDatagram() 逐个消费(network_tool_window.cpp:380-389)。

④ TCP Server 多客户端用 QList<QTcpSocket*> 管理,断开用 deleteLater + 从列表移除,防悬空指针。 每个客户端 QTcpSocket 接自己的 readyRead/disconnected 信号——收数据时用 sender() 区分是哪个客户端(network_tool_window.cpp:334);客户端断开从 tcp_clients_ 移除并 deleteLaternetwork_tool_window.cpp:369-370)。teardownAll 整体清理时,先 disconnect(disconnected signal)disconnectFromHost,避免断开信号回调到正在被删的对象。

⑤ 运行中锁住协议切换和配置输入,且 TCP Client 必须真正 ConnectedState 才允许发送。 运行中改端口/IP 不会即时生效(socket 已建),与其让用户产生「改了怎么没反应」的错觉,不如直接 setEnabled(false) 锁住(network_tool_window.cpp:176-179)。TCP Client 发送前用 tcp_client_->state() == QAbstractSocket::ConnectedState 前置校验(network_tool_window.cpp:433)——以 connected 信号态为准,断开后 write 会被 Qt 丢弃/报错,这里前置拒绝更干净。

4. 怎么读这份 code

按这个顺序读,最快建立心智:

  1. demo/network_tool_window.h 头注释 + 成员——先看「窗口握着什么」(三类 socket 资源、协议三态、收发/状态栏控件),五条关键设计写在头注释里
  2. setupCentralnetwork_tool_window.cpp:56)——QFormLayout 配置面板怎么搭,接收区/发送区/状态栏怎么分区
  3. refreshConfigVisibilitynetwork_tool_window.cpp:155)——协议决定本机端口/目标 IP+端口哪些可见,target_port 单独行连同 target_ip 一起显隐的小细节
  4. onStartStopTogglednetwork_tool_window.cpp:188)——单按钮两态切换、三协议分流装配(listen/bind/connectToHost)、端口校验与失败回滚
  5. onTcpNewConnectionnetwork_tool_window.cpp:313)——hasPendingConnections 循环接客户端、入列、接各自信号
  6. onTcpReadyReadnetwork_tool_window.cpp:333)——sender() 区分来源、while bytesAvailable 累积 readAll
  7. onUdpReadyReadnetwork_tool_window.cpp:378)——hasPendingDatagrams + receiveDatagram 循环(不是 readAll
  8. onTcpDisconnectednetwork_tool_window.cpp:358)——socket=nullptr 表示 Client 自身断开、非空表示 Server 某客户端断开移除
  9. teardownAllnetwork_tool_window.cpp:283)——释放顺序:server close → 客户端先 disconnect 信号再 disconnectFromHost → deleteLater
  10. onSendnetwork_tool_window.cpp:396)——三协议分流写、TCP Client 前置 ConnectedState 校验、UDP 留空回环

入口:demo/main.cppNetworkToolWindow 跑起来,对照读。

5. 踩坑

#现象原因后果解法
端口填 0 起服务,状态栏/日志也显示 0,以为没监听上0 让系统分配空闲端口,实际监听端口要用 serverPort()/localPort() 取回,而不是回显用户填的值用户误判服务没起来、对端连不上listen/bind 成功后用 tcp_server_->serverPort()network_tool_window.cpp:227)/ udp_socket_->localPort()network_tool_window.cpp:269)取真实端口反馈
TCP 收数据只显示前半截,后半截丢/串行readyRead 只保证「至少一字节可读」,大消息可能分多次信号到达;只 readAll 一次就以为收完数据粘包/截断、协议解析错乱while (socket->bytesAvailable() > 0) 循环 readAll 累积到本次可读耗尽(network_tool_window.cpp:340-347
UDP 收数据粘包/边界错乱,甚至收不到用了 TCP 的 readAll 收 UDP——UDP 是离散数据报,readAll 会把多个数据报的字节流粘起来或丢边界数据报无法对应来源、内容错位UDP 收必须 while hasPendingDatagrams() + receiveDatagram() 逐个消费(network_tool_window.cpp:380-389
TCP Client 还没 connected 就点 Send,数据丢了/报错connectToHost 是异步的,立即返回时连接还没建立;此时 write 被丢弃用户以为发了其实没发、或刷一堆 socket error发送前用 tcp_client_->state() == QAbstractSocket::ConnectedState 前置校验,未连拒绝(network_tool_window.cpp:433);以 connected 信号态为准
客户端断开后程序崩,或二次断开触发回调已删对象QTcpSocket::disconnected 信号回调里访问了已 deleteLater 的 socket,或 teardownAll 时 socket 还会发 disconnected 串到正在清理的对象崩溃、悬空指针、二次 deleteteardownAll 清理客户端前先 disconnect(disconnected signal)network_tool_window.cpp:290);Server 客户端断开用 lambda 捕获 socket 指针区分(network_tool_window.cpp:324-325
TCP Server 清理时客户端 socket 没跟着断,泄漏QTcpServer::close() 只停监听,不会主动断开已接的客户端 socket客户端连接残留、socket 泄漏teardownAll 里 server close 之后显式遍历 tcp_clients_ 逐个 disconnectFromHost + deleteLaternetwork_tool_window.cpp:290-294
同一客户端连进来两条 newConnection 信号,丢一条高并发可能一次积压多个 pending,只 nextPendingConnection 一次接不到全部漏接客户端、客户端列表不全while hasPendingConnections() 循环接完所有 pending(network_tool_window.cpp:315
运行中改端口/IP,状态/行为没变化socket 已建好,改配置输入框不会重建 socket用户困惑「改了怎么没生效」运行中 setReadOnly/setEnabled(false) 锁住协议切换和配置输入(network_tool_window.cpp:176-179),改要走 Stop→重新 Start
工程编译报 QHostAddress/QTcpServer 找不到CMakeLists.txt 没链 Qt6::Network整个编译失败find_package(Qt6 ... COMPONENTS Network) + target_link_libraries(... Qt6::Network)demo/CMakeLists.txt:3,15
listen/bind 失败静默,用户不知道端口被占没读 errorString 反馈端口占用/权限不够时用户以为服务正常listen/bind 返回 false 时 QMessageBox::warningerrorString(),并 delete + 置空回滚(network_tool_window.cpp:217-223 / 262-267
UDP 模式填了目标 IP/端口,发送时还是只发给自己(loopback),永远发不到外部目标refreshConfigVisibility 把 UDP 的 need_target 置成 false,目标 IP/端口控件被隐藏,onSend 取不到目标只能走 loopback 回环UDP 永远走 loopback,跨机/跨进程对外发不出去,用户以为「网络不通」need_target 对 UDP 也置 true——UDP 同时显示本机端口 + 目标 IP/端口(network_tool_window.cpp:161),让 onSend 能取到用户填的目标 writeDatagram 出去
TCP Server 接受的客户端 socket,退出时内存泄漏(程序结束 socket 没被回收)nextPendingConnection() 返回的 socket 默认无父对象;靠 disconnected → deleteLater 回收,但程序退出走的是对象树析构、此时没有事件循环跑 deleteLater,于是泄漏socket 泄漏、连接残留;进程虽退也丢的 socket 在长生命周期/嵌入式场景累积内存onTcpNewConnection 里给 socket setParent(this),让 Qt 对象树兜底回收(network_tool_window.cpp:317),不依赖事件循环
TCP Server 广播时 TX 字节统计错:N 个客户端只 +N×1(或 +1),不是 +N×payload;且个别客户端写失败 break 掉整轮,抹掉已成功写入的字节累加逻辑只加一份 payload(漏算 N 倍),或循环里遇到 write < 0break——把前面已成功 write 的字节也丢了流量统计严重偏低、与实际发送不符;部分成功也被当全失败循环内累加每个客户端 write 返回的实发字节network_tool_window.cpp:415-421);个别失败只标记 any_failed 不 break——计入已成功的、提示 partial,全部失败才报 Send failednetwork_tool_window.cpp:423-429
TCP Client 连上后远端断开,但 Send 按钮还亮着,点了才报「Not connected」onTcpDisconnected 没区分自身断开与 Server 客户端断开,自身断开时没禁用 send_button_——UI 与连接态脱节UI 误导(按钮看着能点)、用户点了才知道连不上、体验割裂onTcpDisconnected(socket=nullptr)(自身断开)分支里 send_button_->setEnabled(false)network_tool_window.cpp:361);onTcpConnected 重连成功再恢复 setEnabled(true)network_tool_window.cpp:352),UI 始终对齐连接态
teardownAll 清理自身 tcp_client_ 时偶尔崩,但清 Server 客户端没事tcp_clients_disconnect(disconnected) 屏蔽信号,清 tcp_client_ 没有——不对称,deleteLater 前发的 disconnected 串进来触发回调,脆性时序下踩雷间歇性崩溃、退出时序敏感难复现tcp_client_ 补上对称的 disconnect(disconnected signal)network_tool_window.cpp:297),与 Server 客户端分支一致屏蔽信号

6. 官方文档


这套「三协议三态资源 + 单按钮两态 + 异步收发累积 + teardown 顺序清理」是网络调试类整机应用的通用骨架——任何「起服务/连远端/收发数据」的工具(串口调试、Modbus 主从、MQTT 客户端、自定义协议探针)都能换皮复用。想自己搓?手搓手册带你从一个空 QMainWindow 一行行搓到这个成品。

AwesomeQt v0.2.0-58-gde7eeb4-dirty · de7eeb4 · 2026-07-03