在經歷一年多的 RXTX 串口項目開發後,我將常見問題與解決方案整理於此,供後續開發者參考。RXTX 是基於 Sun 早期 Comm API 擴展的開源庫,突破了原版僅支援 Windows 的限制,實現了 Windows、Linux、Mac OS X 及 Solaris 的全平台串口/並口操作能力。
官方發布頁:RXTX Download
RXTXcomm.jar 複製至 %JAVA_HOME%\jre\lib\extrxtxSerial.dll 複製至 %JAVA_HOME%\jre\bin官方提供安裝包,但手動配置易失敗。建議直接使用社區整合的安裝程式:
下載 RXTX_Tiger.pkg 一鍵完成環境佈署。
RXTXcomm.jar → $JAVA_HOME/jre/lib/extlibrxtxSerial.so → $JAVA_HOME/jre/lib/[架構目錄] (如 i386)chmod a+r librxtxSerial.so 賦予所有用戶讀取權限步驟同 Linux,同樣需釋放 .so 文件權限。
以下類別封裝了串口的開啟、讀寫、事件監聽與資源釋放,採用 SerialPortEventListener 異步接收資料,並提供同步寫入鎖保證線程安全。
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TooManyListenersException;
import gnu.io.*;
public class SerialComm implements SerialPortEventListener, Runnable {
public final static String PORT_OWNER = "MonitorApp";
private boolean isOpen, isStart, isSave, isPrint;
private Thread readThread;
private String portName, portAddress, dataProtocol;
private CommPortIdentifier portId;
private SerialPort serialPort;
private DataInputStream inputStream;
private OutputStream outputStream;
private SimpleDateFormat formatter;
private final Object readWriteLock = new Object();
public SerialComm() {
isOpen = isStart = false;
isSave = true; isPrint = false;
formatter = new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss,SSS]");
portName = "COM1"; portAddress = "LOCAL"; dataProtocol = "Gooseli";
}
public void init(String port, String protocol) throws Exception {
portName = port; portAddress = portName; dataProtocol = protocol; init();
}
public void init(String port, String address, String protocol) throws Exception {
portName = port; portAddress = address; dataProtocol = protocol; init();
}
public void init() throws Exception {
if (isOpen) close();
try {
portId = CommPortIdentifier.getPortIdentifier(portName);
serialPort = (SerialPort) portId.open(PORT_OWNER, 2000);
inputStream = new DataInputStream(serialPort.getInputStream());
outputStream = serialPort.getOutputStream();
isOpen = true;
} catch (NoSuchPortException | PortInUseException ex) {
throw new Exception(ex.toString());
}
}
public void start() throws Exception {
if (!isOpen) throw new Exception(portName + " has not been opened.");
try {
readThread = new Thread(this); readThread.start();
serialPort.notifyOnDataAvailable(true);
serialPort.addEventListener(this);
isStart = true;
} catch (TooManyListenersException ex) {
throw new Exception(ex.toString());
}
}
@Override public void run() {
String at = "at^hcmgr=1\r";
String cmd = at + (char) Integer.parseInt("1a", 16) + "z";
writeComm(cmd);
isPrint = true;
}
public void stop() {
if (isStart) {
serialPort.notifyOnDataAvailable(false);
serialPort.removeEventListener();
isStart = false;
}
}
public void close() {
stop();
if (isOpen) {
try {
inputStream.close(); outputStream.close(); serialPort.close();
isOpen = false;
} catch (IOException ignored) {}
}
}
@Override public void serialEvent(SerialPortEvent event) {
if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE) readComm();
}
private void readComm() {
StringBuilder buf = new StringBuilder();
try (InputStreamReader isr = new InputStreamReader(inputStream, "UTF-8")) {
int c; while ((c = isr.read()) != -1) buf.append((char) c);
String data = buf.toString().trim();
String ts = formatter.format(new Date());
// 此處可對 data 進行協議解析、持久化或回調處理
System.out.println(ts + " RECV: " + data);
} catch (IOException ex) { ex.printStackTrace(); }
}
public void writeComm(String outString) {
synchronized (readWriteLock) {
try { outputStream.write(outString.getBytes()); }
catch (IOException ex) { ex.printStackTrace(); }
}
}
public static void main(String[] args) {
SerialComm sc = new SerialComm();
try {
sc.init("COM3", "Air"); // Windows 測試
// sc.init("/dev/ttyUSB0", "Air"); // Linux 測試
sc.start();
} catch (Exception ex) { ex.printStackTrace(); }
}
}
COMx,Linux/Mac 為 /dev/ttyUSBx 或 /dev/ttySx。serialPort.setSerialPortParams(...)。UTF-8,若設備回傳 GBK/Big5 需對應調整。close(),避免端口被鎖死。