首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 串口通訊開發指南:JavaComm 與 RXTX 實戰詳解

     編輯:Java 相關知識

前言

Java 標準版 (SE) 不包含串口通訊 API,需依賴擴展庫。歷史上有兩大主流方案:**Sun 官方 JavaComm (已停止維護)** 與 **開源 RXTX (社區活躍、跨平台)**。現代開發中,**RXTX (或其衍生版 jSerialComm) 是首選**。本文系統講解串口編程核心流程、JavaComm/RXTX 關鍵差異、安裝坑點、事件驅動模型與最佳實踐代碼。

一、 環境準備與安裝避坑

1. RXTX 安裝 (以 Windows + JDK 8/11/17 為例)

  1. 下載 rxtx-2.2pre2 或更新版 (如 rxtx-2.3、jSerialComm)。
  2. 複製文件到 JDK/JRE 目錄 (需管理員權限):
    • rxtxSerial.dll → %JAVA_HOME%/bin (或 jre/bin)。
    • RXTXcomm.jar → %JAVA_HOME%/lib/ext (或項目 classpath/模塊路徑)。
  3. 關鍵:Windows 下常有多個 JRE (JDK 自帶、公共 JRE、IDE 內置)。**必須安裝到運行程序實際使用的 JRE**。建議在 IDE 運行配置中指定 JDK 路徑,並只維護一份環境。

2. Linux/Unix 權限問題

# 將用戶加入 dialout 組 (擁有 /dev/ttyS* /dev/ttyUSB* 權限)
sudo usermod -a -G dialout $USER
# 重新登錄生效

或臨時授權:sudo chmod 666 /dev/ttyUSB0 (不安全,僅測試用)。

3. 包名差異

庫包名備註
JavaCommjavax.comm.*官方標準,已停更
RXTX 2.0 (兼容層)javax.comm.*作為 JavaComm 實現提供者
RXTX 2.1+ / jSerialCommgnu.io.* / com.fazecast.jSerialComm.*原生 API,功能更全,**推薦新項目直接用**

遷移技巧:若舊代碼用 javax.comm,引入 RXTX 2.0 兼容層即可無縫運行;新項目建議直接用 gnu.io 或 jSerialComm API。

二、 核心編程流程 (以 gnu.io/RXTX 為例)

1. 掃描並獲取端口

import gnu.io.*;
import java.util.Enumeration;

public class SerialPortUtil {
    public static CommPortIdentifier findPort(String targetPortName) {
        Enumeration portEnum = CommPortIdentifier.getPortIdentifiers();
        while (portEnum.hasMoreElements()) {
            CommPortIdentifier pid = portEnum.nextElement();
            if (pid.getPortType() == CommPortIdentifier.PORT_SERIAL 
                && pid.getName().equals(targetPortName)) {
                return pid;
            }
        }
        return null;
    }
}

2. 打開端口並配置參數


public static SerialPort openPort(CommPortIdentifier portId, String appName, int timeoutMs) 
        throws PortInUseException, UnsupportedCommOperationException {
    SerialPort serialPort = (SerialPort) portId.open(appName, timeoutMs);
    
    // 配置參數:波特率、數據位、停止位、校驗位
    serialPort.setSerialPortParams(
        9600,                    // 波特率
        SerialPort.DATABITS_8,   // 數據位 8
        SerialPort.STOPBITS_1,   // 停止位 1
        SerialPort.PARITY_NONE   // 無校驗
    );
    
    // 可選:設置流控 (硬件 RTS/CTS 或軟件 XON/XOFF)
    // serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_RTSCTS_IN | SerialPort.FLOWCONTROL_RTSCTS_OUT);
    
    return serialPort;
}

3. 數據收發 — 兩種模式

模式 A:輪詢/阻塞 I/O (簡單、同步)


// 發送
OutputStream os = serialPort.getOutputStream();
os.write("AT\r\n".getBytes(StandardCharsets.US_ASCII)); // 注意:AT 指令用 \r\n,編碼 US-ASCII
os.flush();

// 接收 (阻塞等待)
InputStream is = serialPort.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.US_ASCII));
String response = reader.readLine(); // 讀取一行 (以 \n 結尾)

缺點:阻塞線程,無法同時處理發送/接收/UI,適合簡單單線程工具。

模式 B:事件驅動異步 I/O (健壯、專業) — **推薦**


public class SerialListener implements SerialPortEventListener {
    private final InputStream is;
    private final OutputStream os;
    private final RingBuffer txBuffer = new RingBuffer(4096); // 發送緩衝區
    private final Object writeLock = new Object();
    
    public SerialListener(SerialPort port) throws IOException {
        this.is = port.getInputStream();
        this.os = port.getOutputStream();
    }
    
    @Override
    public void serialEvent(SerialPortEvent event) {
        switch (event.getEventType()) {
            case SerialPortEvent.DATA_AVAILABLE:
                handleDataAvailable();
                break;
            case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
                handleOutputBufferEmpty();
                break;
            // 其他事件: BI, CD, CTS, DSR, FE, OE, PE, RI ...
        }
    }
    
    private void handleDataAvailable() {
        try {
            byte[] buffer = new byte[1024];
            int len = is.read(buffer);
            if (len > 0) {
                String data = new String(buffer, 0, len, StandardCharsets.US_ASCII);
                // 處理接收數據: 解析協議、回調業務層...
                System.out.println("RX: " + data);
            }
        } catch (IOException e) { /* 處理錯誤 */ }
    }
    
    // 非阻塞發送:主線程寫入環形緩衝區,事件線程實際發送
    public void sendAsync(byte[] data) {
        synchronized (writeLock) {
            txBuffer.write(data);
            // 觸發發送 (若端口空閒)
            // 實際發送邏輯在 handleOutputBufferEmpty 中
        }
    }
    
    private void handleOutputBufferEmpty() {
        synchronized (writeLock) {
            byte[] data = new byte[txBuffer.data()];
            int len = txBuffer.read(data);
            if (len > 0) {
                try {
                    os.write(data, 0, len);
                    os.flush();
                } catch (IOException e) { /* 錯誤處理 */ }
            }
        }
    }
}

// 註冊監聽器
serialPort.addEventListener(new SerialListener(serialPort));
serialPort.notifyOnDataAvailable(true);
serialPort.notifyOnOutputEmpty(true); // 需驅動支持

4. 環形緩衝區 (RingBuffer) — 線程安全異步發送核心

用於主線程 (業務) 與事件線程 (發送) 間解耦傳遞數據,避免阻塞。關鍵操作:

  • write(byte[]):寫入數據,返回實際寫入量 (滿則返回可寫長度)。
  • read(byte[]):讀取數據,返回實際讀取量 (空則返回 0)。
  • 內部用 synchronized 保護頭尾指針,無鎖版本可用 AtomicInteger + 數組 CAS。

三、 進階主題

1. Modem/AT 指令控制

  • JavaComm/RXTX **不封裝高層協議** (如 Hayes AT 指令)。需自行實現:發送 AT\r\n、解析 OK/ERROR/CONNECT、處理回顧、超時重試。
  • 常見流程:初始化 → 設置回顧 ATE0 → 撥號 ATD<號碼>; → 數據傳輸 → 掛斷 ATH。

2. 跨平台部署

  • 原生庫 (.dll/.so/.dylib) 需隨平台打包。
  • 推薦 jSerialComm:純 Java + JNA 加載原生庫,**單 JAR 即可跨平台**,無需手動安裝 DLL/SO,Maven 坐標:com.fazecast:jSerialComm:2.10.0。

3. 常見坑點彙總

現象原因解決
NoClassDefFoundError: javax.comm.CommPortIdentifierJAR 未在 classpath / 模塊路徑錯誤檢查依賴、模塊信息、運行時 JRE
UnsatisfiedLinkError: rxtxSerial.dll原生庫未找到/版本不匹配(32/64位)/多 JRE 衝突確保 dll 在 java.library.path 指向的目錄,統一 64 位
端口打開失敗 PortInUseException端口被其他進程佔用 / 上次未正確關閉檢查串口調試助手、關閉串口 port.close()、重啟機器
收到亂碼編碼不一致 (設備發 ASCII/UTF-8/二進制)確認設備手冊,指定 StandardCharsets.US_ASCII 或二進制處理
發送無響應換行符錯誤 (需 \r\n)、流控未關、波特率不匹配用 \r\n、關閉流控測試、核對波特率

四、 現代替代方案:jSerialComm


// Maven: com.fazecast:jSerialComm:2.10.0
import com.fazecast.jSerialComm.*;

SerialPort[] ports = SerialPort.getCommPorts(); // 獲取所有端口
SerialPort port = ports[0]; // 選擇目標
port.setBaudRate(9600);
port.setNumDataBits(8);
port.setNumStopBits(1);
port.setParity(SerialPort.NO_PARITY);
port.openPort();

// 異步讀取 (內部線程池)
port.addDataListener(new SerialPortDataListener() {
    @Override public int getListeningEvents() { return SerialPort.LISTENING_EVENT_DATA_AVAILABLE; }
    @Override public void serialEvent(SerialPortEvent event) {
        byte[] buffer = new byte[port.bytesAvailable()];
        int numRead = port.readBytes(buffer, buffer.length);
        // 處理數據...
    }
});

// 發送
port.writeBytes("AT\r\n".getBytes(), "AT\r\n".length());
port.closePort();

優勢:無需安裝原生庫、API 現代化、支持 Java 9+ 模塊化、單 JAR 部署、活躍維護。**新項目首選**。

五、 總結

Java 串口開發核心在於:**正確安裝原生庫、掌握事件驅動異步模型、處理編碼與協議細節、妥善管理資源**。從 JavaComm 到 RXTX 再到 jSerialComm,工具在進化,但「掃描端口→打開配置→異步收發→關閉釋放」的核心流程不變。建議新項目直接使用 jSerialComm,老項目維護可用 RXTX 兼容層。深入理解底層原理,才能在工業控制、醫療設備、儀表采集等場景中構建穩定可靠的通訊鏈路。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved