詳細解讀Java的串列埠程式設計

NO IMAGE
1 Star2 Stars3 Stars4 Stars5 Stars 給文章打分!
Loading...

常見問題

JavaComm 和 RxTX 安裝時有一些與眾不同的地方。強烈建議按照安裝說明一點點的安裝。如果安裝說明要求一個jar檔案或一個共享庫必須在某一特定的資料夾下,那這就意味著需要嚴肅對待。如果說明要求一個特定的檔案或裝置需要擁有一個特定的所有權或訪問權,這也意味著需要嚴肅處理。很多安裝問題都只是因為沒有按照安裝說明要求的去做而引起的。

特別要注意的是一些版本的JavaComm會帶有兩個安裝說明。一個用於java 1.2及以後的版本,一個用於java 1.1版本。使用錯誤的安裝說明會導致不能工作的安裝結果。另一方面,TxTx的一些版本/構件/包會包含不完全的說明。在這種情況下需要獲得相關的RxTx釋出的原始碼,它包含了完整的安裝說明。

另外要注意Windows的Jdk安裝程式會包含三個java虛擬機器,因此會有三個擴充套件資料夾。

    一個作為JDK的組成部分。
    一個作為與執行JDK工具的JDK一起的私有JRE的一部分。
    一個作為與執行應用程式的JDK一起的公共JRE的一部分。

更有甚者甚至會有第4個jre,它存在於\Windows的目錄結構中。 JavaComm應該作為擴充套件被安裝到JDK和所有公共JRE中。

Webstart

   JavaComm

關於JavaComm和RxTx的一個常見問題是它們不支援通過Java WebStart進行安裝:JavaComm的臭名昭著是因為需要將一個稱為javax.comm.properties的檔案放到JDK lib目錄下,而這是不能通過Java WebStart完成的。很令人沮喪的是,對於該檔案的需要是JavaComm中一些不必要的設計/決定所導致的惡果,而JavaComm的設計者們可以很容易地避免這種事情。Sun固執地拒絕修正這個錯誤,他們強調這個機制是必不可少的。他們是在睜著眼說瞎話,特別是當提及JavaComm時,因為Java在很長一段時間內擁有一個專門用於此類意圖的服務提供者架構。

這個屬性檔案中的內容只有一行,即提供本地驅動的java類名稱。


driver=com.sun.comm.Win32Driver

以下是一個可以通過Web Start部署JavaComm而無視那個傷腦筋的屬性檔案的技巧。但它有嚴重的缺陷,並且在部署較新的JavaComm時可能會失敗-如果Sun會做一個新版本的話。

首先,關閉安全管理器(security manager)。Sun的一些蠢貨程式設計師覺得一遍又一遍地檢查可怕的javax.comm.properties檔案的存在是很酷的事情,特別是當它最初已經被載入完成之後。這只是單純地檢查檔案是否存在而不為其他原因。


System.setSecurityManager(null);

然後,當初始化JavaComm API時,手動初始化驅動。


String driverName = "com.sun.comm.Win32Driver"; // or get as a JNLP property
CommDriver commDriver = (CommDriver)Class.forName(driverName).newInstance();
commDriver.initialize();

RxTx

RxTx在某些平臺上需要改變串列埠裝置的所有權和訪問權。這也是無法通過WebStart完成的事。

在程式啟動時你應該要求使用者作為超級使用者來執行必要的設定。特別的,RxTx有一個模式匹配演算法來驗證“合法”的串列埠裝置名。當某人想使用不標準的裝置,例如USB轉串列埠轉換器(USB-to-serial converter)時,這常會把事情弄砸。這個機制可以被系統屬性遮蔽掉。詳情參照RxTx的安裝說明。
JavaComm API
引言

Java官方串列埠通訊API是JavaComm API。這個API不是Java 2標準版的組成部分,因而此API的實現需要單獨下載。不幸的是,JavaComm沒有獲得Sun足夠的重視,實際的維護時間也不是很長。Sun只是偶爾修復一些不重要的bug,卻沒有做過一些早已過期的重要檢修。

本節闡述JavaComm API的基本操作。所提供的原始碼保持簡化以展示重點,在實際應用中使用需要完善。

這章的原始碼並不是唯一可用的示例程式碼。很多例子中都包含JavaComm下載。這些例子幾乎包括比其API文件更多的關於如何使用它的資訊。不幸的是,Sun公司沒有任何真正的教程或一些說明文件。因此,要理解這個API的機制,學習這些示例程式碼是值得的,也仍需要學習這個API文件。但最好的方法是,學習這些例子並運用它們。由於缺少易用的應用以及理解這些API的程式設計模型有困難,API通常備受抨擊。相比其名氣和功能,這個API更好,但僅此而已。

該API採用回撥機制通知程式設計師有新資料到來。這也是學習這一機制的好主意,而不是依賴詢問埠。不像Java中的其他回撥介面(如:在圖形介面),這個介面只允許一個監聽器監聽事件。如果多個監聽器請求監聽幾個事件,主監聽器必須通過分派資訊給其他二級監聽器的方式來實現。

下載與安裝

下載

Sun公司的JavaComm網頁指向下載地址。在這個地址下,Sun當前(2007年)提供了支援Solaris/SPARC、Solaris/x86已經Linux x86的JavaComm 3.0版本。下載需要註冊一個Sun公司的賬戶。下載頁提供了註冊頁的連結。註冊的目的並不清楚。在為註冊時,使用者可下載JDK和JREs,但對於這幾乎微不足道的JavaComm,Sun公司在軟體分銷和出口方面卻援引法律條文和政府限制。

官方已不再提供JavaComm的Windows版本,並且Sun已經違背了他們自己的產品死亡策略-不能在Java產品集中下載。但仍可以從這下載2.0的Windows版本(javacom 2.0).
 

安裝

按照與下載一起的安裝說明進行安裝。一些版本的JavaComm 2.0會包含兩個安裝說明。這兩個說明間最明顯的區別是錯誤的那個是用於古老的Java1.1環境的,而適用於Java 1.2(jdk1.2.html)的那個才是正確的。

Windows使用者可能不會意識到他們在不同的地方(一般是3到4個)安裝了同一個VM的副本。一些IDE和Java應用程式可能也會帶有他們自己的私有JRE/JDK。所以JavaComm需要重複安裝到這些VM(JDK和JRE)中,這樣才能夠開發和執行串列埠應用程式。

IDE 都有代表性的IDE的方式來得知一個新的庫(類和文件)。通常一個庫想JavaComm不僅需要被IDE識別,而且每個使用該庫的專案也應當識別。閱讀IDE的文件,應該注意老的JavaComm 2.0 版本以及JavaDoc API文件使用的是Java 1.0 的Java Doc 佈局。一些現代的IDE已經不再認識這些結構並不能將JavaComm2.0的文件整合到他們的幫助系統中了。在這種情況下需要一個外部的瀏覽器來閱讀文件(推薦活動)

一旦軟體安裝完成,它便會推薦測試樣例和JavaDoc 目錄。構建並執行樣例應用來確認安裝是否正確時很有道理的。樣例程式通常需要一些小的調整以便執行在特別的平臺上(像改寫硬編碼的com埠識別符號)。在執行一個樣例程式時最好有一些序列硬體,想cabling,零調變解調器,接線盒,一個真正的貓,PABX以及其他可用的裝置。

Serial_Programming:RS-232 Connections 和Serial_Programming:Modems and AT Commands 提供了一些怎樣搭建序列應用開發環境的資訊。

找到預期的串列埠

當用JavaComm序列程式設計時首先要做的三件事

    列舉JavaComm能訪問的所有串列埠(埠標識)
    從能訪問的埠標識中選擇預期的埠標識
    通過埠標識取得埠

列舉和選擇期望的埠標識在同一個迴圈中完成:


import javax.comm.*;
import java.util.*;
...
//
// Platform specific port name, here= a Unix name
//
// NOTE: On at least one Unix JavaComm implementation JavaComm 
//    enumerates the ports as "COM1" ... "COMx", too, and not
//    by their Unix device names "/dev/tty...". 
//    Yet another good reason to not hard-code the wanted
//    port, but instead make it user configurable.
//
String wantedPortName = "/dev/ttya";
//
// Get an enumeration of all ports known to JavaComm
//
Enumeration portIdentifiers = CommPortIdentifier.getPortIdentifiers();
//
// Check each port identifier if 
//  (a) it indicates a serial (not a parallel) port, and
//  (b) matches the desired name.
//
CommPortIdentifier portId = null; // will be set if port found
while (portIdentifiers.hasMoreElements())
{
CommPortIdentifier pid = (CommPortIdentifier) portIdentifiers.nextElement();
if(pid.getPortType() == CommPortIdentifier.PORT_SERIAL &&
pid.getName().equals(wantedPortName)) 
{
portId = pid;
break;
}
}
if(portId == null)
{
System.err.println("Could not find serial port "   wantedPortName);
System.exit(1);
}
//
// Use port identifier for acquiring the port
//
...
注意:
JavaComm會從與其繫結的特定平臺相關的驅動中獲得一個預設的可訪問串列埠標識列表。這個列表實際上不能通過JavaComm進行配置。方法CommPortIdentifier.addPortName()是有誤導性的,因為驅動類是與平臺相關的,而且它們的實現不是公共API的組成部分。依賴於驅動,這個埠列表可能會在驅動中進行配置/擴充套件。所以,如果JavaComm沒有找到某一特定埠,對驅動進行一些改動有時會有所幫助。
某埠識別符號一旦被找到,就可以用它取得期望的埠:
//
// Use port identifier for acquiring the port
//
SerialPort port = null;
try {
port = (SerialPort) portId.open(
"name", // Name of the application asking for the port 
10000  // Wait max. 10 sec. to acquire port
);
} catch(PortInUseException e) {
System.err.println("Port already in use: "   e);
System.exit(1);
}
//
// Now we are granted exclusive access to the particular serial
// port. We can configure it and obtain input and output streams.
//
...

初始化串列埠

串列埠的初始化是很直觀的。可以逐個地設定通訊引數(波特率,資料位,停止位,奇偶校驗),也可以使用方便的setSerialPortParams(…)方法一下把他們搞定。

作為初始化的一部分,通訊的輸入輸出流可以在如下的示例中配置。


import java.io.*;
...
//
// Set all the params. 
// This may need to go in a try/catch block which throws UnsupportedCommOperationException
//
port.setSerialPortParams(
115200,
SerialPort.DATABITS_8,
SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
//
// Open the input Reader and output stream. The choice of a
// Reader and Stream are arbitrary and need to be adapted to
// the actual application. Typically one would use Streams in
// both directions, since they allow for binary data transfer,
// not only character data transfer.
//
BufferedReader is = null; // for demo purposes only. A stream would be more typical.
PrintStream  os = null;
try {
is = new BufferedReader(new InputStreamReader(port.getInputStream()));
} catch (IOException e) {
System.err.println("Can't open input stream: write-only");
is = null;
}
//
// New Linux systems rely on Unicode, so it might be necessary to
// specify the encoding scheme to be used. Typically this should
// be US-ASCII (7 bit communication), or ISO Latin 1 (8 bit
// communication), as there is likely no modem out there accepting
// Unicode for its commands. An example to specify the encoding
// would look like:
//
//   os = new PrintStream(port.getOutputStream(), true, "ISO-8859-1");
//
os = new PrintStream(port.getOutputStream(), true);
//
// Actual data communication would happen here
// performReadWriteCode();
//
//
// It is very important to close input and output streams as well
// as the port. Otherwise Java, driver and OS resources are not released.
//
if (is != null) is.close();
if (os != null) os.close();
if (port != null) port.close();

簡單資料傳輸
簡單地寫入資料
 

將資料寫入到串列埠與基本的java IO一樣簡單。但在你使用AT Hayes 協議時仍有一些注意事項:

    不要在輸出流(OutputStream)中使用prinln(或其他自動附加”\n”的方法)。調變解調器的AT Hayes協議使用”\r\n”作為分隔符(而不考濾底層的作業系統)。
    寫入輸出流之後,如果調變解調器設定了回顯命令列,輸入流的緩衝區會存有傳送的指令的複述(有換行)和另一個換行(”AT”指令的響應)。所以做為寫操作的一部分,要確保清理輸入流中的這種資訊(實際上它可以用於查錯)。
    當使用Reader/Writer(不是個好主意)時,最少要設定字元編碼為US-ASCII而不是使用系統平臺的預設編碼,否則程式可能不會執行。
    因為使用調變解調器的主要操作是傳輸原始資料,與調變解調器的通訊應該使用輸入/輸出流,而不是Reader/Writer.

Clipboard
 

To do:

    解釋如何在同一個流中混合二進位制與字元的輸入輸出

    修改示例程式使其使用流


// Write to the output 
os.print("AT");
os.print("\r\n"); // Append a carriage return with a line feed
is.readLine(); // First read will contain the echoed command you sent to it. In this case: "AT"
is.readLine(); // Second read will remove the extra line feed that AT generates as output

簡單的資料讀取(輪詢)

如果你正確的使用了寫操作(如上所述),讀操作只需簡單的一條命令。


// Read the response
String response = is.readLine(); // if you sent "AT" then response == "OK"

簡單讀寫的問題

上一節中演示的簡單串列埠讀寫有很嚴重的缺陷。所有的操作都是通過阻塞I/O完成的。這意味著當沒有可讀資料時,或輸出緩衝區滿(裝置不能接受更多資料)時:

讀寫方法(在前面示例中的是os.print()或is.readLine())不會返回, 導致應用程式被暫停。更準確地說,讀寫執行緒被阻塞了。如果那個執行緒是應用程式主執行緒的話,應用程式會停止直到阻塞條件被釋放(即有可讀資料到達或裝置重新接受資料)。

除非應用程式是最原始的那種,否則程式被阻塞是絕不允許的。例如,最起碼也要能讓使用者取消通訊操作。這需要使用非阻塞I/O或非同步I/O。然而JavaComm是基於Java的標準阻塞I/O系統(InputStream,OutputStream)的,但可以採用稍後展示的一個變形技巧。

所謂的”變形技巧”是JavaComm通過事件通知機制為非同步I/O提供的有限的支援。但在Java中要在阻塞I/O的基礎上實現非阻塞I/O的常用解決方案是使用執行緒。對於串列埠寫操作這個方案是切實可行的,強烈建議使用一個單獨的執行緒對串列埠進行寫操作-儘管已經使用了事件通知機制,這稍後會做出解釋。

讀操作也應該在一個單獨的執行緒中進行處理,但如果採用了JavaComm的事件通知機制這也不是必須的。總結:

讀操作使用事件通知和/或單獨執行緒;

寫操作都要使用單獨執行緒,可選用事件通知機制。

接下來的部分會介紹一些其他細節。

事件驅動序列通訊
引言

JavaComm API提供了事件通知機制以克服阻塞I/O帶來的問題。但在這個典型的Sun方式中這個機制也有問題的。

原則上一個應用程式可以註冊事件監聽器到一個特定的串列埠以接收發生在這個埠上的重要事件的通知。讀寫資料的兩個最有意思的事件型別是

    javax.comm.SerialPortEvent.DATA_AVAILABLE和  javax.comm.SerialPortEvent.OUTPUT_BUFFER_EMPTY.

但這也帶來了兩個問題:

    每個串列埠只能註冊一個事件監聽器。這會強制程式設計師編寫”巨大”的監聽器,它以接收到的事件型別來區分要進行的操作。
    OUTPUT_BUFFER_EMPTY是一個可選的事件型別。Sun在文件中隱晦地提到JavaComm的實現不一定都會支援產生這個事件型別。

在進行詳細討論前,下一節將會演示實現和註冊一個串列埠事件處理器的主要方式。要記住一個串列埠只能有一個事件處理器,而且它要處理所有可能的事件。

設定序列事件處理器

 


import javax.comm.*;
/**
* Listener to handle all serial port events.
*
* NOTE: It is typical that the SerialPortEventListener is implemented
*    in the main class that is supposed to communicate with the
*    device. That way the listener has easy access to state information
*    about the communication, e.g. when a particular communication
*    protocol needs to be followed.
*
*    However, for demonstration purposes this example implements a
*    separate class.
*/ 
class SerialListener implements SerialPortEventListener {
/**
* Handle serial events. Dispatches the event to event-specific
* methods.
* @param event The serial event
*/
@Override
public void serialEvent(SerialPortEvent event){
//
// Dispatch event to individual methods. This keeps this ugly
// switch/case statement as short as possible.
//
switch(event.getEventType()) {
case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
outputBufferEmpty(event);
break;
case SerialPortEvent.DATA_AVAILABLE:
dataAvailable(event);
break;
/* Other events, not implemented here ->
case SerialPortEvent.BI:
breakInterrupt(event);
break;
case SerialPortEvent.CD:
carrierDetect(event);
break;
case SerialPortEvent.CTS:
clearToSend(event);
break;
case SerialPortEvent.DSR:
dataSetReady(event);
break;
case SerialPortEvent.FE:
framingError(event);
break;
case SerialPortEvent.OE:
overrunError(event);
break;
case SerialPortEvent.PE:
parityError(event);
break;
case SerialPortEvent.RI:
ringIndicator(event);
break;
<- other events, not implemented here */
}
}
/**
* Handle output buffer empty events.
* NOTE: The reception of this event is optional and not
*    guaranteed by the API specification.
* @param event The output buffer empty event
*/
protected void outputBufferEmpty(SerialPortEvent event) {
// Implement writing more data here
}
/**
* Handle data available events.
*
* @param event The data available event
*/
protected void dataAvailable(SerialPortEvent event) {
// implement reading from the serial port here
}
}

監聽器一旦實現,即可用來監聽特定的串列埠事件。要做到如此,需要為串列埠新增一個監聽器例項。此外,每個事件型別的接收需要進行單獨申請。
 


SerialPort port = ...;
...
//
// Configure port parameters here. Only after the port is configured it
// makes sense to enable events. The event handler might be called immediately
// after an event is enabled.
...
//
// Typically, if the current class implements the SerialEventListener interface
// one would call
//
//    port.addEventListener(this);
//
// but for our example a new instance of SerialListener is created:
//
port.addEventListener(new SerialListener());
//
// Enable the events we are interested in
//
port.notifyOnDataAvailable(true);
port.notifyOnOutputEmpty(true);
/* other events not used in this example ->
port.notifyOnBreakInterrupt(true);
port.notifyOnCarrierDetect(true);
port.notifyOnCTS(true);
port.notifyOnDSR(true);
port.notifyOnFramingError(true);
port.notifyOnOverrunError(true);
port.notifyOnParityError(true);
port.notifyOnRingIndicator(true);
<- other events not used in this example */

資料寫入

使用單獨分離的程序進行資料寫入只有一個目的:避免整個應用程式塊由於某一個串列埠未準備好寫資料而鎖定。

一個簡單的,執行緒安全的環形緩衝區實現

使用一個獨立於主程式執行緒的執行緒進行寫操作,表明需要某種方式將要寫入的資料從主應用執行緒(主執行緒)提交給寫執行緒。這可以採用一個共享的非同步事件緩衝區,例如一個byte陣列。另外,主程式還需要某種方式決定是否可以往資料緩衝區中寫資料或者資料緩衝區是否已經滿了。如果資料緩衝區已滿,表明串列埠還沒有準備好寫操作,並且要輸出的資料正在排隊。主程式需要在共享資料緩衝區中輪詢可用的新的空閒空間。然而,在主程式輪詢的間隙可以做些其他的事,例如更新使用者介面(GUI),提供一個可以退出傳送資料的命令提示等等。

乍一看PipedInputStream/PipedOutputStream對於這種通訊是一個不錯的主意。但如果管道流真的有用的話那Sun就不是Sun了。如果與之對應的PipedOutputStream沒有及時清理的話,PipedInputStream會發生阻塞,進而會阻塞應用程式執行緒。就算使用獨立執行緒也避免不了。而java.nio.Pipe也有與此相同的問題。它的阻塞行為與平臺相關。而將JavaComm使用的傳統I/O改為NIO也不是很好。

在本文中採用了一個很簡單的同步的環形緩衝區來進行執行緒間資料傳遞。在現實世界中的應用程式很可能會使用更加複雜的緩衝區實現。例如在一個現實世界的實現需要以輸入輸出流的視角操作緩衝區。

如此一個環形緩衝器並沒有什麼特別的,線上程處理方面,也沒有特別的屬性。它只是用來這裡用來提供資料緩衝的一個簡單資料結構。這裡已經實現了該緩衝器,以確保訪問該資料結構是執行緒安全的。

 


/**
* Synchronized ring buffer. 
* Suitable to hand over data from one thread to another.
**/
public class RingBuffer {
/** internal buffer to hold the data **/
protected byte buffer[];
/** size of the buffer **/
protected int size;
/** current start of data area **/
protected int start;
/** current end of data area **/
protected int end;
/**
* Construct a RingBuffer with a default buffer size of 1k.
*/
public RingBuffer() {
this(1024);
}
/**
* Construct a RingBuffer with a certain buffer size.
* @param size  Buffer size in bytes
*/
public RingBuffer(int size) {
this.size = size;
buffer = new byte[size];
clear();
}
/**
* Clear the buffer contents. All data still in the buffer is lost.
*/
public void clear() {
// Just reset the pointers. The remaining data fragments, if any,
// will be overwritten during normal operation.
start = end = 0;
}
/**
* Return used space in buffer. This is the size of the
* data currently in the buffer.
* <p>
* Note: While the value is correct upon returning, it
* is not necessarily valid when data is read from the 
* buffer or written to the buffer. Another thread might
* have filled the buffer or emptied it in the mean time.
*
* @return currently amount of data available in buffer
*/
public int data() {
return start <= end
? end - start
: end - start   size;
}
/**
* Return unused space in buffer. Note: While the value is
* correct upon returning, it is not necessarily valid when
* data is written to the buffer or read from the buffer.
* Another thread might have filled the buffer or emptied
* it in the mean time.
*
* @return currently available free space
*/
public int free() {
return start <= end
? size   start - end
: start - end;
}
/**
* Write as much data as possible to the buffer.
* @param data  Data to be written
* @return    Amount of data actually written
*/
int write(byte data[]) {
return write(data, 0, data.length); 
}
/**
* Write as much data as possible to the buffer.
* @param data  Array holding data to be written
* @param off  Offset of data in array
* @param n   Amount of data to write, starting from .
* @return    Amount of data actually written
*/
int write(byte data[], int off, int n) {
if(n <= 0) return 0;
int remain = n;
// @todo check if off is valid: 0= <= off < data.length; throw exception if not
int i = Math.min(remain, (end < start ? start : buffer.length) - end);
if(i > 0) {
System.arraycopy(data, off, buffer, end, i);
off   = i;
remain -= i;
end   = i;
}
i = Math.min(remain, end >= start ? start : 0);
if(i > 0 ) {
System.arraycopy(data, off, buffer, 0, i);
remain -= i;
end = i;
}
return n - remain;
}
/**
* Read as much data as possible from the buffer.
* @param data  Where to store the data
* @return    Amount of data read
*/
int read(byte data[]) {
return read(data, 0, data.length); 
}
/**
* Read as much data as possible from the buffer.
* @param data  Where to store the read data
* @param off  Offset of data in array
* @param n   Amount of data to read
* @return    Amount of data actually read
*/
int read(byte data[], int off, int n) {
if(n <= 0) return 0;
int remain = n;
// @todo check if off is valid: 0= <= off < data.length; throw exception if not
int i = Math.min(remain, (end < start ? buffer.length : end) - start);
if(i > 0) {
System.arraycopy(buffer, start, data, off, i);
off   = i;
remain -= i;
start  = i;
if(start >= buffer.length) start = 0;
}
i = Math.min(remain, end >= start ? 0 : end);
if(i > 0 ) {
System.arraycopy(buffer, 0, data, off, i);
remain -= i;
start = i;
}
return n - remain;
}
}

通過使用該環形緩衝器,你現在可以以一種可控的方式從一個執行緒提交資料到另一個執行緒。當然,其他執行緒安全、非阻塞式的方法同樣可以。這裡的關鍵點在於當緩衝區已滿或者緩衝區為空時,資料的讀寫不會造成堵塞。

根據在 “建立一個串列埠事件處理器”小節演示的事件處理器的輪廓,你可以使用在”一個簡單的,執行緒安全的環形緩衝區實現”小節中介紹的共享環形緩衝區以支援OUTPUT_BUFFER_EMPTY事件。不是所有的JavaComm實現都支援這個事件,所以這段程式碼可能永遠也不會被呼叫。但如果可以,它是確保最佳資料吞吐量的一部分,因為它可以使串列埠不會長時間處於空閒狀態。

事件監聽器的輪廓需要提供一個outputBufferEmpty()方法,它的實現如下:


RingBuffer dataBuffer = ... ;
/**
* Handle output buffer empty events.
* NOTE: The reception is of this event is optional and not
*    guaranteed by the API specification.
* @param event The output buffer empty event
*/
protected void outputBufferEmpty(SerialPortEvent event) {
}

下面的示例假設資料的目的地是某個檔案。當資料到達時它會被從串列埠中取出並寫入目的檔案。這只是個精簡化的檢視,因為實際上你需要檢查資料的EOF標識以將調變解調器(通常稱為“貓”)重置為命令模式。


import javax.comm.*;
...
InputStream is = port.getInputStream();
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("out.dat"));
/**
* Listen to port events
*/ 
class FileListener implements SerialPortEventListener {
/**
* Handle serial event.
*/
void serialEvent(SerialPortEvent e) {
SerialPort port = (SerialPort) e.getSource();
//
// Discriminate handling according to event type
//
switch(e.getEventType()) {
case SerialPortEvent.DATA_AVAILABLE:
//
// Move all currently available data to the file
//
try {
int c;
while((c = is.read()) != -1) {
out.write(c);
}
} catch(IOException ex) {
...
}
break;
case ...:
...
break;
...
}
if (is != null) is.close();
if (port != null) port.close();
}

調變解調器控制

JavaComm主要關心的是一個串列埠的處理和串列埠上資料的傳送。它不懂或者提供對高層協議的支援,比如Hayes調製解調指令通常用來控制客戶級的貓。這不是JavaComm的任務,也就不是一個bug。

如同其他特別的序列裝置,如果希望由JavaComm控制一個貓,那麼就得在JavaComm上寫必要的程式碼。頁面”Hayes-compatible Modems and AT Commands”提供了處理Hayes貓的必要的基本資訊。

一些作業系統,像Windows或某一Linux對於如何配置一個特別型別或牌子的貓的控制命令提供了一個或多或少標準的方式。例如,Windows貓的“驅動”通常只是註冊入口,描述一個個別的貓(真正的驅動是一個通用的序列調製解調驅動)。JavaComm沒法獲取這樣的作業系統的具體的資料。因此,要麼必須提供一個單獨的java工具來允許使用者為使用個別的貓去配置一個應用,要麼就新增一些相應平臺的(本地的)程式碼。

RxTx
概述與版本

由於Sun沒有為Linux提供JavaComm的參考實現,人們為java和linux開發了RxTx。後來RxTx被移植到了其他平臺。最新版本的RxTx已知可執行在100種以上平臺,包括Linux, Windows, Mac OS, Solaris 和其他作業系統。

RxTx可以獨立於JavaComm API使用,也可以作為所謂的Java Comm API服務者。如果採用後者還需要一個稱為JCL的封裝包。JCL和RxTx通常與Linux/Java發行版打包在一起,或者JCL完全與程式碼整合在一起。所以,在一個個地下載他們之前,看一看Linux發行版的CD是值得的。

由於Sun對JavaComm的有限的支援和不適當的文件,放棄JavaComm API,轉而直接使用RxTx而不是通過JCL封裝包似乎成為了一種趨勢。然而RxTx的文件是很稀少的。特別是RxTx開發者喜歡將他們的版本和包內容弄得一團糟(例如使用或未使用整合的JCL)。從1.5版本開始,RxTx包含了公共JavaComm類的替代類。由於法律原因,他們沒有在java.comm包中,而是在gui.io包下。然而現存的兩個版本的打包內容有很大差別。

    RxTx 2.0
    這個版本的RxTx 主要用作JavaComm提供者。它應該源自於RxRx 1.4,這是RxTx新增gui.io包之前的版本。
    RxTx 2.1
    這個版本的RxTx包含了一個完整的代替java.comm的gnu.io包。它應該源自於RxTx 1.5,這是支援gnu.io的起始版本。

因此,如果你想對原始的JavaComm API 程式設計的話你需要

        Sun JavaComm 通用版。撰寫本文時實際上就是Unix包(包含對各種類Unix系統的支援,像Linux或Solaris)即使在Windows上,這個Unix包也是需要用來提供java.comm的通用實現的。只用用Java實現那部分會被用到,然而Unix的本地庫會被忽略的。

    RxTx 2.0, 為了能在JavaComm通用版本下有不同的提供者,不同於JavaComm包下的那個。然而,如果你只想用gnu.io替換包,那麼你只需要將一個JavaComm應用轉換成RxTx應用。

如果你是對Sun公司放棄使JavaComm支援Windows這一行為感到失望的眾多成員中的一個,那麼你應該將你的JavaComm應用轉到RxTx上來。如你在上面所看到的,這裡有兩種方式來完成這件事,假設你已經安裝了RxTx的某一版本,那麼下面的選項可選其一:

    使用RxTx 2.0作為JavaComm介面的實現
    將應用移植到RxTx 2.1環境上

上面的第一項在前面已經解釋,第二項也相當簡單。對於需要將JavaComm應用移植到RxTx 2.1上來的人,只需要將應用原始碼中所有對“java.comm”包的引用換成“gnu.io”包,如果原始的JavaComm應用編寫恰當,這裡就沒有其他的事情需要去做。

在Unix平臺上,RxTx 2.1甚至提供了工具“contrib/ChangePackage.sh”去在原始碼樹形結構中執行全域性的替換,這樣的替換在其他的平臺很容易使用支援重構功能的IDE(整合開發環境)來完成。

您可能感興趣的文章:

java 串列埠通訊詳細及簡單例項使用Java實現串列埠通訊基於Java編寫串列埠通訊工具java 串列埠通訊實現流程示例PHP與Java進行通訊的實現方法使用JAVA實現http通訊詳解Java實現的基於socket通訊的例項程式碼深入理解JAVA多執行緒之執行緒間的通訊方式java多執行緒實現伺服器端與多客戶端之間的通訊基於java TCP網路通訊的例項詳解Java實現的串列埠通訊功能示例

相關文章

程式語言 最新文章