Java Applet读写client串口——终极篇
測試環境:
SDK:Oracle JRockit for Java version 6, Java Communication for Windows 2.0
OS:WINDOWS7
外設:串口條形碼掃描槍
Server:Tomcat6
?
看了網上良莠不齊的關于Applet訪問串口的文章,總結起來所關注的問題無外乎下面3個:
1.??? 三個文件(comm.jar、javax.comm.properties和win32com.dll)究竟應該存放在什么文件夾中?
2.??? 怎樣實現代碼?
3.??? Applet究竟應該這么部署?
?
一.關于第一個問題,網上大致是這樣寫的:
a)????? 將javax.comm.properties文件放在$JAVA_HOME/lib文件夾中;
b)????? 將win32comm.dll文件放在$JAVA_HOME/bin文件夾中;
c)????? 將comm.jar文件放在$JAVA_HOME/lib/ext文件夾中;
先不去討論這些文件應不應該放在這些文件夾中,單從可行性方面討論就不太符合WEB應用程序的做法。首先您不可能預知有多少client的存在,就算您預先知道也不可能在每一個client計算機上部署上述3個文件。好,您說能夠提供使用手冊引導用戶下載文件并依照手冊將上述文件部署到指定文件夾。可是您添加了用戶的使用學習成本,用戶不是IT專家,將本來應該由開發者完畢的任務轉嫁給用戶是否合適呢?
要解決問題,關鍵是要測試,測試Applet在執行時是這么載入這些文件的。經過重復的測試,最終搞清楚當中的來龍去脈:
1.??? javax.comm.properties文件能夠丟棄,由于通過編程的方法能夠在Applet中動態載入串口驅動程序,所以該文件存不存在無所謂。
2.??? comm.jar文件是基本的串口訪問類庫,能夠通過在Applet執行時載入(通過ARCHIVE參數指定,后面有具體的樣例),所以也沒有必要事先部署到client計算機上。
3.??? 最關鍵的是win32comm.dll文件,該文件是用C寫的串(并)口驅動程序,Java通過JNI調該文件里的函數來實現對串(并)口的訪問。所以此文件不可或缺。要將該文件部署到client僅僅能通過下載的方式實現,即在Applet執行時檢查指定文件夾中是否存在win32comm.dll文件,假設不存在則將server端的win32comm.dll文件下載到client的指定文件夾中,最后再動態裝載驅動程序。關于win32comm.dll文件究竟要部署到什么文件夾中,經過測試發現該文件僅僅要存在于由java.library.path系統變量指定的任一文件夾中就可以,該系統變量能夠通過System.getProperty(“java.library.path”)方法獲取。下面是在我的機器中使用該方法獲取的值:
C:/Program Files/Java/jrmc-3.1.2-1.6.0/bin;.;C:/Windows/system32;C:/Windows;C:/Program Files/Java/jrockit-R27.6.5-jre1.6.0_14/bin;C:/Windows/system32;C:/Windows;C:/Windows/System32/Wbem;C:/Windows/System32/WindowsPowerShell/v1.0/;C:/Program Files/ATI Technologies/ATI.ACE/Core-Static;C:/Program Files/Toshiba/Bluetooth Toshiba Stack/sys/;C:/Program Files/SecureCRT/;C:/Program Files/MySQL/MySQL Server 5.1/bin;C:/Tomcat6.0/bin;E:/software/java/jdk/ibm_sdk60/bin;C:/MinGW/bin;C:/Program Files/Java/jrmc-3.1.2-1.6.0/bin;
通過上述分析,我們已經理清了三個文件存放位置,接下來就是怎樣詳細的實現代碼了。
二.代碼實現
a)????? 下載win32comm.dll文件到client:
先看代碼的實現:
private static final String LIB_PATH_SUFFIX = "system32";
private static final String DLL_FILE = "win32com.dll";
try {
// 獲取載入庫時搜索的路徑列表
??? String dirs = System.getProperty("java.library.path");
??? String[] libs = dirs.split(";");
??? String libPath = "";
??? for (String lib : libs) {
??? ??? if (lib.toLowerCase().endsWith(LIB_PATH_SUFFIX)) {
?????? ??? libPath = lib;
?????????? break;
?????? }
??? }
??? File dll = new File(libPath, DLL_FILE);
?????? if (!dll.exists()) {
??? ??? URL url = new URL(super.getCodeBase() + DLL_FILE);
?????? InputStream is = url.openConnection().getInputStream();
?????? FileOutputStream fos = new FileOutputStream(dll);
?????? byte[] buf = new byte[256]; // 讀取緩存
?????? int len = 0;
?????? while ((len = is.read(buf)) != -1) {
?????? ??? fos.write(buf, 0, len);
?????? }
?????? fos.flush();
?????? fos.close();
?????? is.close();
?????? System.out.println("創建文件完畢[" + dll + "].");
??? }
} catch (MalformedURLException e) {
??? e.printStackTrace();
} catch (IOException e) {
??? e.printStackTrace();
}
這段代碼的主要算法例如以下:
1.??? 通過System.getProperty(“java.library.path”)方法獲取載入庫時搜索的文件夾字符串,每一個文件夾之間是用分號隔開的;
2.??? 將文件夾字符串按分號拆分成字符串數組,然后取當中的任一一個就可以。只是我喜歡取“C:/Windows/system32”文件夾;
3.??? 然后實例化一個File對象,該對象存有指向C:/Windows/system32/win32comm.dll文件的句柄。檢測該文件是否存在,假設存在則不做不論什么處理,否則進行下載處理;
4.??? 假設須要下載文件,則先通過getCodeBase()方法獲取server端的根URL對象。然后構造一個指向server端win32comm.dll文件的URL對象;
5.??? 通過URL的openConnection().getInputStream()方法獲取InputStream流準備讀取;
6.??? 在client實例化一個FileOutputStream對象,并將輸出流寫入到client的文件里(C:/Windows/system32/win32comm.dll),完畢文件的下載。事實上文件的下載是通過http協議完畢的,即httpclient。所以server端須要TOMCAT或其它WEBserver軟件。
b)????? 讀取條形碼
主要實現Applet的3個方法:init、start和destroy方法。先看init方法:
private String driverName = "com.sun.comm.Win32Driver";
public void init() {
try {
System.loadLibrary("win32com");
??? ??? CommDriver driver = (CommDriver)Class.forName(driverName).newInstance();
driver.initialize();
} catch (Exception e) {
System.err.println(e);
}
}
init方法在Applet載入時運行一次且僅一次。所以init方法中適合裝載和初始化驅動程序,即載入win32comm.dll文件。
條碼掃描設備與調制解調器不同,調制解調器使用“密步”的通信方式,即請求-應答模式。而條碼掃描設備是事件驅動的,僅僅有在掃描了條碼之后,才干讀取串口的數據,所以使用請求-應答模式肯定不行。為了解決問題,Applet必須實現SerialPortEventListener接口,以便在有數據到達時運行特定的方法。另外還須要啟動一個線程來等待數據的到達。所以Applet類的簽名例如以下:
public class SerialPortApplet extends JApplet implements Runnable,
?????? SerialPortEventListener {
??? public void run() {
??? }
???
public void serialEvent(SerialPortEvent event) {
??? }
}
當中serialEvent(SerialPortEvent event)方法就是須要實現的接口方法,當串口有數據到達時,則會運行該方法中的代碼。
start方法在init方法運行完成之后運行,在Applet的整個生命周期中,start方法能夠被運行多次。所以start方法能夠用來實現尋找可用port,打開port,設置port參數,等待數據到達以及數據處理等代碼,代碼例如以下所看到的:
private CommPortIdentifier portId;
private StringBuilder barcode = new StringBuilder();
private InputStream is;
private boolean over = false; //退出線程的標志
private SerialPort serialPort;
static {
??? System.setSecurityManager(null); //禁用安全管理器(必須寫)
}
public void start() {
??? Enumeration ports = CommPortIdentifier.getPortIdentifiers();
??? while (ports.hasMoreElements()) {
??? ??? portId = (CommPortIdentifier) ports.nextElement();
?????? if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) { // 是串口
?????? ??? if (portId.getName().equals("COM1")) {
?????????? ??? break;
?????????? }
?????? }
??? }
??? try {
??? ??? serialPort = (SerialPort) portId.open("App1",2000); // 打開port
?????? is = serialPort.getInputStream();
?????? serialPort.addEventListener(this); // 注冊監聽器
?????? serialPort.notifyOnDataAvailable(true); // 數據達到時發出通知
????? serialPort.setSerialPortParams(9600, SerialPort.DATABITS_8,SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); // 設置port參數
} catch (PortInUseException e) {
??? ??? System.err.println(e);
??? } catch (IOException e) {
?????? System.err.println(e);
??? } catch (TooManyListenersException e) {
?????? System.err.println(e);
??? } catch (UnsupportedCommOperationException e) {
?????? System.err.println(e);
??? }
??? // 啟動線程
??? Thread t = new Thread(this);
??? t.start();
??? try {
?????? t.join();// 等待線程結束
??? } catch (InterruptedException e) {
}
System.out.println("barcode[" + barcode + "]");
}
線程接口的實現和監聽器接口的實現代碼例如以下所看到的:
public void run() {
??? while (!over) {
??? }
try {
?????? if (is != null) {
?????????? is.close();
?????? }
?????? if (serialPort != null) {
?????????? serialPort.close();
?????? }
??? } catch (IOException e) {
?????? System.out.println(e);
??? }
}
?
public void serialEvent(SerialPortEvent event) {
??? switch (event.getEventType()) {
??? case SerialPortEvent.DATA_AVAILABLE: //數據到達時運行
??? ??? try {
?????? ??? while (true) {
?????????? ??? int b = is.read(); // 假設讀取不到數據則會堵塞
????????????? if (b == 10 || b == 13) { // 假設讀到回車或換行則表示讀取完畢
????????????? ??? over = true;
????????????????? break;
????????????? } else {
????????????????? barcode.append(new String(new byte[] { (byte) b }));
????????????? }
?????????? }
?????? } catch (IOException e) {
?????????? System.err.println(e);
?????? }
??? }
}
destroy方法在Applet銷毀時運行且運行一次,所以能夠該方法中編寫資源釋放的代碼,代碼實現例如以下:
public void destroy() {
??? try {
?????? if (is != null) {
?????????? is.close();
?????? }
?????? if (serialPort != null) {
?????????? serialPort.close();
?????? }
??? } catch (IOException e) {
?????? System.out.println(e);
??? }
}
c)????? HTML頁面(index.html)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
<title></title>
</head>
<body>
<!--"CONVERTED_APPLET"-->
<!-- HTML CONVERTER -->
<OBJECT classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"
WIDTH = "250" HEIGHT = "140"? codebase="http://java.sun.com/update/1.6.0/jinstall-6u17-windows-i586.cab#Version=6,0,0,4">
<PARAM NAME = CODE VALUE = "org.oakman.applets.SerialPortApplet.class" >
<PARAM NAME = CODEBASE VALUE = "." >
<PARAM NAME = ARCHIVE VALUE = "comm.jar, serial_port.jar" >
<PARAM NAME="type" VALUE="application/x-java-applet;version=1.6">
<PARAM NAME="scriptable" VALUE="false">
<COMMENT>
<EMBED type="application/x-java-applet;version=1.6"? CODE = "org.oakman.applets.SerialPortApplet.class" CODEBASE = "." ARCHIVE = " comm.jar,serial_port.jar" WIDTH = "250" HEIGHT = "140"? scriptable=false pluginspage="http://java.sun.com/products/plugin/index.html#download"><NOEMBED>
</NOEMBED>
</EMBED>
</COMMENT>
</OBJECT>
<!--"END_CONVERTED_APPLET"-->
</body>
</html>
當中比較重要的是ARCHIVE參數,能夠將Applet須要用到的全部jar類庫都在這個參數中進行設置,多個jar文件之間用逗號進行分隔。假設client沒有安裝JRE,則首先會要求用戶下載JRE,正常情況下,下載JRE須要10分鐘左右的時間。
經過上述步驟,我們完畢了全部須要實現的代碼。將全部Java代碼打包成jar文件,然后和html文件一起部署到TOMCAT6中,啟動,一切正常,然后很高興的打開瀏覽器,輸入url,雙眼充滿期望的等待令人興奮的一幕。只是……,報錯了!很的沮喪!錯誤提示沒有訪問權限!!郁悶中……
三.部署
Java號稱是最安全的,所以Applet作為在網絡上能夠隨意傳播的小應用程序當然更加須要安全。所以在默認情況下Applet僅僅能執行在JVM的沙箱中。不能訪問client的不論什么資源,包含文件系統的讀寫,網絡套接字等。所以出現上述錯誤理所當然,反而證明了Java的確很的安全。
為了走出沙箱,我們必須對Applet進行簽名:
1.??? 創建密鑰:
使用例如以下命令進行密鑰的創建,這里使用RSA算法而不是Java默認的DSA算法
keytool -genkey -alias oakman -keyalg RSA
執行時會要求您輸入密鑰庫的口令,并要求您輸入名字,組織和位置等信息。填好后就會產生密鑰(密鑰文件在用戶文件夾中,找.keystore文件)。
2.??? 從CA訂購簽名證書:
為了從CA訂購簽名證書,你須要從密鑰庫中導出證書簽名申請(CSR文件)。使用例如以下命令
keytool -certreq -alias oakman -file oakman.csr
該命令會在當前文件夾中產生一個oakman.csr文件,然后你能夠用這個文件以及證明你身份的證明或證件以及數K的RMB到CA那里申請你的證書(比較常見的CA有Verisign)。假設申請成功,CA會給你一個BASE64編碼證書,你就能夠把它導入到密鑰庫中給自己編寫的Applet進行簽名了,導入命令例如以下:
keytool -import -alias oakman -file oakman.cer
當中oakman.cer就是CA給你的證書。
3.??? 對Applet的jar文件進行簽名:
已經將CA的證書導入到密鑰庫中,那么就能夠對自己編寫的Applet進行簽名了(當然,先要打包成JAR包),使用例如以下命令:
jarsigner serial_port.jar oakman
jarsigner comm.jar oakman
Applet全部用到的jar文件都要進行簽名。
經過上述步驟,我們的Applet就能夠走出沙箱了,能夠訪問client的不論什么資源,包含文件系統,外設以及網絡套接字等。將條形碼掃碼槍接上我筆記本的串口,找了一本書,對著書上的條碼一掃,嗶!!掃描成功,接著在Java控制臺出現一串數字,OK!最終搞定。(注:因為我筆記本沒有串口,所以通過USB轉串口來模擬出串口,可能是因為驅動程序的原因,假設在沒有關閉串口的情況關閉IE瀏覽器,則操作系統必定死機,僅僅能手動重新啟動操作系統。)
??? 當然,作為測試,我們沒有必要花數K的RMB去CA那里申請證書,所以能夠將步驟2省略。在生成密鑰庫之后直接對JAR文件進行簽名。
四.部署文件夾
下面為我機器上TOMCAT中應用程序的部署文件夾:
pay
? |-- WEB-INF
?????? |-- web.xml
? |-- comm.jar
? |-- serial_port.jar
? |-- index.html
轉載于:https://www.cnblogs.com/mengfanrong/p/4006098.html
總結
以上是生活随笔為你收集整理的Java Applet读写client串口——终极篇的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: motion
- 下一篇: [13年迁移]firefoxfocus为