欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳細(xì)解讀Java的串口編程

 更新時(shí)間:2015年08月05日 10:17:46   投稿:goldensun  
這篇文章主要介紹了詳細(xì)解讀Java的串口編程,而文中講解的示例主要針對(duì)于JavaComm和RxTx這兩個(gè)庫(kù),需要的朋友可以參考下

常見問題

JavaComm 和 RxTX 安裝時(shí)有一些與眾不同的地方。強(qiáng)烈建議按照安裝說明一點(diǎn)點(diǎn)的安裝。如果安裝說明要求一個(gè)jar文件或一個(gè)共享庫(kù)必須在某一特定的文件夾下,那這就意味著需要嚴(yán)肅對(duì)待。如果說明要求一個(gè)特定的文件或設(shè)備需要擁有一個(gè)特定的所有權(quán)或訪問權(quán),這也意味著需要嚴(yán)肅處理。很多安裝問題都只是因?yàn)闆]有按照安裝說明要求的去做而引起的。


特別要注意的是一些版本的JavaComm會(huì)帶有兩個(gè)安裝說明。一個(gè)用于java 1.2及以后的版本,一個(gè)用于java 1.1版本。使用錯(cuò)誤的安裝說明會(huì)導(dǎo)致不能工作的安裝結(jié)果。另一方面,TxTx的一些版本/構(gòu)件/包會(huì)包含不完全的說明。在這種情況下需要獲得相關(guān)的RxTx發(fā)布的源碼,它包含了完整的安裝說明。

另外要注意Windows的Jdk安裝程序會(huì)包含三個(gè)java虛擬機(jī),因此會(huì)有三個(gè)擴(kuò)展文件夾。

  1.     一個(gè)作為JDK的組成部分。
  2.     一個(gè)作為與運(yùn)行JDK工具的JDK一起的私有JRE的一部分。
  3.     一個(gè)作為與運(yùn)行應(yīng)用程序的JDK一起的公共JRE的一部分。

更有甚者甚至?xí)械?個(gè)jre,它存在于\Windows的目錄結(jié)構(gòu)中。 JavaComm應(yīng)該作為擴(kuò)展被安裝到JDK和所有公共JRE中。

Webstart

   JavaComm

關(guān)于JavaComm和RxTx的一個(gè)常見問題是它們不支持通過Java WebStart進(jìn)行安裝:JavaComm的臭名昭著是因?yàn)樾枰獙⒁粋€(gè)稱為javax.comm.properties的文件放到JDK lib目錄下,而這是不能通過Java WebStart完成的。很令人沮喪的是,對(duì)于該文件的需要是JavaComm中一些不必要的設(shè)計(jì)/決定所導(dǎo)致的惡果,而JavaComm的設(shè)計(jì)者們可以很容易地避免這種事情。Sun固執(zhí)地拒絕修正這個(gè)錯(cuò)誤,他們強(qiáng)調(diào)這個(gè)機(jī)制是必不可少的。他們是在睜著眼說瞎話,特別是當(dāng)提及JavaComm時(shí),因?yàn)镴ava在很長(zhǎng)一段時(shí)間內(nèi)擁有一個(gè)專門用于此類意圖的服務(wù)提供者架構(gòu)。

這個(gè)屬性文件中的內(nèi)容只有一行,即提供本地驅(qū)動(dòng)的java類名稱。

driver=com.sun.comm.Win32Driver


以下是一個(gè)可以通過Web Start部署JavaComm而無視那個(gè)傷腦筋的屬性文件的技巧。但它有嚴(yán)重的缺陷,并且在部署較新的JavaComm時(shí)可能會(huì)失?。绻鸖un會(huì)做一個(gè)新版本的話。

首先,關(guān)閉安全管理器(security manager)。Sun的一些蠢貨程序員覺得一遍又一遍地檢查可怕的javax.comm.properties文件的存在是很酷的事情,特別是當(dāng)它最初已經(jīng)被加載完成之后。這只是單純地檢查文件是否存在而不為其他原因。

System.setSecurityManager(null);

然后,當(dāng)初始化JavaComm API時(shí),手動(dòng)初始化驅(qū)動(dòng)。

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

RxTx

RxTx在某些平臺(tái)上需要改變串口設(shè)備的所有權(quán)和訪問權(quán)。這也是無法通過WebStart完成的事。


在程序啟動(dòng)時(shí)你應(yīng)該要求用戶作為超級(jí)用戶來執(zhí)行必要的設(shè)置。特別的,RxTx有一個(gè)模式匹配算法來驗(yàn)證“合法”的串口設(shè)備名。當(dāng)某人想使用不標(biāo)準(zhǔn)的設(shè)備,例如USB轉(zhuǎn)串口轉(zhuǎn)換器(USB-to-serial converter)時(shí),這常會(huì)把事情弄砸。這個(gè)機(jī)制可以被系統(tǒng)屬性屏蔽掉。詳情參照RxTx的安裝說明。
JavaComm API
引言

Java官方串口通信API是JavaComm API。這個(gè)API不是Java 2標(biāo)準(zhǔn)版的組成部分,因而此API的實(shí)現(xiàn)需要單獨(dú)下載。不幸的是,JavaComm沒有獲得Sun足夠的重視,實(shí)際的維護(hù)時(shí)間也不是很長(zhǎng)。Sun只是偶爾修復(fù)一些不重要的bug,卻沒有做過一些早已過期的重要檢修。


本節(jié)闡述JavaComm API的基本操作。所提供的源碼保持簡(jiǎn)化以展示重點(diǎn),在實(shí)際應(yīng)用中使用需要完善。

這章的源碼并不是唯一可用的示例代碼。很多例子中都包含JavaComm下載。這些例子幾乎包括比其API文檔更多的關(guān)于如何使用它的信息。不幸的是,Sun公司沒有任何真正的教程或一些說明文檔。因此,要理解這個(gè)API的機(jī)制,學(xué)習(xí)這些示例代碼是值得的,也仍需要學(xué)習(xí)這個(gè)API文檔。但最好的方法是,學(xué)習(xí)這些例子并運(yùn)用它們。由于缺少易用的應(yīng)用以及理解這些API的編程模型有困難,API通常備受抨擊。相比其名氣和功能,這個(gè)API更好,但僅此而已。


該API采用回調(diào)機(jī)制通知程序員有新數(shù)據(jù)到來。這也是學(xué)習(xí)這一機(jī)制的好主意,而不是依賴詢問端口。不像Java中的其他回調(diào)接口(如:在圖形界面),這個(gè)接口只允許一個(gè)監(jiān)聽器監(jiān)聽事件。如果多個(gè)監(jiān)聽器請(qǐng)求監(jiān)聽?zhēng)讉€(gè)事件,主監(jiān)聽器必須通過分派信息給其他二級(jí)監(jiān)聽器的方式來實(shí)現(xiàn)。

下載與安裝

下載

Sun公司的JavaComm網(wǎng)頁指向下載地址。在這個(gè)地址下,Sun當(dāng)前(2007年)提供了支持Solaris/SPARC、Solaris/x86已經(jīng)Linux x86的JavaComm 3.0版本。下載需要注冊(cè)一個(gè)Sun公司的賬戶。下載頁提供了注冊(cè)頁的鏈接。注冊(cè)的目的并不清楚。在為注冊(cè)時(shí),用戶可下載JDK和JREs,但對(duì)于這幾乎微不足道的JavaComm,Sun公司在軟件分銷和出口方面卻援引法律條文和政府限制。


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

安裝

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

Windows用戶可能不會(huì)意識(shí)到他們?cè)诓煌牡胤剑ㄒ话闶?到4個(gè))安裝了同一個(gè)VM的副本。一些IDE和Java應(yīng)用程序可能也會(huì)帶有他們自己的私有JRE/JDK。所以JavaComm需要重復(fù)安裝到這些VM(JDK和JRE)中,這樣才能夠開發(fā)和執(zhí)行串口應(yīng)用程序。


IDE 都有代表性的IDE的方式來得知一個(gè)新的庫(kù)(類和文檔)。通常一個(gè)庫(kù)想JavaComm不僅需要被IDE識(shí)別,而且每個(gè)使用該庫(kù)的項(xiàng)目也應(yīng)當(dāng)識(shí)別。閱讀IDE的文檔,應(yīng)該注意老的JavaComm 2.0 版本以及JavaDoc API文檔使用的是Java 1.0 的Java Doc 布局。一些現(xiàn)代的IDE已經(jīng)不再認(rèn)識(shí)這些結(jié)構(gòu)并不能將JavaComm2.0的文檔集成到他們的幫助系統(tǒng)中了。在這種情況下需要一個(gè)外部的瀏覽器來閱讀文檔(推薦活動(dòng))

一旦軟件安裝完成,它便會(huì)推薦測(cè)試樣例和JavaDoc 目錄。構(gòu)建并運(yùn)行樣例應(yīng)用來確認(rèn)安裝是否正確時(shí)很有道理的。樣例程序通常需要一些小的調(diào)整以便運(yùn)行在特別的平臺(tái)上(像改寫硬編碼的com端口標(biāo)識(shí)符)。在運(yùn)行一個(gè)樣例程序時(shí)最好有一些串行硬件,想cabling,零調(diào)制解調(diào)器,接線盒,一個(gè)真正的貓,PABX以及其他可用的設(shè)備。

Serial_Programming:RS-232 Connections 和Serial_Programming:Modems and AT Commands 提供了一些怎樣搭建串行應(yīng)用開發(fā)環(huán)境的信息。

找到預(yù)期的串口

當(dāng)用JavaComm串行編程時(shí)首先要做的三件事

  1.     枚舉JavaComm能訪問的所有串口(端口標(biāo)識(shí))
  2.     從能訪問的端口標(biāo)識(shí)中選擇預(yù)期的端口標(biāo)識(shí)
  3.     通過端口標(biāo)識(shí)取得端口

枚舉和選擇期望的端口標(biāo)識(shí)在同一個(gè)循環(huán)中完成:

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會(huì)從與其綁定的特定平臺(tái)相關(guān)的驅(qū)動(dòng)中獲得一個(gè)默認(rèn)的可訪問串口標(biāo)識(shí)列表。這個(gè)列表實(shí)際上不能通過JavaComm進(jìn)行配置。方法CommPortIdentifier.addPortName()是有誤導(dǎo)性的,因?yàn)轵?qū)動(dòng)類是與平臺(tái)相關(guān)的,而且它們的實(shí)現(xiàn)不是公共API的組成部分。依賴于驅(qū)動(dòng),這個(gè)端口列表可能會(huì)在驅(qū)動(dòng)中進(jìn)行配置/擴(kuò)展。所以,如果JavaComm沒有找到某一特定端口,對(duì)驅(qū)動(dòng)進(jìn)行一些改動(dòng)有時(shí)會(huì)有所幫助。

某端口標(biāo)識(shí)符一旦被找到,就可以用它取得期望的端口:

//
// 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.
//
...


初始化串口

串口的初始化是很直觀的。可以逐個(gè)地設(shè)置通信參數(shù)(波特率,數(shù)據(jù)位,停止位,奇偶校驗(yàn)),也可以使用方便的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();

簡(jiǎn)單數(shù)據(jù)傳輸
簡(jiǎn)單地寫入數(shù)據(jù)
 

將數(shù)據(jù)寫入到串口與基本的java IO一樣簡(jiǎn)單。但在你使用AT Hayes 協(xié)議時(shí)仍有一些注意事項(xiàng):

  •     不要在輸出流(OutputStream)中使用prinln(或其他自動(dòng)附加"\n"的方法)。調(diào)制解調(diào)器的AT Hayes協(xié)議使用"\r\n"作為分隔符(而不考濾底層的操作系統(tǒng))。
  •     寫入輸出流之后,如果調(diào)制解調(diào)器設(shè)置了回顯命令行,輸入流的緩沖區(qū)會(huì)存有發(fā)送的指令的復(fù)述(有換行)和另一個(gè)換行("AT"指令的響應(yīng))。所以做為寫操作的一部分,要確保清理輸入流中的這種信息(實(shí)際上它可以用于查錯(cuò))。
  •     當(dāng)使用Reader/Writer(不是個(gè)好主意)時(shí),最少要設(shè)置字符編碼為US-ASCII而不是使用系統(tǒng)平臺(tái)的默認(rèn)編碼,否則程序可能不會(huì)運(yùn)行。
  •     因?yàn)槭褂谜{(diào)制解調(diào)器的主要操作是傳輸原始數(shù)據(jù),與調(diào)制解調(diào)器的通信應(yīng)該使用輸入/輸出流,而不是Reader/Writer.

Clipboard
 

To do:

    解釋如何在同一個(gè)流中混合二進(jìn)制與字符的輸入輸出


    修改示例程序使其使用流

// 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

簡(jiǎn)單的數(shù)據(jù)讀?。ㄝ喸儯?/p>

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

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

簡(jiǎn)單讀寫的問題

上一節(jié)中演示的簡(jiǎn)單串口讀寫有很嚴(yán)重的缺陷。所有的操作都是通過阻塞I/O完成的。這意味著當(dāng)沒有可讀數(shù)據(jù)時(shí),或輸出緩沖區(qū)滿(設(shè)備不能接受更多數(shù)據(jù))時(shí):

讀寫方法(在前面示例中的是os.print()或is.readLine())不會(huì)返回, 導(dǎo)致應(yīng)用程序被暫停。更準(zhǔn)確地說,讀寫線程被阻塞了。如果那個(gè)線程是應(yīng)用程序主線程的話,應(yīng)用程序會(huì)停止直到阻塞條件被釋放(即有可讀數(shù)據(jù)到達(dá)或設(shè)備重新接受數(shù)據(jù))。


除非應(yīng)用程序是最原始的那種,否則程序被阻塞是絕不允許的。例如,最起碼也要能讓用戶取消通信操作。這需要使用非阻塞I/O或異步I/O。然而JavaComm是基于Java的標(biāo)準(zhǔn)阻塞I/O系統(tǒng)(InputStream,OutputStream)的,但可以采用稍后展示的一個(gè)變形技巧。

所謂的"變形技巧"是JavaComm通過事件通知機(jī)制為異步I/O提供的有限的支持。但在Java中要在阻塞I/O的基礎(chǔ)上實(shí)現(xiàn)非阻塞I/O的常用解決方案是使用線程。對(duì)于串口寫操作這個(gè)方案是切實(shí)可行的,強(qiáng)烈建議使用一個(gè)單獨(dú)的線程對(duì)串口進(jìn)行寫操作-盡管已經(jīng)使用了事件通知機(jī)制,這稍后會(huì)做出解釋。

讀操作也應(yīng)該在一個(gè)單獨(dú)的線程中進(jìn)行處理,但如果采用了JavaComm的事件通知機(jī)制這也不是必須的??偨Y(jié):

讀操作使用事件通知和/或單獨(dú)線程;

寫操作都要使用單獨(dú)線程,可選用事件通知機(jī)制。

接下來的部分會(huì)介紹一些其他細(xì)節(jié)。

事件驅(qū)動(dòng)串行通信
引言

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

原則上一個(gè)應(yīng)用程序可以注冊(cè)事件監(jiān)聽器到一個(gè)特定的串口以接收發(fā)生在這個(gè)端口上的重要事件的通知。讀寫數(shù)據(jù)的兩個(gè)最有意思的事件類型是

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

但這也帶來了兩個(gè)問題:

  1.     每個(gè)串口只能注冊(cè)一個(gè)事件監(jiān)聽器。這會(huì)強(qiáng)制程序員編寫"巨大"的監(jiān)聽器,它以接收到的事件類型來區(qū)分要進(jìn)行的操作。
  2.     OUTPUT_BUFFER_EMPTY是一個(gè)可選的事件類型。Sun在文檔中隱晦地提到JavaComm的實(shí)現(xiàn)不一定都會(huì)支持產(chǎn)生這個(gè)事件類型。

在進(jìn)行詳細(xì)討論前,下一節(jié)將會(huì)演示實(shí)現(xiàn)和注冊(cè)一個(gè)串口事件處理器的主要方式。要記住一個(gè)串口只能有一個(gè)事件處理器,而且它要處理所有可能的事件。


設(shè)置串行事件處理器

 

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
  }
}

監(jiān)聽器一旦實(shí)現(xiàn),即可用來監(jiān)聽特定的串口事件。要做到如此,需要為串口添加一個(gè)監(jiān)聽器實(shí)例。此外,每個(gè)事件類型的接收需要進(jìn)行單獨(dú)申請(qǐng)。
 

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 */

數(shù)據(jù)寫入

使用單獨(dú)分離的進(jìn)程進(jìn)行數(shù)據(jù)寫入只有一個(gè)目的:避免整個(gè)應(yīng)用程序塊由于某一個(gè)串口未準(zhǔn)備好寫數(shù)據(jù)而鎖定。

一個(gè)簡(jiǎn)單的,線程安全的環(huán)形緩沖區(qū)實(shí)現(xiàn)

使用一個(gè)獨(dú)立于主程序線程的線程進(jìn)行寫操作,表明需要某種方式將要寫入的數(shù)據(jù)從主應(yīng)用線程(主線程)提交給寫線程。這可以采用一個(gè)共享的異步事件緩沖區(qū),例如一個(gè)byte數(shù)組。另外,主程序還需要某種方式?jīng)Q定是否可以往數(shù)據(jù)緩沖區(qū)中寫數(shù)據(jù)或者數(shù)據(jù)緩沖區(qū)是否已經(jīng)滿了。如果數(shù)據(jù)緩沖區(qū)已滿,表明串口還沒有準(zhǔn)備好寫操作,并且要輸出的數(shù)據(jù)正在排隊(duì)。主程序需要在共享數(shù)據(jù)緩沖區(qū)中輪詢可用的新的空閑空間。然而,在主程序輪詢的間隙可以做些其他的事,例如更新用戶界面(GUI),提供一個(gè)可以退出發(fā)送數(shù)據(jù)的命令提示等等。


乍一看PipedInputStream/PipedOutputStream對(duì)于這種通信是一個(gè)不錯(cuò)的主意。但如果管道流真的有用的話那Sun就不是Sun了。如果與之對(duì)應(yīng)的PipedOutputStream沒有及時(shí)清理的話,PipedInputStream會(huì)發(fā)生阻塞,進(jìn)而會(huì)阻塞應(yīng)用程序線程。就算使用獨(dú)立線程也避免不了。而java.nio.Pipe也有與此相同的問題。它的阻塞行為與平臺(tái)相關(guān)。而將JavaComm使用的傳統(tǒng)I/O改為NIO也不是很好。

在本文中采用了一個(gè)很簡(jiǎn)單的同步的環(huán)形緩沖區(qū)來進(jìn)行線程間數(shù)據(jù)傳遞。在現(xiàn)實(shí)世界中的應(yīng)用程序很可能會(huì)使用更加復(fù)雜的緩沖區(qū)實(shí)現(xiàn)。例如在一個(gè)現(xiàn)實(shí)世界的實(shí)現(xiàn)需要以輸入輸出流的視角操作緩沖區(qū)。


如此一個(gè)環(huán)形緩沖器并沒有什么特別的,在線程處理方面,也沒有特別的屬性。它只是用來這里用來提供數(shù)據(jù)緩沖的一個(gè)簡(jiǎn)單數(shù)據(jù)結(jié)構(gòu)。這里已經(jīng)實(shí)現(xiàn)了該緩沖器,以確保訪問該數(shù)據(jù)結(jié)構(gòu)是線程安全的。

 

/**
 * 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;
  }
}

通過使用該環(huán)形緩沖器,你現(xiàn)在可以以一種可控的方式從一個(gè)線程提交數(shù)據(jù)到另一個(gè)線程。當(dāng)然,其他線程安全、非阻塞式的方法同樣可以。這里的關(guān)鍵點(diǎn)在于當(dāng)緩沖區(qū)已滿或者緩沖區(qū)為空時(shí),數(shù)據(jù)的讀寫不會(huì)造成堵塞。


根據(jù)在 "建立一個(gè)串口事件處理器"小節(jié)演示的事件處理器的輪廓,你可以使用在"一個(gè)簡(jiǎn)單的,線程安全的環(huán)形緩沖區(qū)實(shí)現(xiàn)"小節(jié)中介紹的共享環(huán)形緩沖區(qū)以支持OUTPUT_BUFFER_EMPTY事件。不是所有的JavaComm實(shí)現(xiàn)都支持這個(gè)事件,所以這段代碼可能永遠(yuǎn)也不會(huì)被調(diào)用。但如果可以,它是確保最佳數(shù)據(jù)吞吐量的一部分,因?yàn)樗梢允勾诓粫?huì)長(zhǎng)時(shí)間處于空閑狀態(tài)。

事件監(jiān)聽器的輪廓需要提供一個(gè)outputBufferEmpty()方法,它的實(shí)現(xiàn)如下:

  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) {
  }

下面的示例假設(shè)數(shù)據(jù)的目的地是某個(gè)文件。當(dāng)數(shù)據(jù)到達(dá)時(shí)它會(huì)被從串口中取出并寫入目的文件。這只是個(gè)精簡(jiǎn)化的視圖,因?yàn)閷?shí)際上你需要檢查數(shù)據(jù)的EOF標(biāo)識(shí)以將調(diào)制解調(diào)器(通常稱為“貓”)重置為命令模式。

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();
  }


調(diào)制解調(diào)器控制

JavaComm主要關(guān)心的是一個(gè)串口的處理和串口上數(shù)據(jù)的傳送。它不懂或者提供對(duì)高層協(xié)議的支持,比如Hayes調(diào)制解調(diào)指令通常用來控制客戶級(jí)的貓。這不是JavaComm的任務(wù),也就不是一個(gè)bug。

如同其他特別的串行設(shè)備,如果希望由JavaComm控制一個(gè)貓,那么就得在JavaComm上寫必要的代碼。頁面"Hayes-compatible Modems and AT Commands"提供了處理Hayes貓的必要的基本信息。


一些操作系統(tǒng),像Windows或某一Linux對(duì)于如何配置一個(gè)特別類型或牌子的貓的控制命令提供了一個(gè)或多或少標(biāo)準(zhǔn)的方式。例如,Windows貓的“驅(qū)動(dòng)”通常只是注冊(cè)入口,描述一個(gè)個(gè)別的貓(真正的驅(qū)動(dòng)是一個(gè)通用的串行調(diào)制解調(diào)驅(qū)動(dòng))。JavaComm沒法獲取這樣的操作系統(tǒng)的具體的數(shù)據(jù)。因此,要么必須提供一個(gè)單獨(dú)的java工具來允許用戶為使用個(gè)別的貓去配置一個(gè)應(yīng)用,要么就添加一些相應(yīng)平臺(tái)的(本地的)代碼。

RxTx
概述與版本

由于Sun沒有為L(zhǎng)inux提供JavaComm的參考實(shí)現(xiàn),人們?yōu)閖ava和linux開發(fā)了RxTx。后來RxTx被移植到了其他平臺(tái)。最新版本的RxTx已知可運(yùn)行在100種以上平臺(tái),包括Linux, Windows, Mac OS, Solaris 和其他操作系統(tǒng)。

RxTx可以獨(dú)立于JavaComm API使用,也可以作為所謂的Java Comm API服務(wù)者。如果采用后者還需要一個(gè)稱為JCL的封裝包。JCL和RxTx通常與Linux/Java發(fā)行版打包在一起,或者JCL完全與代碼集成在一起。所以,在一個(gè)個(gè)地下載他們之前,看一看Linux發(fā)行版的CD是值得的。


由于Sun對(duì)JavaComm的有限的支持和不適當(dāng)?shù)奈臋n,放棄JavaComm API,轉(zhuǎn)而直接使用RxTx而不是通過JCL封裝包似乎成為了一種趨勢(shì)。然而RxTx的文檔是很稀少的。特別是RxTx開發(fā)者喜歡將他們的版本和包內(nèi)容弄得一團(tuán)糟(例如使用或未使用集成的JCL)。從1.5版本開始,RxTx包含了公共JavaComm類的替代類。由于法律原因,他們沒有在java.comm包中,而是在gui.io包下。然而現(xiàn)存的兩個(gè)版本的打包內(nèi)容有很大差別。

  •     RxTx 2.0
  •     這個(gè)版本的RxTx 主要用作JavaComm提供者。它應(yīng)該源自于RxRx 1.4,這是RxTx添加gui.io包之前的版本。
  •     RxTx 2.1
  •     這個(gè)版本的RxTx包含了一個(gè)完整的代替java.comm的gnu.io包。它應(yīng)該源自于RxTx 1.5,這是支持gnu.io的起始版本。


因此,如果你想對(duì)原始的JavaComm API 編程的話你需要

        Sun JavaComm 通用版。撰寫本文時(shí)實(shí)際上就是Unix包(包含對(duì)各種類Unix系統(tǒng)的支持,像Linux或Solaris)即使在Windows上,這個(gè)Unix包也是需要用來提供java.comm的通用實(shí)現(xiàn)的。只用用Java實(shí)現(xiàn)那部分會(huì)被用到,然而Unix的本地庫(kù)會(huì)被忽略的。

    RxTx 2.0, 為了能在JavaComm通用版本下有不同的提供者,不同于JavaComm包下的那個(gè)。然而,如果你只想用gnu.io替換包,那么你只需要將一個(gè)JavaComm應(yīng)用轉(zhuǎn)換成RxTx應(yīng)用。

如果你是對(duì)Sun公司放棄使JavaComm支持Windows這一行為感到失望的眾多成員中的一個(gè),那么你應(yīng)該將你的JavaComm應(yīng)用轉(zhuǎn)到RxTx上來。如你在上面所看到的,這里有兩種方式來完成這件事,假設(shè)你已經(jīng)安裝了RxTx的某一版本,那么下面的選項(xiàng)可選其一:

  •     使用RxTx 2.0作為JavaComm接口的實(shí)現(xiàn)
  •     將應(yīng)用移植到RxTx 2.1環(huán)境上

上面的第一項(xiàng)在前面已經(jīng)解釋,第二項(xiàng)也相當(dāng)簡(jiǎn)單。對(duì)于需要將JavaComm應(yīng)用移植到RxTx 2.1上來的人,只需要將應(yīng)用源代碼中所有對(duì)“java.comm”包的引用換成“gnu.io”包,如果原始的JavaComm應(yīng)用編寫恰當(dāng),這里就沒有其他的事情需要去做。

在Unix平臺(tái)上,RxTx 2.1甚至提供了工具“contrib/ChangePackage.sh”去在源代碼樹形結(jié)構(gòu)中執(zhí)行全局的替換,這樣的替換在其他的平臺(tái)很容易使用支持重構(gòu)功能的IDE(集成開發(fā)環(huán)境)來完成。

相關(guān)文章

  • Springboot+MyBatist實(shí)現(xiàn)前后臺(tái)交互登陸功能方式

    Springboot+MyBatist實(shí)現(xiàn)前后臺(tái)交互登陸功能方式

    這篇文章主要介紹了Springboot+MyBatist實(shí)現(xiàn)前后臺(tái)交互登陸功能方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • SpringBoot中EasyExcel實(shí)現(xiàn)Excel文件的導(dǎo)入導(dǎo)出

    SpringBoot中EasyExcel實(shí)現(xiàn)Excel文件的導(dǎo)入導(dǎo)出

    這篇文章主要介紹了SpringBoot中EasyExcel實(shí)現(xiàn)Excel文件的導(dǎo)入導(dǎo)出,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • java的三種隨機(jī)數(shù)生成方式的實(shí)現(xiàn)方法

    java的三種隨機(jī)數(shù)生成方式的實(shí)現(xiàn)方法

    這篇文章主要介紹了java的三種隨機(jī)數(shù)生成方式的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • 將15位身份證補(bǔ)全為18位身份證的算法示例詳解

    將15位身份證補(bǔ)全為18位身份證的算法示例詳解

    這篇文章主要給大家介紹了關(guān)于將15位身份證補(bǔ)全為18位身份證算法的相關(guān)資料,文中通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編一起來學(xué)習(xí)學(xué)習(xí)吧。
    2017-06-06
  • Java雜談之重復(fù)代碼是什么

    Java雜談之重復(fù)代碼是什么

    剛開始工作時(shí),總有人開玩笑說,編程實(shí)際上就是 CV,調(diào)侃很多程序員寫程序依靠的是復(fù)制粘貼。至今,很多初級(jí)甚至高級(jí)程序員寫代碼依舊是CV,就是把其他項(xiàng)目里的一段代碼復(fù)制過來,稍加改動(dòng),然后,跑一下沒有大問題就完事。這就是在給其他人挖坑
    2021-09-09
  • Java實(shí)現(xiàn)的簡(jiǎn)單數(shù)字時(shí)鐘功能示例

    Java實(shí)現(xiàn)的簡(jiǎn)單數(shù)字時(shí)鐘功能示例

    這篇文章主要介紹了Java實(shí)現(xiàn)的簡(jiǎn)單數(shù)字時(shí)鐘功能,涉及java日期時(shí)間及JFrame框架圖形界面操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2019-02-02
  • IDEA 開發(fā)多項(xiàng)目依賴的方法(圖文)

    IDEA 開發(fā)多項(xiàng)目依賴的方法(圖文)

    這篇文章主要介紹了IDEA 開發(fā)多項(xiàng)目依賴的方法(圖文),本文講一下關(guān)于使用IntelliJ IDEA基于Maven創(chuàng)建多模塊項(xiàng)目的實(shí)際開發(fā),非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2018-10-10
  • IntellJ idea使用FileWatch實(shí)時(shí)編譯less文件的方法

    IntellJ idea使用FileWatch實(shí)時(shí)編譯less文件的方法

    這篇文章主要介紹了IntellJ idea使用FileWatch實(shí)時(shí)編譯less文件的相關(guān)資料,需要的朋友可以參考下
    2018-02-02
  • Java中文件創(chuàng)建于寫入內(nèi)容的常見方法

    Java中文件創(chuàng)建于寫入內(nèi)容的常見方法

    在日常開發(fā)中,肯定離不開要和文件打交道,今天就簡(jiǎn)單羅列一下平時(shí)比較常用的創(chuàng)建文件并向文件中寫入數(shù)據(jù)的幾種方式,希望對(duì)大家有一定的幫助
    2023-10-10
  • Elasticsearch(ES)多種查詢方式案例

    Elasticsearch(ES)多種查詢方式案例

    Elasticsearch是一個(gè)分布式的RESTful搜索和分析引擎,可讓您輕松地大規(guī)模存儲(chǔ),搜索和分析,這篇文章主要給大家介紹了關(guān)于Elasticsearch(ES)多種查詢方式的相關(guān)資料,需要的朋友可以參考下
    2023-09-09

最新評(píng)論