—Applet+Javascript实现B/S读取二维条码扫描枪信息
(一) 背景 2
(二) Applet实现思路 2
(三) Java Windows串口编程简介 4
(四) Applet + Javascript实现细节 5
(五) Applet数字签名制作步骤 13
(六) 二维条码扫描枪客户端程序使用说明(B/S版) 15
(七) 参考资料 17
(一) 背景
根据公司省府办二维条码项目的要求,需要达到以下的功能:在B/S构架的WEB应用中,能够在客户端的浏览器中,通过二维条码扫描枪,把二维条码中包含的信息读到浏览器中的各个输入区域。
根据前期项目已经开发的功能,我们已经完成了生成国家标准的公文交换PDF417二维条码,这种条码中包含多种信息,如标题、发文单位、发文时间、内容等信息,在WEB应用中,应用的方式类似以下描述:在浏览器中,打开的HTML页面包括各种输入区域,如标题输入区域、内容输入区域等,通过扫描抢,扫出二维条码中包含的信息,自动填入相应的输入区域中。扫描枪接在客户端计算机的串口上。
经过2天的摸索,本功能的程序已经开发实现,在摸索的过程中,发现Internet上这类技术的可用资料非常少,并且大多都是英文的资料,阅读起来很费劲。另外,这样的应用方式肯定不是最后一个,马上公司的新项目海南省行政审批中还会用到,所以把这个技术以文挡方式描述清楚,以方便日后使用。
(二) Applet实现思路
根据上面的需求描述知道,要满足需求,需要完成2个步骤:首先,必须有程序通过和本地计算机进行串口通讯,通过串口读出扫描枪扫出来的二维条码包含的全部信息(IO操作);第二,读出的条码信息,必须能够送到浏览器脚本语言控制区,通过脚本语言把信息分到各个输入区域中。
Java处理IO操作有先天的优势,所以上述第一个环节,我们理所当然的想到了使用Java进行串口通讯读出扫描枪中所扫描到的二维条码信息,但串口是位于客户端计算机上,在B/S结构的WEB应用中,因此,采用Applet的Java代码进行串口通讯。
为什么要采用Applet,还有另外的一个重要原因。在上面的描述中,还有第二个环节,需要把IO操作的信息,传输到客户端脚本语言控制区域(客户端脚本语言一般选择Javascript),Applet能够和Javascript进行通讯是第一个环节采用Applet的另外一个重要原因。
以上描述可以总结为:Applet读取连接扫描枪的COM口(即串口)的二维条码信息,Javascript获取到Applet读到的信息后,进行相应处理,图示如下:
Applet实现需要考虑的问题是,因为Applet的安全性要求,在普通情况下,Applet是不允许访问本地计算机的资源的,比如,一个Applet不能去访问本地的文件系统,否则,随便在Internet上放一个Applet,把客户端的C盘文件全部删除,那还得了!但是,Applet的设计者并没有全部抑制这些功能,通过其他机制,是可以访问到本地的资源的,串口作为本地资源,就需要用到这种机制,这种机制就是数字签名。对Applet进行数字签名后,可以访问到本地资源。
应该指出,以上的实现思路并不是唯一的可实现的思路,我猜想(没有实践过),javascript+ActiveX,即用ActiveX代替Applet,也能达到相同的效果,我估计,ActiveX也能和javascript脚本语言进行通讯,原理和Applet应该类似。另外,技术总监提出第三种思路:在客户端使用C开发一个读串口的程序(理由是C读串口很方便),然后通过键盘防真技术,把扫描枪的输入模拟成为键盘输入达到输入效果,由于能力和时间的关系,没有被选择,但这是个很好的思路,日后有时间很值得去研究。
(三) Java Windows串口编程简介
Java的IO操作有先天的优势,Java的IO操作是Java的核心之一,串口通讯作为Java IO操作中的一种,已经被封装的非常良好,使用Java进行串口通讯,程序员的工作变得非常简单。
Java进行串口通讯,可以归结成为以下5个过程:
1、 得到串口
2、 OPEN串口
3、 设置串口
4、 得到输入(输出)流,通过流操作,进行串口通讯
5、 CLOSE串口
串口操作包位于comm.jar,这个一个扩展的java包,在标准的JDK中并没有包含这个包,需要额外下载,另外,Java串口通讯还需要用到win32com.dll,在Sun下载comm.jar时,会包含这个dll。
串口通讯的5个过程,在Java 代码中,实现如下:
//第一步:获取串口
CommPortIdentifier portId = CommPortIdentifier.getPortIdentifier("COM1");
//第二步:OPEN串口
SerialPort serialPort = (SerialPort)portId.open("Serial_Communication", 2000);
//第三步:设置串口
serialPort.setSerialPortParams(
9600,
SerialPort.DATABITS_8,
SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
//第四步:获取IO流,进行流操作
InputStream is = serialPort.getInputStream();
……
//第五步:关闭串口
serialPort.close();
Java串口通讯的资料在Internet中可以找到很多,本文的重点不在于此,各位需要了解更多这方面的资料,可以在Internet中得到。
(四) Applet + Javascript实现细节
根据以上的描述,来看看实现的细节:
串口通讯有个特点,连接上去以后,它发送过来的信息,程序是不知道什么时候是结束的,因为连接上串口以后,串口的输入输出流一直是间断进行数据传送的,永远没有结束(代码中就是InputStream.read()永远不会有-1),除非人为中断它,否则它一直保持连接。如果只有在Applet的主线程中进行串口通讯,那么输入输出流操作永远也不能退出,其他代码永远也没有机会运行。最好的办法是使用多线程(多线程有是Java的另外一个核心优势,现在是越来越喜欢Java了),在Applet的主线程中启动工作线程,该线程专门负责读串口信息,把读到的信息送到Applet的属性msg中,msg将被javascript读走。另外,javascript可以和applet通讯,但applet不能和javascript通讯(我目前的理解是如此,不知道有没有错误?),所以,applet是否读到信息,不能主动通知javascript,必须由javascript每隔一段时间来查看,在applet中设置一个布尔变量ready,javascript每隔一定的时间就来查看该变量,如果为真,则表示applet又读到了一个输入信息,此时,javascript可以拿走该信息,并把ready属性更新为false,以免javascript下次查看applet时,又把已读过的信息读出。
根据细节描述,代码实现就很容易了,先来看看Applet代码,Applet代码包含2个Java类:LocalFileApplet和JobThread,前者为Applet主程序,后者为工作线程。
LocalFileApplet.java代码如下:
-------------------------------------------LocalFileApplet.java--------------------------------------------------------------
import javax.comm.*;
import java.applet.Applet;
import java.awt.*;
import java.io.*;
import java.util.Enumeration;
/**
* User: yrw <br>
* Date: 2006-8-17 <br>
* Time: 8:55:40 <br>
*/
public class LocalFileApplet extends Applet {
static{
System.setSecurityManager(null);
String drivername = "com.sun.comm.Win32Driver";
try {
CommDriver driver = (CommDriver) Class.forName(drivername).newInstance();
driver.initialize();
} catch (InstantiationException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
} catch (IllegalAccessException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
} catch (ClassNotFoundException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
private String msg = "";
private boolean ready;
CommPortIdentifier portId ;
SerialPort serialPort;
InputStream is;
public CommPortIdentifier getPortId() {
return portId;
}
public void setPortId(CommPortIdentifier portId) {
this.portId = portId;
}
public SerialPort getSerialPort() {
return serialPort;
}
public void setSerialPort(SerialPort serialPort) {
this.serialPort = serialPort;
}
public InputStream getIs() {
return is;
}
public void setIs(InputStream is) {
this.is = is;
}
public boolean isReady() {
return ready;
}
public void setReady(boolean ready) {
this.ready = ready;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public void paint(Graphics g ){
}
public void initPort(){
try {
portId = CommPortIdentifier.getPortIdentifier("COM1");
serialPort = (SerialPort)portId.open("Serial_Communication", 2000);
serialPort.setSerialPortParams(9600, SerialPort.DATABITS_8,SerialPort.STOPBITS_1,SerialPort.PARITY_NONE);
is = serialPort.getInputStream();
} catch (NoSuchPortException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
} catch (PortInUseException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
} catch (UnsupportedCommOperationException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
} catch (IOException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
public void cleanPort(){
try {
is.close();
} catch (IOException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
serialPort.close();
}
public void init(){
initPort();
JobThread job = new JobThread(this);
Thread t = new Thread(job);
t.start();
this.setBounds(0,0,0,0);
this.repaint();
}
public void start(){
}
public void stop(){
cleanPort();
}
private void showPorts(Graphics g ){
g.drawString("扫描本地计算机上的串口:!",10,10);
java.util.List ports = listPorts();
if(ports.size()==0){
g.drawString("本地计算机上没有发现串口:!",10,30);
}else{
int y = 50;
g.drawString("本地计算机上发现"+ports.size()+"个串口:!",10,30);
for (int i = 0; i < ports.size(); i++) {
String port = (String) ports.get(i);
g.drawString(port,10,y);
y += 20;
}
}
}
private java.util.List listPorts(){
java.util.List ports = new java.util.ArrayList();
Enumeration en = CommPortIdentifier.getPortIdentifiers();
CommPortIdentifier portId;
while (en.hasMoreElements()) {
portId = (CommPortIdentifier) en.nextElement();
if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL){
ports.add(portId.getName());
}
}
return ports;
}
}
--------------------------------------LocalFileApplet.java--------------------------------------------------------
这个类中,有两个测试方法:listPorts和showPorts,是用来扫描本地计算机上的串口的,本项目中并没有使用到它们。本类中,值得注意的地方有2个:在static代码段中,System.setSecurityManager(null);使得经过数字签名的applet能够顺利访问本地资源,CommDriver driver = (CommDriver) Class.forName(drivername).newInstance();通过手工方式加载串口驱动,免去了去客户端加载驱动配置文件javax.comm.properties可能会引起的路径问题。
JobThread.java代码:
--------------------------------JobThread.Java------------------------------------------------------------
import java.io.InputStream;
import java.io.IOException;
/**
* User: yrw <br>
* Date: 2006-8-17 <br>
* Time: 15:12:42 <br>
*/
public class JobThread implements Runnable{
private LocalFileApplet applet;
public JobThread(LocalFileApplet applet) {
this.applet = applet;
}
public void run() {
InputStream is = applet.getIs();
byte[] buffer = new byte[1024];
try {
while(true){
int ch;
int index=0;
while((ch = is.read())!=-1){
System.out.println(ch);
if(ch==10){
if(buffer[index-1]==13)
break;
}
buffer[index++]=(byte) ch;
}
byte[] bytes = new byte[index-1];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = buffer[i];
}
String msg = new String(bytes);
applet.setMsg(msg);
applet.setReady(true);
System.out.println(msg);
applet.repaint();
}
} catch (IOException e) {
e.printStackTrace();
}
}
} ---------------------------------------------JobThread.Java------------------------------------------------------------
JobThread.java就更简单的,几乎没有什么可以描述的,扫描枪每次扫描都会在最后送来一个回车换行符(ASCII码为1310),所以我们通过回车换行符号决定一次扫描结束。
脚本代码:applet.html:
--------------------------------------------- applet.html ------------------------------------------------------------
<!------------------------------------------------------------------------------------------>
<!--
串口扫描枪扫描Applet程序,这段代码调用程序员不能改动!
-->
<OBJECT classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93" id="pdf417reader" width="0" height="0">
<PARAM NAME="java_code" VALUE="LocalFileApplet">
<PARAM NAME="java_codebase" VALUE=".">
<PARAM NAME="java_type" VALUE="application/x-java-applet;version=1.4">
<PARAM NAME="ARCHIVE" VALUE="LocalFileApplet.jar" >
<PARAM NAME="scriptable" VALUE="true">
</OBJECT>
<script type="text/javascript">
function process(){
var ready = pdf417reader.isReady();
if(ready){
pdf417reader.setReady(false);
var msg = pdf417reader.getMsg();
htmlDoJob(msg);
}
setTimeout('process()',35);
}
process();
</script>
<!--
以上代码是串口扫描枪扫描Applet程序,调用程序员不能改动!
-->
<!------------------------------------------------------------------------------------------>
<textarea name="msgId" cols=70 rows=20></textarea>
<script>
/**
* 这个函数是js处理函数,当扫描枪读到信息后,自动调用该函数,msg是信息
* 这个函数由调用程序员完成
*/
function htmlDoJob(msg){
document.all.msgId.value=document.all.msgId.value+"\n"+msg;
}
</script>
---------------------------applet.html------------------------------------------------------------
脚本代码也很简单,这里需要注意的是,applet是必须要经过数字签名的,经过数字签名的applet不能使用<applet这样的方式调用,要通过ActiveX方式调用,就如上面的代码<object…,<applet调用方式和<object调用方式之间是可以通过转换工具来自动转换的,如需要深究请查阅相关资料。
(五) Applet数字签名制作步骤
访问本地计算机资源的Applet需要经过数字签名,在上面的Applet程序中,需要用到comm.jar包,这个包也要经过数字签名,所以,可以把comm.jar解压缩,把清单文件和目录(MANIFEST.MF和META-INF)去掉,和自己编写的java类一起打成jar包,经数字签名,发布给用户。
步骤如下:
第一步:产生jar包,命令:
jar cvf LocalFileApplet.jar *.*
第二步:为jar包文件创建keystore和keys。其中,keystore将用来存放密匙(private keys)和公共钥匙的认证,alias为别名,命令:
keytool -genkey -keystore LocalFileApplet.keystore -alias LocalFileApplet
此命令生成了一个名为LocalFileApplet.keystore的keystore文件,接着这条命令,系统会问你好多问题,比如你的公司名称,你的地址,你要设定的密码等等,都由自己的随便写。密码将在第三步和第四步中使,其他信息提供给客户端用户,知道这个数字签名是谁提供的。
第三步:对jar包进行签名,命令:
jarsigner -keystore LocalFileApplet.keystore LocalFileApplet.jar LocalFileApplet
第四步:导出认证文件,命令:
keytool -export -keystore LocalFileApplet.keystore -alias LocalFileApplet -file LocalFileApplet.cer
这个步骤将生成LocalFileApplet.cer文件,发布时,把该文件同jar文件和applet.html文件放在同一个目录即可。
关于applet数字签名,可以参考以下地址,这些地址的描述有可能和本文描述有些差异:
http://www.sace.cn/exams/it/java/view/5044.html
http://www.yesky.com/105/1867605.shtml