Linux平台,使用JavaComm3 API及SMSLib项目实现在Web Application中发送手机短信的功能

一、需求:

     在一个Flash程序中通过点击程序中的数字键盘,输入手机号码,提交后通过http请求

例如:http://localhost:8080/golf/stterm/im.jsp?email=yl%2Ewang%40elitechmedia%2Ecom&mobileNumber=_phoneNumber,将信息提交到一个Web服务,完成发送短信到指定手机号码和发送Email到指定的Address的功能。

二、平台:Redhat5企业版

      服务器:apache-tomcat-6.0.26

      JDK:1.6

      Modem:华为E176g3G无线网卡

三、调查

   1)通过调查得知java类库中已实现了相关的串口通信API,通过JNI接口访问本地代码来实现串口通信。可以在以下网址获得详细信息

http://java.sun.com/products/javacomm/,串口通信问题得以解决。

   2)通过SMSLib库实现Java程序发送短信

    SMS(Short Messaging Service)即短消息业务,是由Etsi所制定的一个规范(GSM 03.40 和 GSM03.38)。当使用其7-bits编码时,可以发送最多160个字符;使用8-bit编码,最多可以发送140个字符,通常无法直接通过手机显 示;还有用16-bit编码时,最多70个字符,被用来显示Unicode(UCS2)文本信息,可以被大多数的手机所显示。

目前程序中发送短信大致有三种途径:
1、 向当地的电信部门申请网关,不需要额外的设备,利用对方提供的API调用程序发送短信,适用于大型的通信公司。
2、 借助像GSM MODEM之类的设置(支持AT指令的手机也行),通过数据线连接电脑来发送短信,这种方法比较适用于小公司及个人。要实现这种方式必须理解串口通信、AT指令、短信编码、解码。
3、 利用网站实现,由网站代发短信数据,对网站依赖性太高,对网络的要求也比较高,不适于进行项目开发。
 

    所谓AT,即Attention。AT命令集是从Terminal Equipment或Data Terminal Equipment向Terminal Adapter或Data Circuit Terminating Equipment发送的,通过TA、TE发送AT命令来控制Mobile Station的功能与GSM网络业务进行交互。我们可以通过AT命令进行呼叫短信、电话本、数据业务、补充业务、传真等方面的控制。 由于AT指令操作是非常之简单的,我们完全可以自己写组件完成相关操作,而且针对联通、移动、小灵通等不同的服务需求,自制组件反而更容易控制及扩充。

在Java编程中可以通过Java Comm进行手机与电脑的串口通讯,并通过AT指令控制手机操作。在Google code上有个SMSLib项目,是一个以AT指令实现手机操作的组件(http://code.google.com/p/smslib/downloads/list)。

至此,有了SMSLib项目,解决了许多问题,不用自己去写底层的 AT指令,方便了很多,感谢SMSLib项目作出的贡献。

 

四、开始

    将以上两个项目相关的类库下载后,按照Doc中的安装指导完成安装。只要按照文档正确的安装,即可实现发送短信的功能。在这里我想写的重点是在开发中遇到的一些问题。至于类库的使用请参照文档,这里就不再详细说明了

    需要注意的是,Java Comm2只支持Windows平台串口通信,如果目标平台为Linux,需要下载Java Comm3类库,或者使用开源类库RXTX替代Java Comm3。RXTX的安装可参考如下资源:

http://www.agaveblue.org/howtos/Comm_How-To.shtml

    以下是程序运行中可能出现的一些异常,以及我总结的异常处理方法

 (1)Windows平台,在java工程中运行正常,移植到web application,连接串口就会报告javax.comm.NoSuchPortException

     折腾了很久,收到一个帖子的启发,提到了内置和外置的tomcat,其实问题还是出在jdk版本的问题,smslib必须用jdk1.6的版本才能正常运行,在myeclipse内置的tomcate中无法启动,那是因为内置tomcat采用了jdk1.5.

    config内置的tomcat,可以看到jdk采用的是myclipse内置的jdk,把它修改为1.6的jdk,或者配置外置的tomcat,都可以解决问题。

(2)报告org.smslib.GatewayException: Comm library exception: java.lang.RuntimeException: java.io.IOException: Error setting serial port parameters

   在Linux平台运行程序,抛出以上异常,是因为此3G网卡已经拨上了号,在使用中,使用ifdown ppp0命令,停掉3G后,再运行即可正常使用。

   同样的原因在Windows平台将抛出

org.smslib.GatewayException: Comm library exception: java.lang.RuntimeException: javax.comm.PortInUseException: Port currently owned by Unknown Windows Application

异常,解决方法同样是停掉3G网卡

  可见串口被占用后其他程序就不能使用了

  ps:奇怪的是,同样的原因在Linux和Windows平台抛出不同的异常。

(3)解决了(1)中的问题后,在Windows平台的Web application中可正常运行,于是决定将此Web Application移植到Linux平台,这也是我的终极目标。本来想问题不大,事实是,在移植过程中遇到了很大的困难。

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

按照文档中的要求使用Java Comm3替换了Java Comm2,然后将此Web Application发布到tomcat下运行,出现如下错误:

javax.comm:  Error loading javax.comm.properties!

null

javax.comm: platform driver class name = null

(Check 'driver' property in javax.comm.properties)

Googling了2天,居然没找到一个解决方案,郁闷中。。。

 

没办法了,先试试Linux平台下Java application能不能正常运行。于是下载了myeclipse的linux版本,安装,写程序,运行,No Problem。由此分析,应该是Java项目和Java Web Application在运行上的某种机制不同。有人提到Tomcat的沙箱安全机制导致了从IE中运行程序不能访问javax.comm.properties文件导致了此问题。

参考:http://www.sftw.umac.mo/~utako/research/SmartTicket/collect/applets-faq-qna.html

 

虽然有这个可能,但是,没有人提出一个解决方案。最后想到可从Java Comm3的源代码中找问题。

使用java Decompiler反编译了抛出异常的类CommPortIdentifier,CommProperties

终于弄清楚了,原来由CommProperties类执行加载javax.comm.properties文件,读取配置文件中设置的属性。之后CommPortIdentifier类通过读取的driver加载相应的驱动类。

再查看Linux平台javax.comm.properties文件中的内容,其中设置了以下属性:

××××××××××××××××××××××××××××××××××××××××××××××

# Implementation specific driver
driver=com.sun.comm.LinuxDriver                    (1)

# Paths to server-side serial port devices
serpath0 = /dev/ttyUSB0                                  (2)我修改过
serpath1 = /dev/ttyUSB1

# Paths to server-side parallel port devices
parpath0 = /dev/parport0                                 (3)
parpath1 = /dev/parport1

××××××××××××××××××××××××××××××××××××××××××××××

(1)处为需要加载的串口驱动的类名,此类在Java Comm3中,如果是使用RXTX的话,此处的值需要改为

   gnu.io.RXTXCommDriver

(2)处属性设置了Modem在linux平台的串口设备符,原版的javax.comm.properties文件中此处的值为:

××××××××××××××××××××××

serpath0 = /dev/ttyS0
serpath1 = /dev/ttyS1

××××××××××××××××××××××

/dev/ttyS0,ttyS1是Modem设备在Linux平台的标准设备符。由于我使用的华为E176G映射到Linux平台的设备符为

/dev/ttyUSB_utps_modem,

所以我创建了一个link,将

/dev/ttyUSB0

映射为

dev/ttyUSB_utps_modem

再将javax.comm.properties文件中的serpath0的值修改为

/dev/ttyUSB0

(3)处的属性值设置了Linux平台的并口设备符,由于没有使用,未作修改。

基于以上知识。考虑安全机制导致不能正确加载javax.comm.properties文件,所以决定修改源代码,直接加载驱动类com.sun.comm.LinxuDriver

行动,将CommProperties类中加载javax.comm.properties文件的语句注释掉,然后在CommPortIdentifier类中

注释掉从配置文件中读取驱动的代码,将需要加载的驱动名赋值为:com.sun.comm.LinxuDriver。

××××××××××××××××××××××××××××××××××××××××××××××××××××××

反编译处的代码片段如下,只贴出了修改过的代码,可自行反编译查看完整代码

CommProperties{

...

 

static
  {
    try
    {
      if ((CommProperties.propFilename = findPropFile("javax.comm.properties")) != null) {

        //注释掉加载javax.comm.properties文件的代码
        //commProps.load(new BufferedInputStream(new FileInputStream(new File(propFilename))));
      }
      else
        System.err.println("javax.comm:  Can't find javax.comm.properties!");
    }
    catch (Exception e) {
      System.err.println("javax.comm:  Error loading javax.comm.properties!");

      System.err.println(e.getMessage());
    }
  }

...}

CommPortIdentifier {

...

 static
  {

    //String driverName = null;

//修改为直接赋值com.sun.comm.LinuxDriver
    String driverName = "com.sun.comm.LinuxDriver";
    try {

//以下三行注释,不同属性文件中读取驱动类
      //if (((driverName = CommProperties.getProperty("driver")) == null) &&
      //  ((driverName = CommProperties.getProperty("DRIVER")) == null))
      //  driverName = CommProperties.getProperty("Driver");
      commDriver = loadDriver(driverName);
    } catch (Throwable t) {
      System.err.println("");
      t.printStackTrace();
    }

××××××××××××××××××××××××××××××××××××××××××××××××××××××

大功告成,使用编译后的这两个类替换掉comm.jar包中的同名类,然后使用jar命令重新打包为comm.jar,使用这个修改版的comm.jar文件运行web application应用,不再出现

javax.comm:  Error loading javax.comm.properties!

null

javax.comm: platform driver class name = null

(Check 'driver' property in javax.comm.properties)

异常,可是新的异常由出现了,异常描述找不到端口,同异常类型(1)

 

 

由此想到,忽略了javax.comm.properties中关于serpath0,serpath1的属性值,此为注册串口设备符需要用到的值。

再查看反编译的类,发现在类PlatformPortBundle中通过读取javax.comm.properties文件中的属性serpath0,serpath1将相应的设备符注册给程序,完整代码如下:

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

原版:

package com.sun.comm;

public class PlatformPortBundle extends PathBundle
{
  public PlatformPortBundle()
  {
    String portName = null;
    String portPath = null;

    for (int i = 0; i < 1024; ++i) {
      portName = "serpath" + i;
      if ((portPath = CommProperties.getProperty(portName)) == null) break;
      super.add(portPath, 1);
    }

    for (int i = 0; i < 1024; ++i) {
      portName = "parpath" + i;
      if ((portPath = CommProperties.getProperty(portName)) == null) return;
      super.add(portPath, 2);
    }
  }
}

 

修改版:直接注册为/dev/ttyUSBi(i=0,1)

package com.sun.comm;

public class PlatformPortBundle extends PathBundle
{
  public PlatformPortBundle()
  {
    //String portName = null;
    String portPath = null;

    for (int i = 0; i < 2; ++i) {
      //portName = "serpath" + i;
      portPath = "/dev/ttyUSB" + i;
      //if ((portPath = CommProperties.getProperty(portName)) == null) break;
      super.add(portPath, 1);
    }

    for (int j = 0; j < 2; ++j) {
      //portName = "parpath" + j;
      portPath = "/dev/parport" + j;
      //if ((portPath = CommProperties.getProperty(portName)) == null) return;
      super.add(portPath, 2);
    }
  }
}

替换PlatformPortBundle.class文件后重新打包comm.jar,运行,一切正常,讨厌的Exception再也不出现了。

至此,Linux平台使用SMSLib和JavaComm3API,实现在Java Web Application应用中发送手机短信的项目完成,希望本人在项目中遇到的问题及相关的解决方案对大家有所帮助。

PS:修改版的comm.jar已上传到csdn中,需要的朋友请从以下链接下载

 http://dldx.csdn.net/fd.php?i=990623329111710&s=51294bcdf7487d8ce54d50b300edccc5

 由于我在修改后的源文件中直接将串口设备符注册为/dev/ttyUSB0,/dev/ttUSB1,所以如果你的Modem的设备符不是/dev/ttyUSB0请自行创建一个link,命名为/dev/ttyUSB0

你可能感兴趣的:(linux,Web,api,application,手机,平台)