简介Serial Port Splitter 是一款面向串口通信场景的系统工具适合设备调试人员和工业控制用户用于解决物理串口数量不足及多程序争用串口数据的问题。软件通过虚拟串口技术创建多个虚拟端口让不同应用同时分享同一物理串口支持读写模式和只读模式读写模式便于双向数据交换只读模式适合单独监控串口流量也适用于多设备数据采集、单设备多任务处理等实际需求。压缩包仅 3 个文件共 4.44MB涵盖 Windows 安装程序、使用说明文档与授权协议文件分别用于软件安装、功能了解和合法使用确认。目前已有 297 人下载学习。获取后可快速在 Windows 中部署并参照说明配置串口分流策略从而更高效地管理物理串口资源、简化设备接入流程。1. Serial Port Splitter一个串口不够用时工程师都在用什么老设备只有一根 RS232 串口线但调试要开串口助手、上位机要收数据采集、日志软件还要记录存档三套程序轮着抢同一个 COM 口谁也开不了第二个。Serial Port Splitter 这个方向就是解决“一路物理串口多个程序要同时读写”的尴尬它把串口的数据流复制成多份分发给多个应用程序或是把多个程序的写入合并回物理口让每个程序都以为自己在独占设备。核心价值是把串口资源从“互斥占用”变成“共享管线”省去改设备、加网关的硬件成本。适合做设备调试、上位机开发、工业数据采集的人尤其适合现场不能动硬件、只能靠软件救场的场景。理解它先分清三种工作模式。2. 串口分线的三种工作模式哪条路最适合你的现场2.1 应用层分线最朴素、也最容易丢包的做法应用层分线是理解整个 Splitter 概念的起点。做法很直白写一个守护进程以独占方式打开物理串口再用程序把读到的数据转发给多个下游程序。下游程序不再直接打开物理口而是打开转发程序提供的 TCP/Socket/管道接口。这个模式最大的优点是灵活——转发层可以加协议解析、帧过滤、数据缓存甚至可以按设备地址把不同数据路由到不同程序。我的几个临时采集脚本就是这么干的一会转发给 InfluxDB 采集器一会转发给 Modbus 轮询程序中间加一个字典变量控制路由规则整套逻辑完全可控。代价也非常明显所有下游程序都依赖这个转发进程活着。进程一退数据全断转发程序读得慢队列就堆积高波特率下丢帧是家常便饭。另一个坑是应用层转发绕过了操作系统串口驱动的时间机制字节到达间隔会被打乱对时序敏感的上位机比如要求字符间隔精确的称重仪表会频繁校验失败。所以应用层分线适合调试期、数据量小、对实时性要求不高的场景不适合直接进产线。2.2 驱动层虚拟串口分线业界最常用的方案真正承担“Serial Port Splitter”这个名称的产品绝大多数走的是驱动层。原理是在操作系统里装一个虚拟串口驱动把一个物理 COM 口或 USB 转串口映射成多个额外的虚拟 COM 口。当物理串口收到数据时驱动在自己的中断处理流程里把数据复制成多份分发给所有已经打开的虚拟口句柄反过来任意一个虚拟口写入的数据也会被驱动合并后送到物理口让设备侧感知不到变化。对应用层来说每个虚拟口就是一个标准串口老程序、老库不用改一行代码这正是企业愿意买商业分线工具的核心原因。驱动层相对应用层的优势不只是隐蔽更重要的是时序还原。驱动在 ring 0 层面拿到的是硬件中断原始数据能保持字节到达的相对间隔上位机按 Modbus 的 3.5 字符间隔判帧时基本不受影响。实际工程里可以理解为驱动层做的是“透明硬件虚拟化”应用层做的是“软件搬运”。常见的驱动层工具包括商业的 Eltima 分线器、Virtual Serial Port 类软件以及开源社区基于 com0com 改造的分线驱动。选型时先看驱动是否签名再看是否支持广播模式后面避坑章节会细说。2.3 合并回流模式把多路写入汇成一路分线这个词容易让人只想到“一对多”但实际现场还有反向需求多台工控机要往同一台仪表发指令仪表也只有一路串口。这时候 Splitter 需要工作在“合并模式”。多个虚拟口各自接收应用写入驱动按到达顺序把它们合并写入物理串口。表面看很简单实际是三个模式里最容易出事的两个程序同时写指令会在物理口上交错如果协议没有“一问一答”机制设备会直接忽略。另一个隐患是同一个虚拟口被多个线程同时写时驱动底层没有原子保护字节会互相穿插造成设备收到非法帧。所以合并模式的适用面很窄指令频率低、协议有明确应答和重试、总流量不超过物理口波特率能力的 40%。我的习惯是只要现场有两个程序要主动写设备宁愿多加一台串口服务器走 IP 分线也不在产品上用合并模式硬扛。如果你确实只能用 Splitter务必在应用层做写锁把同一时刻的写者限制为一个。3. Windows 上落地用 com0com 和转发脚本五分钟跑通分线3.1 用 com0com 建虚拟串口对命令与参数Windows 下最常见的免费路线是 com0com它的核心功能是创建成对的虚拟串口一个串口对里有两个端点写入 A 端的数据从 B 端读出来写入 B 端的数据从 A 端读出来。第一次接触很容易误以为它能直接把物理口和虚拟口绑定实际上它只管虚拟口需要一个转发脚本承担物理口与虚拟口之间的搬运。先安装驱动再在管理员命令行里创建端口对com0com --install com0com --create CNCA0,CNCB0 com0com --change CNCA0,PortNameCOM4 com0com --change CNCB0,PortNameCOM5参数含义很直白CNCA0、CNCB0是驱动内部端点的全局名PortName指定对外暴露的 COM 端口号。创建时务必确认 COM4、COM5 没有被系统占用否则驱动会创建失败但命令不报错只在设备管理器里看到黄色感叹号。--install只需要执行一次重启后驱动会自动加载端口对也会保留——这是它比纯软件方案省心的地方。端口对建好后写一个 Python 搬运脚本把物理口 COM3 的读数据搬到 COM4import serial # 物理串口和虚拟端点分别打开 physical serial.Serial(COM3, 9600, timeout0.1) virtual_tx serial.Serial(COM4, 9600, timeout0.1) while True: # 从物理口读原始字节原样搬到虚拟口 data physical.read(4096) if data: virtual_tx.write(data)这段代码是“读方向”的最小闭环设备发来的数据进了虚拟口业务程序只要打开 COM5就能通过驱动内部配对读到这份数据。同时需要在另一个线程里把业务程序写到 COM5 的指令反向搬回 COM4再到物理口。实际工程里我一般把串口对象包成双向类加上异常重连否则只要一个打开失败整个链路就僵死。注意timeout0.1意味着最多延迟 100 毫秒才把数据从驱动缓存里读出来想追实时性可以调到 0.01但会提高 CPU 占用。3.2 用商业分线工具把物理 COM3 拆成 COM4/COM5如果你不想自己维护搬运脚本或者现场有非技术同事要操作商业工具是更稳的选择。以典型的 Serial Port Splitter 图形工具为例安装后界面里会列出当前系统所有串口选择物理 COM3再点一下 Split 操作工具自动生成 COM4、COM5 两个虚拟口。这两个虚拟口由工具自带的驱动管理业务程序直接打开任意一个虚拟口就能同时收物理口数据。商业工具通常还会提供两个可切换模式只读模式——所有虚拟口只能收不能写物理口读写模式——第一个打开虚拟口的程序有写权限其余只能读。这里要特别提醒选型细节买工具前先确认它是否支持“广播”能力。有些便宜的分线工具生成的多个虚拟口实际只把数据发给“最后打开”的那个程序之前打开的程序收不到这本质上是单播转发不符合共享语义。测试方法很简单把物理口接一个一直发数据的设备先后打开 COM4、COM5 两个串口助手如果两边数据同时滚动才是真正的广播分线。数据不回滚的话这个工具的设计就不满足需求趁早退货。商业工具还会引入一个虚拟串口驱动安装后设备管理器里会出现新的串口设备节点。装完必须重启一次系统否则驱动态缓存的端口信息可能不生效这是 Windows 内核串口驱动常见的“装机第一步必须重启”的老规矩别省。3.3 双开监听测试确认分线真的在按预期工作分线工具装好、端口配好不代表链路就是通的我习惯按下面三步做一次完整验收。先做回环测试物理口上一端插一个自环头把 2、3 脚短接或者直接接一个真实设备让它在固定周期回发一帧数据。再用两个串口助手分别打开 COM4 和 COM5观察两边收到的数据是否逐字节一致滚动速度是否同步。第二步做写入测试先在 COM4 的助手里发一条写指令看设备是否有动作再在 COM5 的助手里发同样指令确认第二个程序也能写。第三步做并发冲击让两个助手同时发送大量数据看设备端是否有卡顿、乱码。这个测试同时隐含了一个关键参数Windows 驱动默认的串口接收缓存是 4096 字节。如果设备以高波特率持续发数据而业务程序读得不及时缓冲满了之后新数据会被丢弃现象就是分线后丢失尾帧。对于 115200 波特率、10ms 间隔的数据流这个缓存通常够用如果跑 921600 或者有突发大帧就要在设备管理器里把“接收缓冲区”拉高到 8192 或 16384否则后面怎么调都白搭。4. Linux 上用 socat 与自写脚本实现串口分线4.1 socat 建 pty 端点先造虚拟串口再谈分发Linux 下没有开箱即用的分线工具常规路由是 socat 提供虚拟串口端点自己写守护进程做数据搬运。先用 socat 生成一个伪终端pty把它的地址固定到一个可预期的路径socat -d -d pty,raw,echo0,link/tmp/ttyV0,perm0666 tcp-listen:4100,reuseaddr,fork这条命令的逻辑是在/tmp/ttyV0创建一个对外可见的串口符号链接同时监听 4100 端口。任何程序打开/tmp/ttyV0就能读写串口而 TCP 客户端连到 4100 端口后驱动的字节流和 TCP 流相互桥接。参数里raw表示不经过终端行规则处理echo0防止收到数据再回显perm0666给足权限避免权限坑fork允许每个 TCP 连接受一个独立处理进程。如果你现场有多台机器要共享同一路串口这个 pty 端点就相当于一个网络化的虚拟 COM 口。但要注意,socat 的单条命令是“一对一”桥接不是广播。想要一对多常见做法是同一个物理串口的数据源由你的守护进程统一读取再分别写入多个ttyV端点这样每个端点各自拥有独立的数据副本不依赖 socat 的转发语义。这个组合比单独跑多个 socat 实例更稳因为多个 socat 会互相竞争打开物理口后启动的进程会直接报Device or resource busy。4.2 Python 多路分发器线程加队列把一路读到 N 路Linux 下我一般直接用 Python 写一个常驻分发器核心只有三步一个线程读物理串口把原始数据放进广播队列N 个写线程各消费同一个队列把数据写入各自的 pty 端点反过来业务程序写入 pty 的数据也要搬回物理口这一步走独立队列但只有一个写者能发送避免指令交错。最小可运行版本如下#!/usr/bin/env python3 import serial import threading import queue import fcntl PHYS_PORT /dev/ttyUSB0 BAUD 115200 VIRT_PORTS [/tmp/ttyV0, /tmp/ttyV1] # 先用 socat 建好 q_out queue.Queue(maxsize4096) # 物理口 - 虚拟口广播队列 def read_physical(): ser serial.Serial(PHYS_PORT, BAUD, timeout0.05) while True: data ser.read(1024) if data: q_out.put(data) def broadcast_to_virtual(path): # 打开虚拟串口不阻塞等待数据不断消费广播队列 while True: data q_out.get() try: fd os.open(path, os.O_WRONLY | os.O_NOCTTY) os.write(fd, data) os.close(fd) except OSError: pass # 下游程序没打开时静默丢弃 threading.Thread(targetread_physical, daemonTrue).start() for v in VIRT_PORTS: threading.Thread(targetbroadcast_to_virtual, args(v,), daemonTrue).start() threading.Event().wait()这段代码的关键在队列设计maxsize4096是一个缓冲上限物理口瞬时涌入超过 4096 条数据时入队动作会阻塞读线程——这比无限队列更安全至少不会内存爆炸。写线程里每次打开、写入、关闭虚拟口是比较粗暴的做法好处是下游程序反复开关端口不会引发句柄泄露代价是频繁 open/close 有开销适合下游程序数量少、数据频率不高的现场。如果你要低延迟就把虚拟口的 fd 常驻用select/epoll监控可写状态。有人会把这段逻辑塞进 systemd 服务里开机自启这是对的但注意不要把多个虚拟口线程的写入放在同一个队列消费者里否则某个下游处理慢就会拖累所有下游。每个虚拟口一个独立消费者线程才能做到单个下游卡死不影响其他路这是分线器最容易踩的并发设计坑之一。4.3 必调参数波特率、字符间隔、缓冲上限参数推荐值设置理由波特率与物理设备一致分线器不改协议误设则整条链路乱码物理口 timeout0.05 秒兼顾实时性与 CPU 占用低波特率可放到 0.1广播队列 maxsize4096防内存溢出数据突发大时按峰值提高虚拟口写入模式O_NOCTTY防止虚拟口变成进程控制终端导致信号中断系统串口 low_latency开启setserial /dev/ttyUSB0 low_latency减少调度延迟额外提醒串口分线器是字节搬运工不要在里面做协议解析、不要试图按帧缓存。帧边界应该由上位机程序自己处理你只保证“进多少、出多少、顺序不变”。这个原则在 Windows 和 Linux 通用很多人分线后乱码不是线的问题是自己在分线层加了粘包拆包逻辑。5. 避坑Serial Port Splitter 常见问题与排查5.1 程序 A 能收、程序 B 收不到广播模式没开现象用同一个分线器工具建了两个虚拟口先打开的程序 A 数据正常后打开的程序 B 界面永远空白。原因相当一部分分线工具的驱动实现不是数据广播而是“最后打开的句柄接管”。它内部维护了一个活跃接收者列表新句柄打开后数据只发给最后一个句柄。这是很多商业工具为了省驱动资源刻意为之的设计不会在界面标注。解决回到选型时提到的广播模式测试用两个串口助手同时打开验证。如果工具确认不支持广播且不允许升级换用 com0com 自写转发脚本让脚本用独立线程向每个虚拟口单独write()这是开源路线里最可控的方案。5.2 虚拟口打开失败权限、占用、命名空间现象Linux 下打开/tmp/ttyV0报Permission deniedWindows 下打开 COM10 报“系统找不到指定的文件”。原因Linux 侧是用户不在dialout组或 udev 规则没给设备节点权限Windows 侧则是 COM10 以上的高级端口号在某些精简版驱动里没有注册命名空间。解决Linux 把用户加入组并完善 udev 规则后重插设备Windows 在设备管理器里把虚拟口映射强制改到 COM1~COM8 范围或者用mode COM10命令确认系统是否识别。检查端口是否被占用用mode最直接会列出当前已分配的所有 COM 口。5.3 装完工具后系统不稳定驱动签名问题现象装了某款分线工具重启后虚拟口创建失败设备管理器里分线驱动设备显示黄色感叹号甚至系统频繁蓝屏。原因Windows 10/11 对内核态串口驱动有强制签名要求未签名的分线驱动会被系统直接禁用但驱动初始化逻辑已经被加载导致资源冲突。解决生产机器不要尝试关闭签名强制加载这是让整个系统变成黑匣子的风险动作。换用有微软 WHQL 签名的商业版本或者直接走 com0com——它的驱动在国内社区验证非常广泛签名完备。这是我在这个项目上翻车最狠的一次贪便宜装了个绿色版结果整机蓝屏现场设备全断。5.4 数据乱码、半帧字节间隔被打破现象物理口设备数据正常但业务程序通过虚拟口收到的帧偶尔乱码或者 Modbus 报文在分线后频繁报 CRC 错误。原因分线器把数据从物理口读出来再写入虚拟口两个动作之间存在时间差。如果业务程序按“字符间隔超过 3.5 字符时间”判帧这个时间差会让一帧数据被切开上位机就会把半帧误判为完整帧。解决优先选择驱动层分线它在中断上下文里复制数据时间间隔保持得最好。如果只能用应用层脚本把读循环 timeout 调到 10ms 以内并且不要在分线代码里做time.sleep(),数据一律走队列直通。乱码问题 80% 是分线层引入延迟不是波特率错误。5.5 业务程序重启后物理口数据消失现象分线器服务还在虚拟口也在但程序重启后物理口设备不再响应数据全断。原因部分分线工具把“虚拟口连接数量”当成了物理口共享的生命周期。当最后一个虚拟口被关闭业务程序退出时驱动认为没有接收者主动把物理口也关闭掉设备侧看到 DTR/RTS 变化自然就不发了。解决把物理口的两根流控线强制拉高保持设备在线。在自写脚本里打开物理口时设置dsrdtrFalse, rtsctsFalse并在初始化后手动把 RTS 置为 True。如果是商业工具找设置里的“保持物理口连接”开关没有就换工具。6. 进阶用数据镜像与帧过滤把分线做成可观测管线6.1 按帧头过滤把调试流量和业务流量分开很多现场的分线需求不只是复制数据还想让不同下游看到不同数据流。我一般会在分发器里加一层按帧头过滤比如设备周期上报的标定帧帧头 0xAA 0x55送给标定软件运行帧帧头 0x68送给监控后台。过滤逻辑放在分发器里能减少下游无效处理但别做完整拆包只做头部匹配和粗粒度裁剪避免误杀变长帧def dispatch_by_header(data): if data.startswith(b\xaa\x55): q_calibration.put(data) # 标定软件队列 elif data.startswith(b\x68): q_scada.put(data) # 监控后台队列 else: q_debug.put(data) # 调试镜像队列这个函数每次读到一个物理口数据块就调用一次数据块边界不等于业务帧边界所以下游拿到后仍要做完整组帧。过滤的价值在于降低下游程序的工作量不是替代它。6.2 把分线器做成 systemd 服务掉线自动重连分发器是现场链路的地基不能跟着会话退出。我习惯把它固定成系统服务加入自动重启参数并且关闭标准输入输出避免被挂起[Unit] DescriptionSerial Port Splitter Service Afternetwork.target [Service] ExecStart/usr/local/bin/serial_splitter.py Restartalways RestartSec3 StandardOutputjournal [Install] WantedBymulti-user.targetRestartalways配合RestartSec3防止脚本崩溃后拖垮整条数据链路日志走 journal排查时用journalctl -u serial-splitter直接看。现场跑了一年最大的教训是不要试图在分线器里处理所有协议它只是搬运工也不要相信任何不经过双开测试的分线工具。做这个方向先把广播模式验证清楚再谈并发和调优永远别让一个临时脚本直接上产线。希望帮到你。本文还有配套的精品资源点击获取