# TcpDebugTool **Repository Path**: Gatico/tcp-debug-tool ## Basic Information - **Project Name**: TcpDebugTool - **Description**: tcp-debug-tool - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-14 - **Last Updated**: 2026-09-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # TCP/IP 调试工具(TcpDebugTool) 基于 **Java Swing** 的 TCP / UDP / 端口综合调试工具。 **三条通道(TCP 服务端 / TCP 客户端 / UDP)可以同时运行** —— 例如一边监听 8888、一边用客户端连上 `127.0.0.1:8888`,就能在同一个程序里完成自定义协议的封包与拆包自测。 内置**可视化协议编辑器**,可自由定义帧格式(帧头、长度、序列号、命令码、校验、帧尾), 并自动完成**封包**与**流式拆包**(粘包 / 半包 / 校验失败都会正确处理)。 构建产物除可执行 jar 外,还会用 **jlink** 生成最小化 JRE,再用 **jpackage** 打包成 **自带精简运行时的 Windows 绿色版 exe**(目标机器无需安装 JDK),另可选生成 `.exe` 安装包。 --- ## 一、环境与构建 | 项目 | 路径 | | --- | --- | | JDK(打包用) | `D:\Project\jdk\jdk-25.0.1`(普通 OpenJDK 25 LTS) | | GraalVM(原生 exe 用) | `D:\Project\jdk\graalvm-jdk-25.0.3+9.1`(带 `native-image`) | | Visual Studio 2022 | 需装「使用 C++ 的桌面开发」工作负载,native-image 靠它编译链接 exe | | 工程目录 | `D:\Project\Java\TcpDebugTool` | | Maven | `D:\Project\maven\apache-maven-3.8.8` | | WiX | `wix.exe` 5.0.2(`C:\Users\Administrator\.dotnet\tools\`,已装 Util/UI 扩展,打包安装包用) | > **为什么用普通 OpenJDK 而不是 GraalVM**:GraalVM 自带 `bin/jvmcicompiler.dll`(Graal JIT 编译器, > 42~49 MB),它会占掉精简运行时的一半,而且**删不掉**(删了 JVM 直接拒绝启动)。 > 普通 OpenJDK 没有这个 DLL,打包体积直接**减半**。本程序是纯 Swing + java.net,用不到 Graal JIT。 > > 编译目标字节码保持 **release 21**,所以产出的 jar 在 JDK 21~25 上都能跑; > exe 里捆绑的运行时版本由**运行 Maven 的那个 JDK** 决定(见 `build.bat` 的 `JAVA_HOME`),两者互不影响。 ### 一键构建 / 运行 ```bat cd /d D:\Project\Java\TcpDebugTool build.bat :: jar + 最小化 JRE + 绿色版 exe build.bat installer :: 额外生成 .exe 安装包 build-native.bat :: GraalVM 原生 exe(不依赖 JVM,见第三节) run.bat :: 优先启动绿色版 exe,否则用 JDK 跑 jar :: 换 JDK 打包(例如用 GraalVM,体积会大一倍) set TCPDEBUG_JDK=D:\Project\jdk\graalvm-jdk-25.0.3+9.1 build.bat installer ``` > 在 Git Bash / MSYS 环境下,原生 exe 可用 `bash native/build-native.sh` 等价构建 > (脚本会自动探测 MSVC 与 Windows SDK 版本,无需手动 `vcvars64.bat`)。 ### 命令行方式 ```bat set JAVA_HOME=D:\Project\jdk\jdk-25.0.1 set PATH=D:\Project\maven\apache-maven-3.8.8\bin;%JAVA_HOME%\bin;%PATH% mvn -Pdist clean package :: jar + 最小化 JRE + 绿色版 exe mvn -Pdist,installer clean package :: 额外生成 .exe 安装包 mvn clean package -P '!dist' :: 只要 jar,跳过打包(快) ``` ### 产物一览(OpenJDK 25 打包) | 产物 | 说明 | 大小 | | --- | --- | --- | | `target\TcpDebugTool.jar` | 可执行 fat jar(已内嵌 FlatLaf),需要 JDK 才能运行 | 约 2.1 MB | | `target\jre\` | **jlink 生成的最小化 JRE**,可直接 `target\jre\bin\java -jar target\TcpDebugTool.jar` | 约 47 MB | | `target\dist\TcpDebugTool\` | **绿色版**:`TcpDebugTool.exe` + `app\` + `runtime\`,整个目录拷走即可运行,**目标机器无需 JDK** | 约 50 MB | | `target\dist\installer\TcpDebugTool-1.1.2.exe` | Windows 安装包(开始菜单项 / 桌面快捷方式 / 可选安装目录),需 `-Pinstaller` | 约 35 MB | > 绿色版 exe 与安装包都是 **x64 GUI 程序**,启动时不弹控制台窗口。 ### 不同 JDK 打包体积对比 | 项目 | GraalVM 21.0.11 | GraalVM 25.0.3 | **OpenJDK 25.0.1** | | --- | --- | --- | --- | | 完整 JDK | 601 MB | 771 MB | 约 350 MB | | `jvmcicompiler.dll`(Graal JIT) | 49 MB | 42 MB | **无** | | `bin/server`(jvm.dll) | 13 MB | 15 MB | 14 MB | | **最小化 JRE** | 95 MB | 90 MB | **47 MB** | | **绿色版目录** | 97 MB | 93 MB | **50 MB** | | **安装包** | 58 MB | — | **35 MB** | **结论**: - 换 GraalVM 21 → GraalVM 25 只省 4 MB(差异几乎全在那个 JIT DLL); - 换 GraalVM → **普通 OpenJDK 才是关键**,绿色版 93 MB → **50 MB**,安装包 58 MB → **35 MB**,接近减半。 `--compress=zip-9`、`--strip-native-commands` 这类参数实测只能再省几十到一百多 KB,可以忽略。 ### 打包安装包需要 WiX 4/5 扩展 JDK 21 的 `jpackage` 走 WiX 3(`candle.exe`/`light.exe`,本机 `D:\Program\wix` 已装); **JDK 22+ 的 `jpackage` 改走 WiX 4/5 的 `wix.exe`**,且需要两个扩展。 本机 `wix.exe` 是 5.0.2,已安装以下扩展: ```bat wix extension add -g WixToolset.Util.wixext/5.0.2 wix extension add -g WixToolset.UI.wixext/5.0.2 ``` 如果换机器打包安装包时报 exit 144,就是这两个扩展没装,跑一遍上面的命令即可。 绿色版(`APP_IMAGE`)不需要 WiX,任何情况下都能打。 ### JDK 25 下的启动警告(已处理) FlatLaf 会 `System.load` 一个本地库,JDK 24+ 默认对「受限原生方法」告警, 未来版本会直接拒绝执行。已在 `pom.xml` 的 `javaOptions` 里加了: ``` --enable-native-access=ALL-UNNAMED ``` 该参数 JDK 21 也接受,所以两边都能用,加了之后启动无任何警告输出。 --- ## 二、打包实现(jlink + jpackage) Maven 中绑定了 5 个步骤,全部在 `package` 阶段按顺序执行: | 步骤 | 插件 | 作用 | | --- | --- | --- | | ① | `maven-shade-plugin` | 打成含依赖的 fat jar | | ② | `maven-antrun-plugin` | 清理上一轮的 `jre/`、`jpackage-input/`、`dist/`(jlink/jpackage 不允许输出目录已存在) | | ③ | `maven-resources-plugin` | 把 fat jar 复制到 `target/jpackage-input`(jpackage 的 `--input` 目录) | | ④ | `exec-maven-plugin` | 调用 `jlink` 生成最小化运行时 | | ⑤ | `org.panteleyev:jpackage-maven-plugin` | 调用 `jpackage` 生成绿色版 exe / 安装包 | ### 最小化 JRE 的模块集 模块集由 `jdeps` 分析得出,配置在 `pom.xml` 的 `jlink.modules`: ```bat jdeps --print-module-deps --ignore-missing-depends --multi-release 21 target\TcpDebugTool.jar :: 输出:java.base,java.desktop,java.logging,java.prefs ``` `java.desktop` 会自动带上 `java.datatransfer`、`java.prefs`、`java.xml`,所以只需声明两个模块: ```xml java.desktop,java.logging ``` jlink 参数:`--strip-debug --no-header-files --no-man-pages --compress=zip-6`。 ### 关于体积 **不要用 GraalVM 打包**。GraalVM 特有的 `bin/jvmcicompiler.dll`(Graal JIT 编译器)在 JDK 21 里 49 MB、 JDK 25 里 42 MB,会占掉精简运行时的一半;而且**删不掉** —— 实测删掉后 JVM 直接拒绝启动 (`Cannot use JVMCI compiler: No JVMCI compiler found`),加 `-XX:-UseJVMCICompiler` 或 `-XX:-EnableJVMCI` 都无法绕过。 普通 OpenJDK 没有这个 DLL,同样的模块集打包出来只有 47 MB(GraalVM 是 90~95 MB)。 本程序是纯 Swing + java.net,用不到 Graal JIT,所以直接用 `D:\Project\jdk\jdk-25.0.1` 打包即可。 想验证的话,用 `TCPDEBUG_JDK` 切回 GraalVM 打一次包对比体积即可。 `--compress=zip-9`(对比 zip-6 省 42 KB)和 `--strip-native-commands`(省 135 KB)这类参数 实测收益极小,不值得为此牺牲 `target\jre\bin\java` 的可用性。 ### 应用图标 `packaging/app.ico` 是 7 个尺寸(16/24/32/48/64/128/256)的图标,由 `packaging/make_icon.py` 用纯标准库生成(圆角方块 + 蓝色渐变 + 双向箭头)。改图标只需重新运行: ```bat python packaging\make_icon.py ``` --- ## 三、GraalVM Native Image(原生 exe) `build-native.bat` 用 GraalVM Native Image 把程序**提前编译(AOT)成不依赖 JVM 的原生 exe**, 双击即启动,冷启动和内存占用都明显优于 jpackage 版。 ### 构建 ```bat build-native.bat :: GUI 子系统,双击不弹黑框(推荐) build-native.bat console :: 保留控制台,排错时用 :: 换 GraalVM set TCPDEBUG_GRAALVM=D:\Project\jdk\graalvm-jdk-25.0.3+9.1 build-native.bat ``` | 依赖 | 说明 | | --- | --- | | GraalVM JDK 25 | 需带 `native-image`,默认 `D:\Project\jdk\graalvm-jdk-25.0.3+9.1` | | Visual Studio 2022 + C++ 生成工具 | native-image 靠 MSVC 编译链接 exe;脚本用 `vswhere` 自动定位并调用 `vcvars64.bat` | | Maven + 普通 OpenJDK 25 | 只用来产出 fat jar,`target\dist` 仍保持 OpenJDK 的小体积版本 | ### 产物 > ⚠️ **原生版不是单文件**。Windows 上 native-image 会把 JDK 的原生库单独吐在 exe 旁边, > 必须整个目录一起分发。 ``` target\native\ ├── TcpDebugTool.exe 54.8 MB 原生可执行文件 ├── awt.dll / freetype.dll / fontmanager.dll / lcms.dll / ... 约 3.8 MB └── lib\fontconfig.bfc 4.8 KB AWT 字体配置 ``` | 版本 | 形式 | 体积 | | --- | --- | --- | | jpackage 绿色版 | `exe + app\ + runtime\` | 50 MB | | **Native Image** | `exe + 若干 dll + lib\` | **58.6 MB** | 体积**比 jpackage 版更大**(多出的主要是反射元数据,见下方第 2 点), 优势在于**启动更快、内存占用更低、不需要 runtime 目录**; 代价是**只能跑在 x64 Windows 上**(跨平台要各自重新构建),构建一次约 3.5 分钟。 ### 为什么需要额外配置(三个坑) Swing 程序打 Native Image 不是加个参数就行,实测踩到三个问题,都已修好: **1. `java.home` 为 null** —— 见 `TcpDebugTool.installJavaHomeForNativeImage()`。 Native Image 没有 JDK 安装目录,`java.home` 默认是 `null`,而 AWT 的字体初始化 (`sun.awt.FontConfiguration.findFontConfigFile`)直接抛 `java.home property not set`。 即使给个空串也不行:找不到 fontconfig 会让内部的 `head` 保持 null,接着 `SunFontManager` 调 `getExtraFontPath()` 又抛 “Fontconfig head is null”, 最终 FlatLaf 初始化失败、界面根本起不来。 程序里把 `java.home` 指向 **exe 所在目录**,构建脚本再把构建用 JDK 的 `lib/fontconfig.bfc` 复制过去 —— 于是原生版的字体行为与普通 JVM 完全一致。 **2. AWT / Swing 的反射与 JNI 元数据** —— 见 `src/main/resources/META-INF/native-image/`。 Swing 大量用 `Class.forName` + 反射创建 UI 委托,AWT 的 native 代码(`awt.dll`)还会反过来 查 Java 侧的方法和字段。Native Image 要求这些**提前注册**,否则运行时报 `ClassNotFoundException` / `NoSuchFieldError` / `MissingReflectionRegistrationError`。 配置由**追踪代理**生成,再用合并脚本补全: ```bash bash native/run-agent.sh ``` 代理部分会把 `native/agent/AgentDriver.java` 编译后带 `-agentlib:native-image-agent` 跑一遍: 驱动类创建主窗口、打开各个对话框、渲染带助记符的控件、渲染带选中态的列表与表格、 弹出下拉框、**浅色深色两套主题各跑一次**,让代理记录真实用到的反射、JNI、资源。 合并脚本(`native/merge-metadata.py`)再补上三类代理**必然抓不到**的东西: | 补什么 | 为什么代理抓不到 | | --- | --- | | **FlatLaf 靠 properties 反射实例化的 101 个类**(`FlatListCellBorder$Selected`、各 `*UI.createUI` 等) | 从 fat jar 里扫 `com/formdev/flatlaf/**.properties` 自动提取,不用手写 | | **16 个 `java.awt.event.*Event` 类 + `WWindowPeer` 的 JNI 注册** | 调用点在 `awt.dll` 的 C 代码里,驱动收不到真实键鼠消息 | | **输入法(IME)**:`WInputMethod` / `WInputMethodDescriptor` / `InputMethodAdapter` | 同上;不注册的话**文本框打不了中文**(见下方说明) | | **Java2D 渲染管线**(复杂裁剪、位图光栅、字体) | 同上 | > **关于中文输入(踩过的坑)** > > 症状:原生 exe 的文本框**打不进中文**,日志里刷 > `NoClassDefFoundError: sun/awt/windows/WInputMethod`(`JNIFunctions.FindClass`)。 > > 容易误判为「类没打进镜像」——但用 `strings TcpDebugTool.exe | grep WInputMethod` > **能搜到类名**,类其实在镜像里(`WToolkit` 直接 `new` 出 `WInputMethodDescriptor`, > 整个 IME 类族都被拉进来了)。 > > 真正原因是:**JNI 的 `FindClass` 只认注册为 JNI 可访问的类,仅仅「可达」不够。** > 所以判断某个类是否 OK,要看**有没有 `jniAccessible` 条目**, > 而不是能不能在 exe 里搜到类名。 > > 补上这三个类的 `jniAccessible` + `allDeclaredConstructors` + `allDeclaredMethods` > 后中文输入恢复正常。`native/merge-metadata.py` 已包含。 > 改了界面代码或升级 FlatLaf 后,若原生 exe 冒出反射相关的报错, > **把 stderr 日志喂给合并脚本**即可自动补全 —— 新版 Native Image 的 > `MissingReflectionRegistrationError` 会直接给出该加的 JSON: > > ```bash > python native/merge-metadata.py native/agent-config/reachability-metadata.json \ > src/main/resources/META-INF/native-image/com.tcpdebug/tcp-debug-tool/reachability-metadata.json \ > target/TcpDebugTool.jar --log=<程序日志> > ``` **3. 启动告警** —— 与 jpackage 版一致,构建时加了 `--enable-native-access=ALL-UNNAMED` (FlatLaf 会 `System.load` 本地库),启动无任何警告输出。 ### 怎么验证原生 exe 真的没问题(重要) > ⚠️ **「窗口出现了、日志是空的」不等于打包成功。** 实测:原生 exe 启动完全干净、窗口标题正常、日志 0 字节, 但**一点按钮、一按键盘就狂刷异常** —— 因为缺失的反射/JNI 注册 只在对应代码路径真的被执行时才暴露,而「启动一个窗口」远不足以走到那些路径。 所以验证必须**真的操作界面**。本机沙箱拦截了 PowerShell 的 `Add-Type`(P/Invoke) 和 COM 实例化,但可以用 MSVC 自己编两个小工具: | 工具 | 作用 | | --- | --- | | `native/tools/sendinput.c` | 发真实鼠标点击 + 按键,并**枚举顶层窗口证明对话框真的弹出了** | | `native/tools/typeuni.c` | 用 `KEYEVENTF_UNICODE` 直接发中文字符,验证**中文输入通路** | ```bash # 编译(复用 native/msvc-env.sh 的工具链) cl.exe /nologo /O2 /W3 /D_CRT_SECURE_NO_WARNINGS /Fe:sendinput.exe sendinput.c user32.lib cl.exe /nologo /O2 /W3 /D_CRT_SECURE_NO_WARNINGS /Fe:typeuni.exe typeuni.c user32.lib # 启动程序后,在同一个 shell 里发消息(进程会在工具调用之间被回收) ./TcpDebugTool.exe > run.log 2>&1 & ./sendinput.exe # 点界面 ./typeuni.exe # 发中文 ``` 正常结果长这样(`peak=2` 说明真的弹出了对话框): ``` [BEFORE] top-level windows: 1 clicks=36 keys=20 | windows before=1 peak=2 after=1 | dialogs_opened=1 RESULT: OK - dialogs really opened, interaction paths were exercised ``` 此时 `run.log` 应当**为 0 字节**。实测三轮(点击 + 中文 + 再点击)日志始终为空。 ### `build-native.bat` 与 `build-native.sh` 的差异 两条路径的构建参数与产物**完全一致**,只有一处刻意不同 —— **遇到「程序正在运行」时怎么处理**: | | `build-native.bat`(双击用) | `native/build-native.sh`(命令行用) | | --- | --- | --- | | exe 正在运行 | **报错并暂停**,让用户自己关掉 | 直接 `taskkill /F` 杀掉后继续构建 | | 清理 `%SystemDrive%` 残留 | 有 | 有 | 理由:双击 `.bat` 时,静默杀掉用户正在用的程序比报错更糟;而 `.sh` 通常跑在 自动化流程里,需要它能无人值守地跑通。 ### 构建排错速查 | 现象 | 原因 | 处理 | | --- | --- | --- | | `LINK : fatal error LNK1104: 无法打开文件 ...TcpDebugTool.exe` | 上次冒烟测试的进程还占着 exe | 关掉程序;`.bat` 会自动检测并提示,`.sh` 会自动杀掉 | | 项目根目录多出名为 `%SystemDrive%` 的目录(约 900 KB,内容是 Windows 缓存 db) | native-image 收尾阶段的副产物,成因未定位,不影响产物 | 两个构建脚本都会自动清理 | | 某个界面操作突然刷 `MissingReflectionRegistrationError` | 那条代码路径没被追踪代理走到 | 把 stderr 日志喂给 `merge-metadata.py --log=` 后重新构建 | | 文本框打不进中文 | 缺 IME 类的 JNI 注册 | 见上方「关于中文输入」;`merge-metadata.py` 已含 | | 某类「明明在 exe 里搜得到」却报 `NoClassDefFoundError` | **可达 ≠ 能被 JNI 找到** | 查元数据里有没有 `jniAccessible` 条目,而不是 `strings` | > **在 Git Bash 里跑验证脚本时注意**:`kill $!` **杀不掉 Windows 进程** > ——`$!` 是 bash 子进程的 PID,与真实 PID 不同。要用: > > ```bash > MSYS2_ARG_CONV_EXCL='*' taskkill /F /IM TcpDebugTool.exe > ``` --- ## 四、目录结构 ``` TcpDebugTool/ ├── pom.xml Maven 配置(JDK21 / FlatLaf / shade / jlink / jpackage) ├── build.bat / run.bat 构建与启动脚本 ├── build-native.bat GraalVM 原生 exe 构建脚本 ├── packaging/ │ ├── app.ico 应用图标(多尺寸) │ └── make_icon.py 图标生成脚本(纯标准库,无需 Pillow) ├── native/ Native Image 构建支持 │ ├── build-native.sh native-image 构建(命令行版,见「.bat 与 .sh 的差异」) │ ├── msvc-env.sh 手工导出 MSVC 工具链环境变量(自动探测 MSVC/SDK 版本) │ ├── run-agent.sh 重跑追踪代理、重新生成元数据 │ ├── merge-metadata.py 合并代理产出 + 自动扫 FlatLaf properties + --log 补漏 │ ├── agent/AgentDriver.java 追踪驱动类(把界面代码跑一遍,不参与打包) │ ├── agent-classes/ 驱动类的编译输出(中间产物,可删) │ ├── agent-config/ 追踪代理的原始产出,喂给 merge-metadata.py │ └── tools/ 交互验证工具(用 MSVC 编译,见第三节) │ ├── sendinput.c 发真实鼠标点击 + 按键,并证明对话框真的弹出 │ └── typeuni.c 用 KEYEVENTF_UNICODE 直发中文字符 ├── README.md ├── src/main/resources/ │ ├── icons/ 应用图标(7 个尺寸的 PNG) │ └── META-INF/native-image/com.tcpdebug/tcp-debug-tool/ │ └── reachability-metadata.json 反射 / JNI / 资源注册(代理生成) └── src/main/java/com/tcpdebug/ ├── TcpDebugTool.java 程序入口(启动前恢复主题与字体偏好) ├── core/ 网络层 │ ├── NetService.java 服务基类(事件分发、广播、定向发送) │ ├── TcpServerService.java TCP 服务端(多客户端接入) │ ├── TcpClientService.java TCP 客户端(异步连接 + 自动重连) │ ├── UdpService.java UDP 收发通道 │ ├── Session.java 单条链路(字节统计、状态) │ ├── PortScanTask.java 多线程端口扫描 + Banner 抓取 │ ├── NetInfoUtil.java 网卡 / IP / MAC / DNS / 端口占用 │ ├── TrafficEvent.java 流量事件(带通道标签) │ └── Direction.java / TrafficListener.java ├── protocol/ 自定义协议引擎 │ ├── ProtocolDefinition.java 协议定义(字段列表) │ ├── ProtocolField.java 字段定义 │ ├── FieldType.java 字段类型枚举 │ ├── LengthMode.java 长度字段语义 │ ├── ChecksumType.java 12 种校验算法 │ ├── FrameCodec.java 封包 + 流式拆包(FrameDecoder) │ └── ProtocolStore.java 协议模板持久化 ├── ui/ 界面层 │ ├── MainFrame.java 主窗口(菜单 / 布局 / 主题 / 字体) │ ├── ConnectionPanel.java 三通道配置 + 统一连接列表 + 协议选择 │ ├── DataViewPanel.java 收发数据视图(多显示模式 / 通道筛选 / 过滤 / 统计) │ ├── SendPanel.java 发送面板(多目标 / HEX / 定时 / 快捷指令) │ ├── ProtocolEditorDialog.java 协议编辑器(字段表格 + 实时预览) │ ├── PortScanDialog.java 端口扫描窗口 │ ├── NetInfoDialog.java 本机网络信息窗口 │ ├── AppContext.java 全局上下文(协议、多通道、解码、事件) │ └── UiUtil.java 字体 / 配色 / 组件工厂 └── util/ ├── HexUtil.java HEX 转换、hexdump、大小端读写 ├── FontSupport.java 字体探测(本地化字体名 + 中文字形覆盖检查) ├── SimpleJson.java 零依赖 JSON 读写 ├── AppConfig.java 界面偏好持久化 └── AppPaths.java 配置目录 ``` --- ## 五、功能一览 ### 1. 三条并行通道(左侧面板) TCP 服务端、TCP 客户端、UDP 是**三条互相独立的通道,可以同时启动**,各自独立启停, 停止其中一条不会影响其它通道。 **TCP 服务端** - 监听地址可选 `0.0.0.0`(所有网卡)或任一具体网卡 IP,端口 1–65535 - 启动前会检测端口占用并提示 - 支持**多个客户端同时接入**,连接列表实时显示每个客户端的收发字节与连接时间 - 支持**通道内广播**(发给所有连接)与**定向发送**(发给选中的某条连接) - 右键连接可断开、双击可复制地址 **TCP 客户端** - 异步连接,不会阻塞界面;可设置连接超时 - 支持**断线自动重连**(可配置重连间隔),连接失败会持续重试 - 「连到本机服务端端口」按钮可一键把目标设为服务端当前端口 **UDP** - 绑定本地地址/端口收包,向指定远端地址/端口发包 - 显示每条数据报的来源,自动统计收发包数 **连接列表**统一展示所有通道的会话,带「通道」列区分;底部有「全部停止」按钮。 ### 2. 数据视图(右上) - 显示模式:`HEX`、`HEX + ASCII`、`ASCII(转义)`、`文本 UTF-8`、`文本 GBK`、`文本 ISO-8859-1` - **通道筛选**:只看 TCP 服务端 / TCP 客户端 / UDP 的数据 - 行首用 `[服务端]` / `[客户端]` / `[UDP]` 标出数据来自哪条通道 - 可选时间戳(毫秒)、方向标记(`<<` 接收 / `>>` 发送 / `--` 提示 / `!!` 错误) - 按方向着色,**校验失败的帧标红** - 关键字过滤、暂停/继续(暂停期间数据缓存,恢复后一次性刷出) - 接收/发送字节数与包数统计、显示行数、缓冲队列长度 - 保存日志、复制全部、右键菜单 - 超长日志自动裁剪,长时间运行不卡顿(队列 + 定时批量刷新) ### 3. 发送面板(右下) - 输入模式:**文本** / **HEX**(HEX 支持空格、逗号、`0x` 前缀,可一键美化) - 文本模式可选编码(UTF-8 / GBK / GB2312 / ASCII / ISO-8859-1)与行尾符(无 / LF / CR / CRLF) - **发送目标**下拉会列出所有运行中通道的会话,例如: - `[服务端] 127.0.0.1:53211 #1` - `[服务端] 全部连接(广播)` - `[客户端] 127.0.0.1:8888` - `[UDP] 127.0.0.1:8889` - **按协议封装**:自动加帧头、长度、序列号、校验、帧尾;可指定命令码 - **实际发送字节**实时预览 - **定时发送**:可设间隔(ms)与次数(0 = 不限) - **快捷指令**:保存常用报文,双击填入,持久化到本地 ### 4. 自定义协议编辑器(工具 → 协议编辑器) 左侧管理协议模板(新建 / 复制 / 删除 / 恢复内置),右侧以**字段表格**描述帧结构: | 字段类型 | 说明 | | --- | --- | | 帧头/前导码 | 固定字节,用于拆包同步,如 `AA 55` | | 长度字段 | 描述帧长,可设长度语义、字节数、字节序、补偿值 | | 序列号 | 自动递增,可设初值与步长 | | 命令码 | 发送时可覆盖的数值字段 | | 数据负载 | 变长,占据剩余空间 | | 校验码 | 12 种算法,可设校验范围 | | 帧尾 | 固定字节,可作分帧依据,如 `0D 0A` | | 固定值 | 常量字段,如从站地址 | | 保留填充 | 固定长度的 0x00 填充 | 每个字段可独立设置:**字节数**、**字节序**(BIG / LITTLE)、**值**(HEX 或十进制)。 **长度字段语义(LengthMode)**——记「负载偏移」为负载在帧中的起始位置,「尾部长度」为负载之后所有固定字段之和: | 模式 | 长度值的含义 | 推出的帧长 | | --- | --- | --- | | 仅负载长度 | 负载字节数 | 负载偏移 + 值 + 尾部长度 | | 整帧长度 | 整帧字节数 | 值 | | 整帧长度-长度字段自身 | 整帧字节数 − 长度字段长度 | 值 + 长度字段长度 | | 从长度字段起到帧尾 | 长度字段起至帧尾的字节数 | 长度字段偏移 + 值 | | 从负载起到帧尾 | 负载起至帧尾的字节数(含校验/帧尾) | 负载偏移 + 值 | > 说明:长度字段需放置在数据负载之前,否则无法在未知负载长度时定位。 **校验算法(ChecksumType)**:无校验、SUM8、SUM16、XOR8、XOR16、LRC、CRC-8(poly 07)、 CRC-16/MODBUS、CRC-16/IBM(ARC)、CRC-16/CCITT-FALSE、CRC-16/XMODEM、CRC-32。 **校验范围**:`校验起` 为相对帧首的起始偏移(默认 0);`校验止` 为结束偏移,填 `-1` 表示到校验字段之前。 编辑器底部提供**实时预览**:操作栏固定在顶部(「生成帧并解析」「复制帧 HEX」按钮始终可见), 下方三个文本区横向并排 —— **负载输入 → 生成的帧(HEX)→ 解析结果**, 解析结果会给出帧总长、负载偏移、命令码、序号、校验是否通过以及解析出的负载。 **拆包能力**:接收侧自动完成 - 帧头搜索与同步,帧头前的垃圾数据会被丢弃并提示 - 长度字段尚未收全时等待(半包) - 一次收到多帧时逐帧拆出(粘包) - 校验失败会标红并给出计算值 / 帧内值对比 - 单帧超过「最大帧长」时丢弃 1 字节重新同步,防止内存被撑爆 ### 5. 辅助工具 - **端口扫描**:多线程探测指定主机端口范围(1–65535),自动标注常见服务名并尝试抓取 Banner, 支持快捷范围、进度显示、取消、复制结果、导出 CSV - **本机网络信息**:全部网卡的名称 / 显示名 / IP / MAC / 状态,DNS 域名解析,本机端口占用检查 --- ## 六、中文显示与字体(重要) **如果输入框里的中文显示成方框**,是等宽字体不含中文字形导致的。 常见编程字体(Consolas、Cascadia Mono、JetBrains Mono)**都没有中文字形**, 在 Swing 里不会自动回退,中文会渲染成方框。 本工具的处理方式: 1. 启动时自动挑选**能显示中文**的等宽字体(优先常见编程字体,但不满足中文字形就跳过; 本机实测选中 `新宋体`),最后兜底逻辑字体 `Monospaced`。 2. 菜单 **视图 → 等宽字体** 可以手动切换,该菜单**只列出本机「等宽 + 含中文字形」的字体**。 3. 选择会写入 `~/.tcp-debug-tool/config.json`,下次启动自动恢复。 4. 若当前字体不含中文字形,启动时会在数据区给出红色警告。 > 额外说明:在中文 Windows 上,Java 的 `getAvailableFontFamilyNames()` 返回的是**本地化字体名**, > 例如 `SimSun` 返回为 `宋体`、`Microsoft YaHei` 返回为 `微软雅黑`。 > 因此判断字体是否存在不能只按英文名匹配(`FontSupport` 已处理这一点)。 > > 另外 CJK 等宽字体的规律是「ASCII 半角等宽 + 中文全角等宽」,中文宽度约为 ASCII 的两倍, > 判断等宽时不能要求两者相等。 如果想用更好看的编程字体,可以安装 [更纱黑体 / Sarasa Mono SC](https://github.com/be5invis/Sarasa-Gothic) 或 [JetBrains Mono NL](https://www.jetbrains.com/lp/mono/) + 中文字体,安装后重启工具即可在菜单中选到。 --- ## 七、内置协议模板 | 模板 | 结构 | | --- | --- | | 裸数据(不封装) | 不做任何封装与拆包,按到达顺序显示 | | 帧头+长度+CRC16 | `AA55` + 2 字节整帧长度(小端) + 负载 + CRC16/MODBUS(小端) | | 帧头+命令+长度+SUM8+帧尾 | `7E` + 命令码 + 1 字节负载长度 + 负载 + SUM8 + `7E` | | Modbus-RTU 风格 | 从站地址 + 功能码 + 负载 + CRC16/MODBUS(小端) | | 文本行(CRLF 结尾) | 以 `0D 0A` 作为分帧符,适合 AT 指令 / 文本协议 | --- ## 八、数据文件 ``` ~/.tcp-debug-tool/ ├── protocols.json 协议模板 ├── commands.json 快捷指令 ├── config.json 界面偏好(主题 / 字体 / 字号) └── logs/ 日志目录(通过「保存日志」导出) ``` 删除该目录即可恢复出厂设置。 --- ## 九、快捷键 | 快捷键 | 功能 | | --- | --- | | `Ctrl + Enter` | 发送当前输入内容 | | `Ctrl + S` | 保存数据日志 | | `Ctrl + C` | 复制全部数据 | | `Ctrl + L` | 清空数据区 | | `Ctrl + P` | 打开协议编辑器 | | `Ctrl + N` | 打开端口扫描 | | `Ctrl + I` | 打开本机网络信息 | | `Ctrl + +` / `Ctrl + -` / `Ctrl + 0` | 放大 / 缩小 / 重置数据区字号 | | `Ctrl + Q` | 退出 | --- ## 十、典型用法 ### 场景一:用同一个程序自测协议(服务端 + 客户端同时跑) 1. 左侧 **TCP 服务端** 标签页:端口填 `8888`,点「启动监听」。 2. 左侧 **TCP 客户端** 标签页:点「连到本机服务端端口」→「连接」。 - 两条通道同时在跑,连接列表里会出现 `服务端 | 127.0.0.1:xxxxx | 已连接`。 3. `工具 → 协议编辑器` 配置好协议(或选内置模板),确认后点「保存并应用」。 4. 右下 **发送目标** 选 `[客户端] 127.0.0.1:8888`,勾选「按协议封装」,填入负载,点「发送」。 5. 右上数据区会看到两条记录: - `>> [客户端] 127.0.0.1:8888 AA 55 0A 00 DE AD BE EF 54 46` - `<< [服务端] 127.0.0.1:53211 #1 AA 55 ... 帧 10B 校验通过 负载=4B` 即:客户端发出 → 服务端收到并按协议拆包成功。切换发送目标为 `[服务端] ...` 即可反向验证。 ### 场景二:调试自研设备协议 1. 协议编辑器新建协议:帧头 `AA55`、长度(整帧长度,2 字节小端)、数据、CRC16/MODBUS。 2. 预览区输入负载,确认生成的帧与设备文档一致。 3. `TCP 客户端` 连接设备地址与端口,勾选「按协议封装」后发送。 4. 数据视图会显示每帧的解析结果与校验状态;校验失败的行标红。 ### 场景三:模拟服务端联调 1. `TCP 服务端` 监听 `0.0.0.0:8888`。 2. 对端设备/程序连接进来,连接列表显示每个客户端。 3. 发送目标选 `[服务端] 全部连接(广播)` 向所有客户端下发,或选具体连接定向回复。 ### 场景四:排查网络连通性 1. `工具 → 端口扫描`,填目标主机与端口范围(如 1–1024),开始扫描。 2. 查看开放端口、常见服务名与 Banner。 3. `工具 → 本机网络信息` 确认本机 IP 与端口占用情况。 --- ## 十一、开发说明 - 编译目标:Java 21(`maven.compiler.release=21`) - 运行依赖:仅 FlatLaf(界面外观),其余全部为标准库 - 扩展新校验算法:在 `ChecksumType` 增加枚举项并实现 `compute` - 扩展新字段类型:在 `FieldType` 增加枚举,并在 `FrameCodec.encode` / `FrameDecoder.parseFrame` 中处理 - 核心逻辑与界面解耦:`core` / `protocol` / `util` 包不依赖任何 Swing 类,可单独复用或做单元测试 (`AppContext` 也不依赖 Swing,可直接在无界面环境下测试多通道逻辑) ### 已验证行为 项目自带的自检覆盖了以下场景(均通过,共 50 项): **协议编解码(8 项)** - CRC-16/MODBUS 标准向量校验(`01 03 00 00 00 01` → `0x0A84`) - 帧头+长度+CRC16 的封包 / 拆包往返 - 半包:逐字节喂入仍能正确重组 - 粘包:两帧连发能拆成两帧 - 帧头前垃圾数据丢弃并重新同步 - 帧头+命令+长度+SUM8+帧尾 模板 - 文本行(CRLF 分帧)模板 - 协议 JSON 序列化 / 反序列化往返 **网络收发(10 项)** - TCP 服务端 ↔ 客户端 双向收发、广播、定向发送、字节统计、断开清理 - UDP 收发 - 端口扫描能发现监听端口 **多通道 + 字体(14 项)** - 自动挑选的等宽字体支持中文 - 能列出本机等宽中文字体;Cascadia Mono 被正确识别为不含中文字形 - 服务端与客户端可同时启动、同时运行 - 客户端 → 服务端 按协议封装发送,服务端拆包成功(校验通过、命令码正确、帧长正确) - 事件带上正确的通道标签;可按会话反查通道 - 服务端广播回发、客户端拆包成功;定向发送生效 - 停止客户端通道不影响服务端通道 - UDP 可与 TCP 通道同时运行;全部停止后无残留 **界面布局(18 项)** - 主窗口在默认尺寸(1420×900)与最小尺寸(1120×720)下,三个连接页签内容均完整可见 - 协议编辑器在默认尺寸(1200×820)与最小尺寸(940×620)下,5 个关键按钮均完整可见, 预览文本区可视高度 ≥ 40px **打包产物** - `target\jre\bin\java.exe -jar target\TcpDebugTool.jar` 用最小化运行时启动成功 - `target\dist\TcpDebugTool\TcpDebugTool.exe` 绿色版启动成功 - 绿色版 exe 与安装包均通过 PE 头校验(x64、GUI 子系统,无控制台窗口) - `packaging\app.ico` 通过 ICO 结构自检(7 个尺寸、BMP 头尺寸一致、数据未越界)