MMyBlog
← 返回博客
我为什么要搭建 TCP/UDP 网络调试助手(NDATools)
NDATools
8 分钟·3,036 ·109 次阅读

我为什么要搭建 TCP/UDP 网络调试助手(NDATools)

为什么要做这个项目

大二学完计算机网络课之后,TCP 三次握手、四次挥手、滑动窗口、拥塞控制这些概念背得滚瓜烂熟,考试也能拿高分。但说实话,我真的"理解"它们吗?

大三上学期,我选了一门 C++ 项目实践的课,要求在 Qt 框架下做一个桌面应用。当时我正好在折腾 Wireshark 抓包,突然想到:要不自己写一个网络调试工具?这样既能练 C++,又能真正理解 Socket 编程,一举两得。

于是 NDATools 就诞生了。

要做成什么样

市面上已经有 Wireshark、NetAssist 这种成熟的网络工具了,我不可能也没必要做一个比它们更强大的。我的定位很明确:一个轻量级的、好用的、自己能完全理解每一行代码的网络调试工具箱

功能设计上我分了六个模块,用 QTabWidget 作为主容器:

NDATools 六大功能模块

TCP 服务端的实现细节

服务端是整个工具最核心也最复杂的一个模块。核心思路是用 QTcpServer 监听端口,每当有新客户端连接时就创建一个 QTcpSocket,把 socket 加入一个 QList 统一管理。

日志系统花了不少心思。每个客户端连接、断开、收发数据都记录一条带时间戳和颜色编码的日志。为了避免日志无限增长,超过 1000 行就自动裁剪。窗口关闭时通过重写 closeEvent 把日志写入磁盘,下次打开能接着看。

数据收发方面,TCP 是流式协议,没有消息边界。如果连续发两个小包,接收端可能一次收到一条长包(粘包),也可能收到两条不完整的包(拆包)。我用长度前缀法解决这个问题:每个消息前面 4 字节存消息长度,接收端先读长度再按长度读数据。

TCP 服务端

TCP 服务端主界面 — 支持多客户端连接管理、彩色日志和自动落盘

TCP 客户端的安全防护

客户端比服务端简单,但安全防护反而更复杂——因为服务端的数据是"可信"的(自己发的),客户端的数据来自网络,什么都可能出现。

我做了三层防护:第一层,收到数据先检查长度,超过 1MB 直接丢弃并断开连接,防止内存被撑爆;第二层,用 QTimer 实现超时重连,每 5 秒自动尝试一次;第三层,用 std::atomic 做收发计数,所有统计操作都是原子的,不用担心多线程竞争。

UDP 的简洁之美

UDP 模块的实现比 TCP 简单太多了。不需要 listen,不需要 accept,一个 QUdpSocket 绑定端口就能收发。发送端只管 send,接收端只管 readDatagram,不需要处理粘包拆包。

但这种"简单"是有代价的:UDP 不保证送达、不保证顺序、不保证不重复。所以我在日志里加了丢包率统计,让用户能直观地看到 UDP 的不可靠性。

UDP 服务端

UDP 服务端界面 — 绑定端口后接收数据并回显

数据转换:细节决定体验

数据转换模块看起来最简单(十六进制和字符串互转),实际上用户体验细节最多。

第一个版本是每次按键都触发转换,输入快一点就卡。改进方案是加了 300 毫秒 QTimer 防抖——用户停止输入 300ms 后才开始转换。这样既保证了实时性,又不会频繁触发重计算。

第二个问题是输入校验。用户可能在十六进制输入框里拍空格、拍字母 G(超出了 0-F 范围)。我用 QRegularExpression 做预编译正则校验,非法字符直接拦截,不给转换函数处理的机会。

数据转换

数据转换模块 — 支持十六进制 / Base64 / ASCII 互转,QTimer 防抖

数据校验:从串行到异步

CRC16/32 查表法 O(n) 很快,但 MD5 和 SHA256 在大量数据下计算量不小。我在 UI 线程跑了一次 50000 字符的数据,界面直接卡了 2 秒。

改进方案有两步。第一步是优化算法本身:CRC16/32 用查表法(预计算 256 个值的表存起来,计算时直接查),SHA 系列用 Qt 内置的 QCryptographicHash,底层是高度优化的 C 实现。

第二步是引入异步:数据量超过 40000 字符时,自动切换到 QtConcurrent::run 在线程池计算,完成后通过信号槽把结果传回 UI 线程。QMetaObject::invokeMethod 配合 Qt::QueuedConnection 确保 UI 更新的线程安全。

数据校验

数据校验模块 — CRC16/32 查表法 + MD5/SHA1/SHA256,大数据量自动异步计算

全局错误处理

一开始错误处理是零散的——每个模块自己 try-catch,日志到处写,出了问题根本找不到。后来写了一个 ErrorHandler 单例类统一管理,定义了四个错误级别:

ErrorHandler 四级错误级别

所有日志通过 QMutexLocker 保证线程安全,日志文件做了 5MB 滚动切割和按日切换,长时间运行也不会撑爆磁盘。

这个项目教会我的事

做完这个项目,我对 TCP/IP 的理解从"书上背过"变成了"亲手写过"。以前觉得 Socket 编程很底层很复杂,真写了之后发现其实就是几个函数的组合调用。真正难的不是 API,而是那些"非功能性"的东西:错误处理、日志管理、线程安全、用户体验细节。

Qt 的信号槽机制也颠覆了我对事件驱动编程的理解。在接触 Qt 之前,我写回调写到崩溃。有了信号槽之后,组件之间的通信变得特别自然——就像在说"这件事发生了,你们谁关心谁来处理"。


NDATools 目前已经完成了核心功能,后续计划加入 HTTP 请求调试、WebSocket 支持和两个客户端之间的报文差异对比。项目源码在 GitHub 上,欢迎交流。

文章链接: