Java Applet读写client串口——终极篇
測試環(huán)境:
SDK:Oracle JRockit for Java version 6, Java Communication for Windows 2.0
OS:WINDOWS7
外設(shè):串口條形碼掃描槍
Server:Tomcat6
?
看了網(wǎng)上良莠不齊的關(guān)于Applet訪問串口的文章,總結(jié)起來所關(guān)注的問題無外乎下面3個(gè):
1.??? 三個(gè)文件(comm.jar、javax.comm.properties和win32com.dll)究竟應(yīng)該存放在什么文件夾中?
2.??? 怎樣實(shí)現(xiàn)代碼?
3.??? Applet究竟應(yīng)該這么部署?
?
一.關(guān)于第一個(gè)問題,網(wǎng)上大致是這樣寫的:
a)????? 將javax.comm.properties文件放在$JAVA_HOME/lib文件夾中;
b)????? 將win32comm.dll文件放在$JAVA_HOME/bin文件夾中;
c)????? 將comm.jar文件放在$JAVA_HOME/lib/ext文件夾中;
先不去討論這些文件應(yīng)不應(yīng)該放在這些文件夾中,單從可行性方面討論就不太符合WEB應(yīng)用程序的做法。首先您不可能預(yù)知有多少client的存在,就算您預(yù)先知道也不可能在每一個(gè)client計(jì)算機(jī)上部署上述3個(gè)文件。好,您說能夠提供使用手冊引導(dǎo)用戶下載文件并依照手冊將上述文件部署到指定文件夾。可是您添加了用戶的使用學(xué)習(xí)成本,用戶不是IT專家,將本來應(yīng)該由開發(fā)者完畢的任務(wù)轉(zhuǎn)嫁給用戶是否合適呢?
要解決問題,關(guān)鍵是要測試,測試Applet在執(zhí)行時(shí)是這么載入這些文件的。經(jīng)過重復(fù)的測試,最終搞清楚當(dāng)中的來龍去脈:
1.??? javax.comm.properties文件能夠丟棄,由于通過編程的方法能夠在Applet中動(dòng)態(tài)載入串口驅(qū)動(dòng)程序,所以該文件存不存在無所謂。
2.??? comm.jar文件是基本的串口訪問類庫,能夠通過在Applet執(zhí)行時(shí)載入(通過ARCHIVE參數(shù)指定,后面有具體的樣例),所以也沒有必要事先部署到client計(jì)算機(jī)上。
3.??? 最關(guān)鍵的是win32comm.dll文件,該文件是用C寫的串(并)口驅(qū)動(dòng)程序,Java通過JNI調(diào)該文件里的函數(shù)來實(shí)現(xiàn)對串(并)口的訪問。所以此文件不可或缺。要將該文件部署到client僅僅能通過下載的方式實(shí)現(xiàn),即在Applet執(zhí)行時(shí)檢查指定文件夾中是否存在win32comm.dll文件,假設(shè)不存在則將server端的win32comm.dll文件下載到client的指定文件夾中,最后再動(dòng)態(tài)裝載驅(qū)動(dòng)程序。關(guān)于win32comm.dll文件究竟要部署到什么文件夾中,經(jīng)過測試發(fā)現(xiàn)該文件僅僅要存在于由java.library.path系統(tǒng)變量指定的任一文件夾中就可以,該系統(tǒng)變量能夠通過System.getProperty(“java.library.path”)方法獲取。下面是在我的機(jī)器中使用該方法獲取的值:
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;
通過上述分析,我們已經(jīng)理清了三個(gè)文件存放位置,接下來就是怎樣詳細(xì)的實(shí)現(xiàn)代碼了。
二.代碼實(shí)現(xiàn)
a)????? 下載win32comm.dll文件到client:
先看代碼的實(shí)現(xiàn):
private static final String LIB_PATH_SUFFIX = "system32";
private static final String DLL_FILE = "win32com.dll";
try {
// 獲取載入庫時(shí)搜索的路徑列表
??? 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("創(chuàng)建文件完畢[" + dll + "].");
??? }
} catch (MalformedURLException e) {
??? e.printStackTrace();
} catch (IOException e) {
??? e.printStackTrace();
}
這段代碼的主要算法例如以下:
1.??? 通過System.getProperty(“java.library.path”)方法獲取載入庫時(shí)搜索的文件夾字符串,每一個(gè)文件夾之間是用分號隔開的;
2.??? 將文件夾字符串按分號拆分成字符串?dāng)?shù)組,然后取當(dāng)中的任一一個(gè)就可以。只是我喜歡取“C:/Windows/system32”文件夾;
3.??? 然后實(shí)例化一個(gè)File對象,該對象存有指向C:/Windows/system32/win32comm.dll文件的句柄。檢測該文件是否存在,假設(shè)存在則不做不論什么處理,否則進(jìn)行下載處理;
4.??? 假設(shè)須要下載文件,則先通過getCodeBase()方法獲取server端的根URL對象。然后構(gòu)造一個(gè)指向server端win32comm.dll文件的URL對象;
5.??? 通過URL的openConnection().getInputStream()方法獲取InputStream流準(zhǔn)備讀取;
6.??? 在client實(shí)例化一個(gè)FileOutputStream對象,并將輸出流寫入到client的文件里(C:/Windows/system32/win32comm.dll),完畢文件的下載。事實(shí)上文件的下載是通過http協(xié)議完畢的,即httpclient。所以server端須要TOMCAT或其它WEBserver軟件。
b)????? 讀取條形碼
主要實(shí)現(xiàn)Applet的3個(gè)方法: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載入時(shí)運(yùn)行一次且僅一次。所以init方法中適合裝載和初始化驅(qū)動(dòng)程序,即載入win32comm.dll文件。
條碼掃描設(shè)備與調(diào)制解調(diào)器不同,調(diào)制解調(diào)器使用“密步”的通信方式,即請求-應(yīng)答模式。而條碼掃描設(shè)備是事件驅(qū)動(dòng)的,僅僅有在掃描了條碼之后,才干讀取串口的數(shù)據(jù),所以使用請求-應(yīng)答模式肯定不行。為了解決問題,Applet必須實(shí)現(xiàn)SerialPortEventListener接口,以便在有數(shù)據(jù)到達(dá)時(shí)運(yùn)行特定的方法。另外還須要啟動(dòng)一個(gè)線程來等待數(shù)據(jù)的到達(dá)。所以Applet類的簽名例如以下:
public class SerialPortApplet extends JApplet implements Runnable,
?????? SerialPortEventListener {
??? public void run() {
??? }
???
public void serialEvent(SerialPortEvent event) {
??? }
}
當(dāng)中serialEvent(SerialPortEvent event)方法就是須要實(shí)現(xiàn)的接口方法,當(dāng)串口有數(shù)據(jù)到達(dá)時(shí),則會運(yùn)行該方法中的代碼。
start方法在init方法運(yùn)行完成之后運(yùn)行,在Applet的整個(gè)生命周期中,start方法能夠被運(yùn)行多次。所以start方法能夠用來實(shí)現(xiàn)尋找可用port,打開port,設(shè)置port參數(shù),等待數(shù)據(jù)到達(dá)以及數(shù)據(jù)處理等代碼,代碼例如以下所看到的:
private CommPortIdentifier portId;
private StringBuilder barcode = new StringBuilder();
private InputStream is;
private boolean over = false; //退出線程的標(biāo)志
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); // 注冊監(jiān)聽器
?????? serialPort.notifyOnDataAvailable(true); // 數(shù)據(jù)達(dá)到時(shí)發(fā)出通知
????? serialPort.setSerialPortParams(9600, SerialPort.DATABITS_8,SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); // 設(shè)置port參數(shù)
} 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);
??? }
??? // 啟動(dòng)線程
??? Thread t = new Thread(this);
??? t.start();
??? try {
?????? t.join();// 等待線程結(jié)束
??? } catch (InterruptedException e) {
}
System.out.println("barcode[" + barcode + "]");
}
線程接口的實(shí)現(xiàn)和監(jiān)聽器接口的實(shí)現(xiàn)代碼例如以下所看到的:
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: //數(shù)據(jù)到達(dá)時(shí)運(yùn)行
??? ??? try {
?????? ??? while (true) {
?????????? ??? int b = is.read(); // 假設(shè)讀取不到數(shù)據(jù)則會堵塞
????????????? if (b == 10 || b == 13) { // 假設(shè)讀到回車或換行則表示讀取完畢
????????????? ??? over = true;
????????????????? break;
????????????? } else {
????????????????? barcode.append(new String(new byte[] { (byte) b }));
????????????? }
?????????? }
?????? } catch (IOException e) {
?????????? System.err.println(e);
?????? }
??? }
}
destroy方法在Applet銷毀時(shí)運(yùn)行且運(yùn)行一次,所以能夠該方法中編寫資源釋放的代碼,代碼實(shí)現(xiàn)例如以下:
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>
當(dāng)中比較重要的是ARCHIVE參數(shù),能夠?qū)?/span>Applet須要用到的全部jar類庫都在這個(gè)參數(shù)中進(jìn)行設(shè)置,多個(gè)jar文件之間用逗號進(jìn)行分隔。假設(shè)client沒有安裝JRE,則首先會要求用戶下載JRE,正常情況下,下載JRE須要10分鐘左右的時(shí)間。
經(jīng)過上述步驟,我們完畢了全部須要實(shí)現(xiàn)的代碼。將全部Java代碼打包成jar文件,然后和html文件一起部署到TOMCAT6中,啟動(dòng),一切正常,然后很高興的打開瀏覽器,輸入url,雙眼充滿期望的等待令人興奮的一幕。只是……,報(bào)錯(cuò)了!很的沮喪!錯(cuò)誤提示沒有訪問權(quán)限!!郁悶中……
三.部署
Java號稱是最安全的,所以Applet作為在網(wǎng)絡(luò)上能夠隨意傳播的小應(yīng)用程序當(dāng)然更加須要安全。所以在默認(rèn)情況下Applet僅僅能執(zhí)行在JVM的沙箱中。不能訪問client的不論什么資源,包含文件系統(tǒng)的讀寫,網(wǎng)絡(luò)套接字等。所以出現(xiàn)上述錯(cuò)誤理所當(dāng)然,反而證明了Java的確很的安全。
為了走出沙箱,我們必須對Applet進(jìn)行簽名:
1.??? 創(chuàng)建密鑰:
使用例如以下命令進(jìn)行密鑰的創(chuàng)建,這里使用RSA算法而不是Java默認(rèn)的DSA算法
keytool -genkey -alias oakman -keyalg RSA
執(zhí)行時(shí)會要求您輸入密鑰庫的口令,并要求您輸入名字,組織和位置等信息。填好后就會產(chǎn)生密鑰(密鑰文件在用戶文件夾中,找.keystore文件)。
2.??? 從CA訂購簽名證書:
為了從CA訂購簽名證書,你須要從密鑰庫中導(dǎo)出證書簽名申請(CSR文件)。使用例如以下命令
keytool -certreq -alias oakman -file oakman.csr
該命令會在當(dāng)前文件夾中產(chǎn)生一個(gè)oakman.csr文件,然后你能夠用這個(gè)文件以及證明你身份的證明或證件以及數(shù)K的RMB到CA那里申請你的證書(比較常見的CA有Verisign)。假設(shè)申請成功,CA會給你一個(gè)BASE64編碼證書,你就能夠把它導(dǎo)入到密鑰庫中給自己編寫的Applet進(jìn)行簽名了,導(dǎo)入命令例如以下:
keytool -import -alias oakman -file oakman.cer
當(dāng)中oakman.cer就是CA給你的證書。
3.??? 對Applet的jar文件進(jìn)行簽名:
已經(jīng)將CA的證書導(dǎo)入到密鑰庫中,那么就能夠?qū)ψ约壕帉懙?/span>Applet進(jìn)行簽名了(當(dāng)然,先要打包成JAR包),使用例如以下命令:
jarsigner serial_port.jar oakman
jarsigner comm.jar oakman
Applet全部用到的jar文件都要進(jìn)行簽名。
經(jīng)過上述步驟,我們的Applet就能夠走出沙箱了,能夠訪問client的不論什么資源,包含文件系統(tǒng),外設(shè)以及網(wǎng)絡(luò)套接字等。將條形碼掃碼槍接上我筆記本的串口,找了一本書,對著書上的條碼一掃,嗶!!掃描成功,接著在Java控制臺出現(xiàn)一串?dāng)?shù)字,OK!最終搞定。(注:因?yàn)槲夜P記本沒有串口,所以通過USB轉(zhuǎn)串口來模擬出串口,可能是因?yàn)轵?qū)動(dòng)程序的原因,假設(shè)在沒有關(guān)閉串口的情況關(guān)閉IE瀏覽器,則操作系統(tǒng)必定死機(jī),僅僅能手動(dòng)重新啟動(dòng)操作系統(tǒng)。)
??? 當(dāng)然,作為測試,我們沒有必要花數(shù)K的RMB去CA那里申請證書,所以能夠?qū)⒉襟E2省略。在生成密鑰庫之后直接對JAR文件進(jìn)行簽名。
四.部署文件夾
下面為我機(jī)器上TOMCAT中應(yīng)用程序的部署文件夾:
pay
? |-- WEB-INF
?????? |-- web.xml
? |-- comm.jar
? |-- serial_port.jar
? |-- index.html
轉(zhuǎn)載于:https://www.cnblogs.com/mengfanrong/p/4006098.html
總結(jié)
以上是生活随笔為你收集整理的Java Applet读写client串口——终极篇的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: motion
- 下一篇: [13年迁移]firefoxfocus为