# 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 头尺寸一致、数据未越界)