一、这些场景你大概率遇到过
写嵌入式固件的朋友,这三件事应该没少折腾:
- 串口被一个程序独占。串口助手开着收数据,上位机脚本就打不开同一个 COM 口;想两边同时看,只能开一对虚拟串口,或者来回关开关开。
- 设备热拔插就要全部重连。USB 线一碰、设备重新枚举,所有程序集体断线。更坑的是有的工具断线后"窗口还开着、数据早不来了",你盯着空屏排查十分钟,其实是旧句柄在设备抖动后已经失效了。
- 想在浏览器或脚本里摸串口。无头浏览器拿不到 Web Serial 授权,网页上位机连不上设备;自动化测试想用脚本假装一台设备跟被测系统对发字节,也缺一条顺手的管道。
SerialHub 就是为这些场景做的:Apache-2.0 开源、完全免费,Rust 写的跨平台单文件,管理台网页直接内嵌在二进制里,下载解压就能用。它把串口变成一条 WebSocket 字节管道,浏览器和脚本直接读写原始字节,所有数据全程留在本机。
二、它是什么
一句话:每块串口对应一座"桥",桥的另一头是一个 WebSocket 数据网址;所有桥由一个固定的网页管理台统一管理。
COM1 ══ 桥1 ══ ws://127.0.0.1:8101/ws ══ 网页上位机(自带协议栈)
COM3 ══ 桥2 ══ ws://127.0.0.1:8102/ws ══ Python 测试脚本
│
管理台 http://127.0.0.1:8080
(建桥 / 启停 / 改配 / 删除 / 速率统计,重启自动恢复)
数据端口由程序自动分配(默认从 8101 起),桥的生命周期内不变。两个网址记住一句话就够:管理台网址管程序,数据网址管字节,程序接入永远连数据网址。
动手做这个工具之前,我把现成方案用了一圈:有的在桥里做 Modbus、对外只吐 JSON;有的用 JSON+base64 包帧,页面侧协议栈得推倒重来;有的明确单客户端、没有广播;还有的停更好几年了。SerialHub 的取舍是反过来的:桥里不做任何协议语义,只搬运字节。为什么这么选,第五部分细说。
三、快速上手
从 GitHub Releases 下载对应平台的压缩包,解压就是全部:Windows x86_64 的 zip 只有约 1.6 MB,不用装运行时、不用注册账号,双击就能跑。想从源码编译,仓库里 cargo install --path . 一条命令的事。
命令行三步(旧单桥参数 = 自动建一座桥并启动,命令与注释原样来自 README):
bash
# 列出本机串口
serialhub --list-ports
# 一行起桥: 串口 COM1, 115200, 8N2; 管理台地址 127.0.0.1:8080
serialhub --port COM1 --baud 115200 --config 8N2 --addr 127.0.0.1:8080
# 浏览器打开 http://127.0.0.1:8080 ------ 管理台即用
# 程序接入: ws://127.0.0.1:8081/ws (本桥数据端点, 纯二进制; 裸地址 ws://127.0.0.1:8081 亦可)
它有两种形态:
- 桌面客户端 :Windows 双击
serialhub.exe就是原生窗口 + 系统托盘,关窗退到后台,桥继续收发; - 纯命令行 :脚本和 CI 场景加
--headless,无窗口无托盘:
bash
serialhub --headless --port COM1 # 纯命令行模式 (脚本/CI)
串口参数是全覆盖的:波特率 110~2,000,000,数据位 7/8,校验 N/E/O,停止位 1/2,流控 none/rtscts/xonxoff。CLI 和管理台完全对等,界面上每个配置项在命令行都有对应物,管理台里还能一键复制与当前配置等价的启动命令。
四、多桥管理台:所有桥一个网页管完

- 多桥并存:每行一座桥,状态徽章(运行中 / 连接中 / 重连中 / 已停止)、连接数、运行时长、最近错误一眼看完;
- 可视化:每桥一张流程图(串口 ⇄ 桥 ⇄ 数据网址),有数据流动线路就点亮;RX/TX 速率火花线和累计字节数每秒刷新;

- 新建桥零门槛:选串口、设波特率就能跑,数据端口自动预填下一个空闲端口;表单记住上次的串口和波特率,连建多座桥很快;

- 只读旁看:点桥名打开抽屉,「串口数据」页签只读查看串口原始流,ASCII / HEX 随时切换,最多保留 5000 行;

- 重启自动恢复:所有桥的配置持久化在 fleet.json 里,程序重启自动恢复全部桥,不用每天早上重新建一遍;
- 自动重连可开关:每座桥独立控制(默认开),运行中改开关即时生效;
- 主题插件:内置浅色 / 深色 / 示例·奥利奥 / Windows 95 四套主题,exe 旁 themes/ 文件夹放一个 .css 就是一套新主题,切换不刷新页面;
- 托盘常驻:图标颜色跟着桥状态变(绿=运行中 / 琥珀=重连中 / 灰=已停止)。
命令行形态也有截图可看:

五、关键实现思路
讲三个设计决策,素材来自仓库里的架构决策记录(ADR),想看原文的去仓库翻 decisions.md。
1. 数据面只做管道:纯原始二进制帧
串口桥最容易做偏的地方,是"顺手"帮你解析协议。我调研时见过的反例就是 JSON+base64 包帧、桥内做 Modbus:桥一旦做语义,页面侧协议栈就得跟着它的帧格式重写,接入那天就是推翻自家代码的一天。SerialHub 把定位钉死在"管道":/ws 数据通道只传原始二进制帧 ,双向;组帧、解析、校验全部留给页面和脚本自己的协议栈。配置与状态走 /api/*(JSON),控制面与数据面彻底分离,管理台页面挂了也不影响字节流动。
这个设计换来两条使用前必须心里有数的规则(手册「程序接入」章原文要点):
- WebSocket 消息边界 ≠ 串口帧边界:设备一次 write 可能被拆成多条消息到达,多次 write 也可能合并成一条;请按自己的协议组帧,别依赖消息边界;
- 慢客户端丢旧帧:下行缓冲 1024 条,消费跟不上时丢最旧帧保连接,不反压、不踢人。高波特率加慢消费端的组合,请确保消费端跟得上。
2. 多客户端:下行广播,上行 FIFO 单写者
串口本质是单用户设备,多个客户端同时写就是灾难。SerialHub 的仲裁很朴素:
- 下行(串口 → 客户端)用广播通道:收到的字节推给所有客户端,测试脚本和网页可以同时围观同一条设备流;
- 上行(客户端 → 串口)单写者:所有客户端的帧进一个 mpsc 队列,由唯一写者按 FIFO 顺序写入串口,先到先发,文档明示不做客户端优先级。
对比有的实现让每个会话各自轮询同一端口、互相抢字节,这条路至少保证了三件事:不抢字节、不乱序、不 panic。
3. 自动重连:独立监督任务,重试全程可见
这个功能来自真实的教训:CH340 抖动一次,旧串口句柄可能永久失效,"桥活着却不转发"是极难排查的静默故障。所以 SerialHub 没有把重开写成 try-catch 兜底,而是做成一等公民:
- 一个独立于数据面的监督任务按 1 秒节奏重试,永不阻塞数据转发;
- 状态机
Closed → Opening → Open → Retry全程在状态接口可见; - 管理台明示「串口已断开,正在自动重连 (第 n 次)...」,第几次都数给你看。
对你的脚本来说什么都没发生:期间 WebSocket 连接保持着,设备插回来数秒内恢复,数据自动续传,客户端零动作。每座桥也可以独立关掉这个开关(关了掉线就直接转「已停止」)。
顺带一组仓库更新日志里记录的自测基线(v1.0.0,仅供参考):双向吞吐 7.3 / 10.4 Mbps,串口收到字节到 WebSocket 客户端收到的延迟 p95 约 0.61 ms。工程侧仓库里有 83 条 Rust 单元测试和 63 条跑在 COM1↔COM2 虚拟串口对上的集成测试,回归是有保障的。
六、客户端接入示例
Python(摘自 examples/python-client.py,先 pip install websockets)
python
DATA_URL = "ws://127.0.0.1:8090/ws" # 改成你的桥数据网址
async def main():
async with websockets.connect(DATA_URL) as ws:
async def rx(): # 下行: 串口收到的原始字节, 打 HEX
async for data in ws:
print("收", data.hex(" "))
async def tx(): # 上行: 每秒一行 "ping" (UTF-8 原始字节)
while True:
await asyncio.sleep(1)
await ws.send(b"ping\n")
await asyncio.gather(rx(), tx())
浏览器(摘自 examples/web-client.html,零依赖单文件,双击即用)
js
ws = new WebSocket($('url').value); // 数据端点: ws://<桥网址>/ws
ws.binaryType = 'arraybuffer'; // 铁律 1: 拿到的就是原始字节
ws.onmessage = e => log('收', new Uint8Array(e.data)); // 铁律 2: 只展示, 不组帧
发送侧:
js
ws.send(new TextEncoder().encode(s)); // 二进制帧 → 桥串行写入串口
两个示例都在仓库 examples/ 目录,拿来改改就能用。注意管理台端口 ≠ 数据端口,真实数据网址以桥卡片或启动输出为准。
七、限制与诚实声明
- 无 TLS、无鉴权(v1.x 现状) :任何连得上管理台或数据网址的人都能读写字节、改配置。只在本机或可信内网使用,勿暴露公网;需要远程访问请自己套 SSH 隧道。
- macOS 安装包未签名:首次打开若被系统拦下,右键点文件选「打开」,再点一次「打开」即可放行。
- Linux/macOS 桌面形态 需要 webkit2gtk / WKWebView 运行库;服务器脚本场景直接用
--headless纯命令行。 - Windows 发布版没有控制台黑窗 (双击即用),代价是
--headless模式 stdout 不回显到终端------脚本判活请轮询/api/status。 - 它不是串口终端仿真器:不做 VT100/xterm 终端;管理台「串口数据」只能看不能发,发数据是客户端程序(网页/脚本)的职责。
八、链接
- 官网(下载 + 三步上手):https://misakamikoto128.github.io/serialhub/
- GitHub 仓库(源码 + Releases + 手册):https://github.com/MisakaMikoto128/serialhub
SerialHub 按 Apache-2.0 完全开源,没有付费墙,也没有功能锁定。如果你也被"一个串口,全家排队"折磨过,下载试一试,顺手去仓库点个 Star,就是对它最好的支持;用得不顺手,欢迎直接提 issue。