基于JSSC实现多COM端口并发读取与聚合输出技术问询
解决多串口数据聚合时的丢包问题
看来你遇到了多串口数据聚合时的丢包坑——循环轮询端口的方式本质是阻塞式的,你盯着一个端口读数据时,其他端口的 incoming 数据要么被串口缓冲区溢出丢弃,要么因为没及时读取而被后续数据覆盖。结合你用 JSSC 做串口操作的场景,给你几个落地可行的解决方案:
1. 给每个输入串口分配独立异步读取线程
这是最直接的破局思路:给4个4800波特率、1个34800波特率的输入端口各开一个专属读取线程,每个线程只负责监听自己的串口数据。当数据到来时,线程把完整的NEMA帧存入线程安全的队列,再由输出端口的线程统一从队列取数据发送。
核心实现要点:
- 改造
RS232Control类,给每个串口实例绑定一个读取线程,用JSSC自带的SerialPortEventListener做事件驱动监听(比主动轮询高效得多)。 - 必须处理粘包/半包问题:串口数据可能分多次到达,比如一次只收到帧的一部分,所以每个串口要维护一个缓冲区,把收到的字节拼接起来,直到提取出以
$开头、CR/LF结尾的完整NEMA帧,再加入队列。
示例伪代码片段:
public class RS232Control { private SerialPort serialPort; private BlockingQueue<String> dataQueue; private StringBuilder frameBuffer = new StringBuilder(); // 缓存半包数据 public RS232Control(String portName, int baudRate, BlockingQueue<String> queue) throws SerialPortException { this.dataQueue = queue; serialPort = new SerialPort(portName); serialPort.openPort(); serialPort.setParams(baudRate, 8, 1, 0); // 注册串口数据监听事件 serialPort.addEventListener(event -> { if (event.isRXCHAR() && event.getEventValue() > 0) { try { byte[] receivedBytes = serialPort.readBytes(); frameBuffer.append(new String(receivedBytes)); // 提取所有完整的NEMA帧 extractCompleteFrames(); } catch (Exception e) { e.printStackTrace(); } } }, SerialPort.MASK_RXCHAR); } private void extractCompleteFrames() { String bufferContent = frameBuffer.toString(); int endIndex; while ((endIndex = bufferContent.indexOf("\r\n")) != -1) { String completeFrame = bufferContent.substring(0, endIndex + 2); if (completeFrame.startsWith("$")) { // 校验NEMA帧格式 dataQueue.put(completeFrame); } // 保留剩余的半包数据 frameBuffer = new StringBuilder(bufferContent.substring(endIndex + 2)); bufferContent = frameBuffer.toString(); } } }
2. 输出端口用独立线程做队列消费
输出端口(192k波特率)单独开一个线程,从共享的线程安全队列里取数据并发送。因为输入总带宽(4*4800 + 34800 = 54k)远小于输出带宽(192k),理论上不会出现消费跟不上生产的情况,但要做好队列的边界处理。
示例输出线程代码:
public class OutputPortSender implements Runnable { private final RS232Control outputPort; private final BlockingQueue<String> dataQueue; public OutputPortSender(RS232Control outputPort, BlockingQueue<String> dataQueue) { this.outputPort = outputPort; this.dataQueue = dataQueue; } @Override public void run() { while (!Thread.currentThread().isInterrupted()) { try { String frame = dataQueue.take(); // 队列为空时自动阻塞等待 outputPort.sendData(frame.getBytes()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 优雅终止线程 } catch (SerialPortException e) { e.printStackTrace(); } } } }
3. 额外优化建议
- 队列容量限制:给
LinkedBlockingQueue设置固定容量,当队列满时,可选择丢弃最旧的帧(用offer方法,失败时移除队首再尝试),避免内存溢出。 - 资源优雅释放:程序退出时,要关闭所有串口、中断所有线程,比如添加JVM shutdown hook。
- 帧校验增强:除了开头结尾的校验,可选择性验证NEMA帧的校验和(如果你的数据源支持),进一步过滤无效数据。
这种异步事件驱动+线程安全队列的模式,能让每个串口的数据都被及时捕获和处理,彻底解决轮询切换导致的丢包问题。你可以基于现有的RS232Control、NEMA_aggregrator类做改造,替换掉原来的循环轮询逻辑即可。
内容的提问来源于stack exchange,提问作者ian5142
相关产品推荐
相关产品推荐

