现在的位置: 首页 > 综合 > 正文

在浏览器中和本地计算机串口进行通讯

2013年09月30日 ⁄ 综合 ⁄ 共 11307字 ⁄ 字号 评论关闭

—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

 

抱歉!评论已关闭.