pytcper TCP调试助手 - 开发记录
用户文档(功能总览 / 运行环境与启动 / 界面操作说明 / 二进制分发版)见 README.md;本文档整合项目结构、源码分析、测试情况、源码 Bug 分析、构建打包与系统兼容性、常见问题、后续可扩展方向与版本历史。
一、项目结构
Tcp调试工具/
├── tcp_debug_tool.py # 入口模块(聚合导出,`python tcp_debug_tool.py` 启动;关键符号 main())
├── tcp_utils.py # 常量与工具函数(ENCODINGS、encode_text、decode_bytes、guess_decodable、close_socket)
├── base_tab.py # 公共标签页基类(_BaseTab:日志区、发送区、消息泵、循环发送、历史、统计)
├── client_tab.py # TCP 客户端标签页(ClientTab:连接/断开、自动重连、代际令牌)
├── server_tab.py # TCP 服务端标签页(ServerTab:多客户端、定向/广播、踢线、keepalive)
├── app.py # 主窗口与程序入口(App:Notebook 双标签页、窗口记忆、关于菜单)
├── start.pyw # 无控制台窗口启动入口(GUI 子系统,pythonw 执行)
├── 启动调试助手.bat # 一键启动器(本机 pythonw 后台运行,无 DOS 窗口)
├── smoke_test.py # 无界面冒烟测试脚本(14 项)
├── build_linux.sh # Linux 构建脚本(PyArmor 加密 + PyInstaller 打包)
└── .tcp_tool_config.json # 运行配置(窗口大小),首次关闭窗口时自动生成
依赖单向无环:tcp_utils → base_tab ← client_tab / server_tab → app → 聚合入口,import tcp_debug_tool 与启动命令保持不变。
二、源码分析(技术实现要点)
- 线程模型:连接 / 接收 / 监听全部在后台线程执行(daemon),界面永不阻塞;socket 操作与 tkinter 界面更新通过
queue.Queue+ 主线程 50ms 周期after()消息泵(_drain)解耦,避免跨线程操作 tkinter; - 线程安全:tkinter 变量只在主线程访问;子线程通过
trace_add同步的普通属性缓存读取编码 / 显示配置,避免跨线程访问 tk 变量(修复了切换编码后立即收发导致乱码的问题);收发统计使用独立互斥锁_stats_lock保护累加,多客户端并发接收不丢计数; - 流式增量解码:接收端按连接维护
codecs.getincrementaldecoder状态,多字节字符(GBK / UTF-8 等)被 TCP 拆到多个数据包时不会出现半个字节的乱码;编码或「接收显示HEX」切换时自动重建解码器; - 自动编码探测:服务端无法获知客户端发送字节的编码,当按当前编码解码出替换字符(乱码)时,
guess_decodable在 UTF-8 / GBK / GB2312 / BIG5 / UTF-16LE / Latin-1 中挑选替换字符最少的方案显示(仅显示用途,不改变流式解码器状态,不误伤跨包分包); - TCP keepalive 死连接检测:对每个接入的客户端 socket 开启 keepalive(空闲 30s 探测 / 间隔 10s / 失败 3 次),客户端拔网线 / 崩溃(无 FIN/RST)时 recv 最终抛错并触发
_remove_client,及时从客户端列表移除,不再无限期显示在线; - 发送限时:发送统一走带超时封装的
_sendall_safe(单次 5s,空闲 30s),对端接收窗口已满或网络中断而未被 TCP 检测到时,界面不会因sendall阻塞而冻结;接收循环对超时单独处理为继续等待,不误判断线; - 连接代际令牌:每次发起 / 断开连接递增
_conn_epoch,过期的连接成功 / 失败 / 断线消息被丢弃;连接进行中重复点击按钮会被守卫拦截,不再产生并发连接线程与界面状态错乱; - 周期任务管理:消息泵、在线时长刷新、循环发送、自动重连均记录
after句柄,关闭窗口时统一取消,无残留回调报错; - 资源清理:
shutdown()→_close_conns()统一关闭 socket、取消周期回调;正在连接的线程在窗口关闭后不再建立连接; - 健壮性细节:日志区超过 5000 行自动裁剪头部防止内存无限增长;客户端列表选中项按「ip:port」名称匹配 socket(而非索引),避免列表未刷新时发错对象,且每秒刷新重建列表时按名称恢复选中项,点选后不会因在线时长更新而丢失选择;窗口位置完全落在屏幕外时不恢复,防止窗口不可见。
三、测试情况
py -m py_compile tcp_debug_tool.py:语法编译检查通过;py smoke_test.py:无界面冒烟测试 14 项,覆盖:- 工具函数(编码转换、HEX、时长格式化)
- 服务端启动 + 客户端连接
- 客户端列表在线时长显示
- 客户端 GBK 中文发送 → 服务端 GBK 接收
- 服务端 HEX 发送 → 客户端 HEX 显示
- 收发统计(条数与字节数)
- 广播 + 历史报文记录与切换
- 定时循环发送(3 次自动停止)
- 发送文件(原样字节)
- 导出收发记录
- 服务端断开所选客户端
- 自动重连(断线 → 服务恢复 → 自动连上)
- 断开连接 / 停止服务(资源清理无残留回调)
- 窗口大小记忆
- v1.1 针对性验证(独立脚本):
- 增量解码:GBK 多字节字符按 3 字节分包模拟 TCP 拆包,仍完整解出「你好服务器」;
- 并发统计:8 线程 × 2000 次并发累加,收发统计无丢失(16000/16000)。
- v1.1.1 针对性验证(独立脚本):
- 点选客户端后等待 2 次列表刷新(2.3s),选中状态保留(
curselection不为空); - 选中保留后定向发送成功(客户端收到报文)、「断开所选」成功断开对应客户端。
- 点选客户端后等待 2 次列表刷新(2.3s),选中状态保留(
- v1.2.0 后续针对性验证(独立脚本):
- 客户端 / 服务端标签页均存在「清除」按钮;
- 输入内容后点击清除,发送框清空、历史导航索引重置。
- v2.0.001 针对性验证(独立脚本):
- 跨编码显示:客户端 GBK / UTF-16 发送,服务端界面保持 UTF-8,收发记录正确显示中文(自动编码探测);
- 断开同步:客户端正常断开后服务端立即移除列表项;
_enable_keepalive生效,SO_KEEPALIVE 可正常设置;- 13 种编码标签端到端编码 / 解码均正常,UTF-8(BOM) 前导 BOM 正确。
当前状态(2026-08-20 会话)
- 冒烟测试 PASS 1-13 全绿(含本次修复的踢线 / 自动重连 / 断开停止);第 14 项受
app.py最小宽度改动影响(见第四节 Bug 分析); - 便携 Python 3.11.16(Tk 9.0)下完整运行冒烟测试:PASS 1-13 全部通过;
- 高兼容版二进制在 X11 环境启动正常,进入 tkinter GUI 事件循环。
四、源码 Bug 分析
对全部源码逐文件审查,发现并修复 2 个真实 Bug(均在冒烟测试中验证)。
Bug 1(已修复):Linux 下直接 close 正被阻塞 recv 的 socket 不生效
- 位置:
base_tab.py/client_tab.py/server_tab.py中所有对连接 socket 的直接close() - 现象:服务端「断开所选客户端」后,客户端始终感知不到断开;冒烟测试第 11 项稳定失败(
踢线后客户端未断开) - 根因:Linux 上若在另一线程正阻塞于
recv()时直接close()该 socket,关闭不会真正生效——对端收不到 FIN,阻塞的 recv 线程永久阻塞(Windows 上 close 会中断 recv,因此开发机上未暴露)。已用纯 socket 脚本复现确认 - 修复:
tcp_utils.py新增close_socket(),先shutdown(SHUT_RDWR)唤醒阻塞线程,再close;替换全部相关关闭点(含客户端断开、服务端踢线/移除/停止) - 验证:冒烟测试第 11-13 项(踢线、自动重连、断开/停止)全部通过
Bug 2(已修复):_accept_loop 双重读取 self.server_sock 的竞态
- 位置:
server_tab.py_accept_loop - 现象:循环条件与
accept()调用分别读取self.server_sock,停止服务瞬间可能读到None后调用None.accept()→AttributeError使 accept 线程崩溃 - 修复:循环顶部一次性捕获
srv = self.server_sock,为None即退出,消除竞态窗口 - 验证:冒烟测试第 2/13 项(服务端启动/停止)通过
其他审查结论(非缺陷,未改动)
_refresh_stats_label无锁读取统计字典:GIL 下仅可能短暂滞后,无害CONFIG_FILE基于__file__定位:单文件打包后配置写入临时解包目录,窗口大小记忆无法跨运行持久化(见第五节)- 冒烟测试第 14 项(窗口大小记忆):根因是工作区
app.py在会话中途被外部修改,minsize由 720 提升至 1200(标题同步增加空格),最小宽度超过需恢复的 900,窗口被钳制到 1200 宽导致断言失败——非显示环境问题(此前"环境伪影"的结论已更正)。该改动与代码原注释的设计意图(先恢复尺寸、再设最小值避免钳制)相冲突,会使窗口大小记忆对较窄窗口失效;若不需要可还原。在原始minsize(720)下,第 14 项在会话早期已完整通过(PASS 1-14 全绿)
五、构建、打包与系统兼容性
5.1 源码加密(PyArmor,可选加固)
pyarmor gen -O obf tcp_debug_tool.py tcp_utils.py base_tab.py client_tab.py server_tab.py app.py
- 6 个模块加密为字节码 blob + 引导头,源码不可直接阅读;
- 加密产物整体移入
obf/,可在打包时一并以--add-data "obf:obf"、--paths obf打入二进制; - 加密提高静态破解门槛,运行时仍需还原字节码,无法绝对不可破解(客户端加密方案通病);
- PyArmor trial 版产物标注
(trial),正式分发请注册许可。
5.2 构建命令
本机版(系统 Python,x86_64,要求 glibc ≥ 2.38):
python3 -m PyInstaller --onefile --name pytcper --windowed --clean \
--distpath dist-linux --workpath build/pyinstaller-onefile tcp_debug_tool.py
高兼容版(便携 Python 构建,可运行于 openEuler 22.03 等 glibc ≥ 2.17 系统):
# 下载 python-build-standalone 便携 Python(glibc 2.17 兼容、内置 tkinter,任意目录解压,以 /tmp/python 为例):
curl -L -o pybs.tar.gz "https://github.com/astral-sh/python-build-standalone/releases/download/<tag>/cpython-3.11.16%2B<tag>-x86_64-unknown-linux-gnu-install_only.tar.gz"
tar -xzf pybs.tar.gz
/tmp/python/bin/python3 -m pip install pyinstaller
# 构建(注意显式补齐 Tcl/Tk 9.0 库与数据目录,默认 hook 不收集 9.0):
/tmp/python/bin/python3 -m PyInstaller --onefile --name pytcper --windowed --clean \
--distpath dist-linux-oe2203 --workpath build/pyinstaller-portable \
--add-binary "/tmp/python/lib/libtcl9.0.so:." \
--add-binary "/tmp/python/lib/libtcl9tk9.0.so:." \
--add-binary "/tmp/python/lib/thread3.0.6/libtcl9thread3.0.6.so:." \
--add-data "/tmp/python/lib/tcl9.0:_tcl_data" \
--add-data "/tmp/python/lib/tk9.0:_tk_data" \
tcp_debug_tool.py
目录方式(启动快,便于调试排查):
python3 -m PyInstaller --name pytcper --windowed --clean \
--distpath build --workpath build/pyinstaller-dir tcp_debug_tool.py
5.3 产物清单
| 产物 | 路径 | 大小 | 说明 |
|---|---|---|---|
| 单文件二进制(高兼容版) | dist-linux-oe2203/pytcper |
22 MB | 便携 Python 构建,要求 glibc ≥ 2.17,可运行于 openEuler 22.03(推荐分发) |
| 单文件二进制(本机版) | dist-linux/pytcper |
12 MB | 系统 Python 构建,要求 glibc ≥ 2.38,仅限新系统 |
| 目录方式二进制 | build/pytcper/pytcper |
1.6 MB | 含运行时依赖目录,启动更快 |
| aarch64 发布包 | release/pytcper-v2.0.001-linux-aarch64-onefile.tar.gz |
12 MB | Linux ARM64,PyArmor 加密 + PyInstaller 打包 |
| 构建规格 | pytcper.spec |
— | 可复用:pyinstaller pytcper.spec |
5.4 运行验证
- 本机版 / 目录方式 / aarch64 产物均已在 X11 显示环境后台启动 3-4 秒,确认正常进入 tkinter GUI 事件循环(与
build_linux.sh验证方式一致); - 高兼容版二进制在 X11 环境启动正常(捆绑 Tk 9.0);便携 Python 3.11.16 下完整运行冒烟测试 PASS 1-13 全部通过。
5.5 系统兼容性(glibc 版本要求)与 openEuler 22.03 适配
- 二进制运行时依赖的不是 gcc 版本,而是 glibc(动态链接器)版本;gcc 仅在构建期使用(本环境 gcc 12.3.1)。
- 本机版(系统 Python 3.11.13,构建于 openEuler 25.09 / glibc 2.38):捆绑的
libpython3.11.so、libtk/libtcl、libX11等引用 GLIBC_2.38 符号(如__isoc23_strtol、fmod),要求 glibc ≥ 2.38,无法在 openEuler 22.03(glibc 2.34)上运行(报version GLIBC_2.38 not found)。 - 高兼容版(便携 Python 3.11.16):经实测,外层 bootloader 最高要求 GLIBC_2.14,全部捆绑库最高要求 GLIBC_2.17(libpython 2.17、libtcl9tk9 2.15、libtcl9 2.14、_tkinter 2.2),可运行于 openEuler 22.03(glibc 2.34),也覆盖 CentOS 7(2.17)、Ubuntu 18.04+、Debian 10+ 等 glibc ≥ 2.17 的系统。
降低 glibc 要求的方法(三选一):
- 便携 Python 构建(本仓库采用):python-build-standalone 的 CPython 针对 glibc 2.17 构建,产物自动兼容 glibc ≥ 2.17(见 5.2)。构建机无需容器 / 特权;
- openEuler 22.03 容器内构建:
docker run --rm -v $PWD:/src openeuler/openeuler:22.03-lts-sp4 bash -c "dnf install -y python3 python3-tkinter && pip install pyinstaller && cd /src && python3 -m PyInstaller ..."; - 在目标系统本机构建:直接在 openEuler 22.03 机器上装 python3 + python3-tkinter + pyinstaller 后执行 5.2 原命令。
5.6 说明与限制
- 架构绑定 / 不支持交叉编译:Windows 上生成的 exe 只含 Windows 解释器与 DLL;Linux 产物必须在 Linux x86_64 环境(已装 Python 3.8+ 且
python3-tk/python3-tkinter)构建;aarch64 同理需在 ARM64 机器构建; - 无需 Python:exe 与 Linux 单文件均为自包含(内嵌 Python 运行时与 tkinter),目标机无需安装 Python;
- 单文件模式的配置持久化:
tcp_utils.CONFIG_FILE基于__file__定位,单文件打包后落在临时解包目录(每次运行重建),窗口大小记忆无法跨运行持久化;如需持久化,可将配置路径改为用户目录(如~/.config/pytcper/); - 构建产物不入库:
dist/、build/、dist-linux/、dist-linux-oe2203/、obf/、*.spec均为构建输出,已在.gitignore忽略。
六、无控制台窗口启动(优化 DOS 黑窗)
6.1 原因
用 py / python 直接运行 tcp_debug_tool.py 时,解释器为控制台子系统程序,每次打开都会弹出黑色 DOS 窗口。GUI 应用不应有此控制台。
6.2 方案
改用 pythonw(GUI 子系统 Python,随官方安装包提供,位于安装目录)运行,启动时不创建控制台窗口。
| 入口 | 说明 |
|---|---|
start.pyw |
.pyw 扩展名脚本,双击 / 运行默认由 pythonw 执行,无控制台窗口 |
启动调试助手.bat |
一键启动器:定位 pythonw 后台运行 tcp_debug_tool.py(start "" pythonw ...),批处理窗口立即自行退出 |
.bat 内已做回退:找不到 pythonw 时退回 python(此时会保留控制台,但属兜底)。
6.3 优先级建议
- 日常使用:双击
启动调试助手.bat或start.pyw,均无黑窗; - 开发 / 调试:仍用
py tcp_debug_tool.py(控制台可查看打印与报错)。
6.4 覆盖验证
py -m py_compile start.pyw通过;- 用
pythonw实际启动tcp_debug_tool.py,进程正常进入 GUI 事件循环(tasklist 可见pythonw.exe存活),无控制台窗口。
七、常见问题
| 问题 | 处理方式 |
|---|---|
| 启动服务报「地址被占用」 | 端口被其他程序占用,更换监听端口 |
| 连接超时 / 拒绝 | 确认目标服务地址、端口、防火墙放行 |
| 接收中文乱码 | 收发双方使用一致的编码(如设备用 GBK,工具就选 GBK) |
| HEX 发送报错 | 输入需为偶数个十六进制字符,如 01 0A FF |
| 循环发送没反应 | 确认已连接服务器 / 已选中目标客户端,发送失败会自动停止并提示 |
八、后续可扩展方向
- 报文保存与回放(录制 → 重放)
- 收发数据按关键字过滤 / 高亮
- 多路连接同时管理
- 数据加解密(AES / 自定义算法)
九、版本更新记录
v2.0.001(2026-08-19)— 版本号更新
| 变更 | 说明 |
|---|---|
| 更新 | 软件版本号由 v1.3.0 更新为 v2.0.001 |
v1.3.0(2026-08-18)— 更名与版本号更新
| 变更 | 说明 |
|---|---|
| 更新 | 软件名由「TCP 调试助手」更名为「pytcper TCP调试助手」(窗口标题、关于对话框、文档同步) |
| 更新 | 软件版本号由 v1.2.0 提升至 v1.3.0 |
v1.2.0 后续变更(2026-08-18)— 功能增强与重构
| 变更 | 说明 |
|---|---|
| 新增 | 发送区「清除」按钮(客户端 / 服务端通用),一键清空发送框并重置历史导航起点 |
| 重构 | 单文件(1036 行)拆分为模块化结构:tcp_utils / base_tab / client_tab / server_tab / app + 聚合入口,依赖单向无环,import tcp_debug_tool 与启动命令保持不变 |
| 更新 | 「关于开发者」对话框标签由「开发版本所有者」改为「版权所有者」 |
| 更新 | README 拆分为用户文档 + 开发记录(dev-doc.md) |
验证:py -m py_compile 全部模块通过;smoke_test.py 14 项回归通过;清除按钮功能实测通过。
v1.2.0(2026-08-18)— 新增「关于开发者」
| 变更 | 说明 |
|---|---|
| 新增 | 菜单栏「帮助 → 关于开发者」对话框,显示版权所有者(micoder)与项目地址,点击地址可跳转浏览器打开 |
| 更新 | 软件版本号由 v1.1.1 提升至 v1.2.0 |
v1.1.1(2026-08-18)— 服务端列表选中修复
| 级别 | 问题 | 修复方案 |
|---|---|---|
| 中等 | 服务端客户端列表每秒刷新(在线时长更新)时重建列表,清空用户点选的客户端:无法断开所选、无法定向发送,仅广播可用 | 刷新前记录选中项的客户端名称,重建后按名称恢复选中并滚动到可见位置;客户端若已断开则自然不恢复 |
验证:点选后等待 2.3s(跨 2 次刷新)选中保留;定向发送 / 断开所选均正常;py -m py_compile 通过;smoke_test.py 14 项全部通过。
v1.1(2026-08-18)— Bug 修复版本
| 级别 | 问题 | 修复方案 |
|---|---|---|
| 严重 | 多字节字符(GBK/UTF-8)跨 TCP 分包时被截断成乱码 | 接收端改用按连接维护的增量解码器(codecs.getincrementaldecoder),编码/HEX 视图切换自动重建 |
| 严重 | 对端异常时 sendall 无超时阻塞,主线程冻结、界面卡死 |
发送统一走带超时的 _sendall_safe(单次 5s / 空闲 30s);接收循环对 socket.timeout 单独处理为继续等待 |
| 中等 | 双击「连接」启动多个连接线程;过期的连接成功/失败/断线消息导致界面状态错乱 | 引入连接代际令牌 _conn_epoch 过滤过期消息;_connecting 守卫拦截重复点击 |
| 低 | 多客户端并发接收时统计累加丢计数 | _bump_stats 加独立互斥锁保护 |
| 低 | 关闭窗口瞬间正在连接的 socket 泄漏 | _connect_worker 在窗口已关闭时关闭 socket 并退出 |
| 低 | HEX 发送 + 自动追加换行会在字节流尾部多出 0D 0A |
HEX 发送模式下不再追加换行 |
| 低 | 日志区无限增长 | 超过 5000 行自动裁剪头部 |
| 低 | 客户端断开到列表刷新的间隙,选中索引错位可能发错对象 | 选中项按「ip:port」名称匹配 socket(取最新连接) |
| 低 | 手动编辑发送框后按 ↑/↓ 历史跳转起点错乱 | 导航前比对当前文本与历史项,不一致时重新定位 |
| 低 | 窗口位置恢复到已移除的屏幕外区域,窗口不可见 | 恢复前校验几何位置是否落在当前屏幕内 |
验证:py -m py_compile 通过;smoke_test.py 14 项全部通过;增量解码与并发统计两项针对性脚本验证通过。