【IT168 技术文档】
一、 概述
随着手机的逐渐普及,它的主要业务之一“短信”的使用量也水涨船高。但使用手机发短信还有一些不方便的地方,如输入汉字慢、功能有限、手机的存储容量有限等。因此,近几年开始兴起使用电脑向手机发送短信。使用电脑发送短信的方法很多,如通过126、新浪等短信平台通过注册自己的手机号,就可以通过电脑发短信了。但这样做有一些不足,如发短信时电脑必须联入Internet,而且一般使用电脑发短信的费用要比直接使用手机发短信的费用高一些。
当然,还有其它方法发短信。如象126那样租网通或移动的短信服务器,然后通过短信服务器发送短信。这种方式虽然很直接,但是价格昂贵,不是一般人可以承受的(只有象126、新浪这样的服务网站才能用得起)。
最省钱的方法就是到网上去找一个可以免费发短信的软件,我以前使用过一个叫“灵犀机器人”的软件,它们可以有限地免费发送短信,但好象现在也都收费了。这种软件现在越来越少了。
那么是否有折衷的方法,使发短信的费用和手机一样,而且又可以象电脑一样方便地输入、保存、修改和查询短信呢?答案是肯定的,那就是通过数据线将手机和电脑连在一起,使用电脑控制手机发短信。而且这样做电脑无需联入Internet。
二、 如何通过数据线控制手机发短信
一般手机的数据线可以通过COM口或USB口和计算机进行通讯。在本文中我们采用带有COM口的数据线,因为控制COM口比控制USB口更容易、更简单。通过Java和COM口进行通讯有很多方法,可以在Java中直接调用系统API,也可以采用第三方的Java库(这些库在底层也是通过调用系统API实现的)。在本文中我们采用第二种方法,也就是通过第三方的Java库来和COM口进行通讯。在网上这种库很多,在本文中介绍了如何使用Sun的Java通讯API和手机进行通讯,可以通过http://www.matrix.org.cn/resource/upload/forum/2006_02_02_174639_eEDCZtxWNS.zip下载Sun的Java通讯API库 。
三、 如何安装Sun的Java通讯API。
安装Java通讯API可分为以下几步:
1. 将下载后的压缩文件zip压缩包解压,假设解压目录为C:\commapi。并且保证你的机器中已经安装了Java开发包,假设Java开发包安装在了C盘的C:\jdk1.5中。
2. 使用如下命令将win32com.dll复制到C:\jdk1.5\bin中。
copy c:\commapi\win32com.dll c:\jdk1.5\bin
3.使用如下命令将comm.jar复制到c:\jdk1.5\lib目录中。
copy c:\commapi\comm.jar c:\jdk1.5\lib
4. 使用如下命令将Javax.comm.properties复制到c:\jdk`1.5\lib中。
copy c:\commapi\Javax.comm.properties c:\jdk1.5\lib
这个文件必须被安装在这,否则系统无法发现COM口。
5. 将comm.jar加入到classpath中。
四、 使用Java通讯API开发短信软件
和访问网络资源一样,和COM口通讯的第一步必须要打开某一个COM口。因此,我们首先需要一个可以操作COM口的类,我们叫它MySerial(这将是我们实现的第一个Java类)。
上面实现的类是我们的核心类,它的主要功能之一就是初始化COM口,并保证COM口可以正常使用。我们可以发现,在MySerial类中还调用了两个类:SerialBuffer和readSerial。下面就来实现这两个类。package com.message
import Java.io.*;
import Java.util.*;
import Javax.comm.*;
![]()
public class MySerial
...{
private String portName;
private CommPortIdentifier portId;
private SerialPort serialPort;
private OutputStream out;
private InputStream in;
![]()
SerialBuffer serialBuffer; // SerialBuffer类在后面实现
ReadSerial readSerial; // ReadSerial在后面实现
![]()
/**//*
这个方法是MySerial的构造函数,参数port表示COM口,1:COM1,2:COM2,
以此类推。在这个类中设置了COM口名:portName。portName必须是这种格式,因为getPortIdentifier方法要使用它
*/
public MySerial(int port)
...{
portName = "COM" + port;
}
![]()
/**//**
这个函数初始化了COM口,也就是我们所说的打开COM口,并得到用于和COM口通讯的输入、输出流。
*/
public int Initialize()
...{
private int success = 1;
private int fail = -1;
try ...{
// 根据portName得到COM的标识对象
portId = CommPortIdentifier.getPortIdentifier(portName);
![]()
try ...{
// 打开COM口, open方法的第二个参数是超时时间,单位是毫秒。
// 在本程序中超时时间是5秒
serialPort = (SerialPort)
portId.open("Serial_Communication", 5000);
}
catch (PortInUseException e)
...{
return fail;
}
![]()
// 下面是得到用于和COM口通讯的输入、输出流。
Try
...{
in = serialPort.getInputStream();
out = serialPort.getOutputStream();
}
catch (IOException e)
...{
return fail;
}
// 下面是初始化COM口的传输参数,如传输速率:9600等。
try ...{
serialPort.setSerialPortParams(9600,
SerialPort.DATABITS_8,
SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
}
catch (UnsupportedCommOperationException e)
...{
return fail;
}
}
catch (NoSuchPortException e)
...{
return fail;
}
![]()
// 当上面的代码执行成功后,将建立一个数据缓冲区,然后启动
// 一个线程,用于接收从COM 口传回的数据
serialBuffer = new SerialBuffer();
readSerial = new ReadSerial(serialBuffer, in);
readSerial.start();
![]()
return success;
}
![]()
// 这个方法从COM口读出length个字符
public String readPort(int length) ...{
String msg;
msg = getMsg(Length);
return msg;
}
![]()
// 这个函数向COM口写入一个字符串
public void writePort(String msg) ...{
int c;
try ...{
for (int i = 0; i < msg.length(); i++) ...{
out.write(msg.charAt(i));
}
}
catch (IOException e) ...{}
}
![]()
// 关闭正在使用的COM口
public void closePort() ...{
readSerial.stop();
serialPort.close();
}
}
SerialBuffer类的实现
对于SerialBuffer类有一点需要说明一下,就是在putChar和getMsg方法前必须加synchoronized关键字,这是因为这个类是在多线程中调用,由于在这两个方法中访问了共享资源,为了不出现脏数据,因此,必须加这个关键字。package com.message;
![]()
public class SerialBuffer ...{
private String content = "";
private String currentMsg, tempContent;
private boolean available = false;
private int lengthNeeded = 1;
![]()
// 这个函数返回指定长度的信息串
public synchronized String getMsg(int length)
...{
lengthNeeded = length;
notifyAll();
![]()
if (lengthNeeded > content.length()) ...{
available = false;
while (available == false) ...{ // 循环等待,直到可以读数据为止
try ...{
wait();
}
catch (InterruptedException e) ...{}
}
}
![]()
currentMsg = content.substring(0, LengthNeeded);
tempContent = content.substring(LengthNeeded);
content = tempContent;
lengthNeeded = 1;
notifyAll();
return currentMsg;
}
![]()
// 这个方法将一个字符写入COM口
public synchronized void putChar(int c) ...{
Character d = new Character( (char) c);
content = Content.concat(d.toString());
if (lengthNeeded < Content.length()) ...{
available = true;
}
notifyAll();
}
}
readSerial类的实现
接下来实现负责通过数据线和手机通信的类OperateMobile。这个类可以通过send方法控制手机发送短信。package com.message;
![]()
import Java.io.*;
![]()
// 这个类从指定的COM口读数据,并将这些数据保存在缓冲区中
// ReadSerial是一个线程类
public class ReadSerial extends Thread ...{
private SerialBuffer comBuffer;
private InputStream comPort;
![]()
// 这个方法是ReadSerial类的构造函数,第一个参数将缓冲区传入类中,第二个参数// 是被打开的COM口的输入流
public ReadSerial(SerialBuffer serialBuffer, InputStream port) ...{
comBuffer = serialBuffer;
comPort = port;
}
// 调用Thread的start方法后,运行run方法
public void run() ...{
int c;
try ...{
while (true) ...{
c = comPort.read(); // 一个字符、一个字符地从COM口读数据
comBuffer.putChar(c);
}
}
catch (IOException e) ...{}
}
}
最后让我们调用OperateMobile类来发送短信。public class OperateMobile
...{
private MySerial mySerial;
private static char symbol = 10;
private static char symbol1 = 13;
![]()
public static boolean open(int port) ...{
mySerial = new MySerial(port);
if (mySerial.Initialize() == 1) ...{
return true;
}
else ...{
return false;
}
}
// 设置短信发送模式
private static boolean setMode(int mode)
String strReturn, strSend = “”;
try ...{
// 模式字符串
strSend = "AT+CMGF=" + String.valueOf(mode) +
String.valueOf(symbol1);
// 将模式通过COM口写入手机
mySerial.WritePort(strSend);
strReturn = "";
// 从手机读出长度为6的返回信息,如果包含“OK”,则说明设置成功
strReturn = mySerial.ReadPort(6);
if (strReturn.indexOf("OK", 0) != -1) ...{
return true;
}
return false;
}
catch (Exception ex) ...{
return false;
}
}
public static boolean send (String phoneCode, String msg) ...{
String strReturn = “”, strSend = “”;
char symbol2 = 34;
char symbol3 = 26;
if (setMode(1) != true) ...{ // 设置短信发送模式
return false;
}
try ...{
strSend = "AT+CSMP=1,173,36,08" + String.valueOf(symbol1);
mySerial.writePort(strSend);
sleep(300);
strReturn = mySerial.ReadPort(6); // 读返回字符串
if (strReturn.indexOf("OK", 0) != -1) ...{ // 如果成功,进行下一步
// 将对方的电话号传入手机
strSend = "AT+CMGS=" + String.valueOf(symbol2) + phoneCode +
String.valueOf(symbol2) +
String.valueOf(symbol1);
mySerial.writePort(strSend);
strReturn = "";
sleep(200);
strReturn = mySerial.ReadPort(4);
byte[] str1 = null;
try ...{
str1 = msg.getBytes("GBK");
}
catch (Java.io.UnsupportedEncodingException e) ...{
e.printStackTrace();
}
// 将发送信息变成十六进制发送
strSend = encodeHex(str1, msg) + String.valueOf(symbol3) +
String.valueOf(symbol1);
mySerial.writePort(strSend);
sleep(200);
strReturn = mySerial.ReadPort(8); // 读信息以确定是否发送成功
if (strReturn.indexOf("+CMGS", 0) != -1) ...{
System.out.println("OK");
return true;
}
}
return false;
}
catch (Exception ex) ...{
ex.printStackTrace();
return false;
}
}
// 这个方法对要发送的信息进行编码
private static final String encodeHex(byte[] bytes, String msg) ...{
StringBuffer buff = new StringBuffer(bytes.length * 4);
String b;
char a;
int n = 0;
int m = 0;
for (int i = 0; i < bytes.length; i++) ...{
b = Integer.toHexString(bytes[i]);
if (bytes[i] > 0) ...{
buff.append("00");
buff.append(b);
n = n + 1;
}
else ...{
a = msg.charAt( (i - n) / 2 + n);
m = a;
b = Integer.toHexString(m);
buff.append(b.substring(0, 4));
![]()
i = i + 1;
}
}
return buff.toString();
}
![]()
}
五、 小结public class Main
...{
public static void main(String[] args) ...{
OperateModile.open(1); // 打开COM1端口(要将数据线插到COM1上)
OperateModile.send(“13912345678”, “收到我的短信了吗?收到了请回复?”);
}
}
使用计算机控制手机发送短信具有很大的灵活性。以上的例子只是抛砖引玉,读者可以通过这个例子进行扩展,设计出更灵活、更强大的发送短信的软件。这样,就不用再一个字一个字地在手机上输汉字了!
