级别: 初级 Soma Ghosh (sghosh@entigo.com ), 应用程序开发人员, Entigo
2003 年 5 月 27 日 无线消息传递的目标是扩展 J2ME 应用程序的联网和 I/O 能力,以便能使用 GSM 网络上的消息传递服务(如短信服务(Short Message Service,SMS)和小区广播服务(Cell Broadcast Service,CBS))来发送和接收消息。在本文中,您将了解关于以下主题的更多信息:
- 无线消息传递系统
- 通用消息传递 API,javax.wireless.messaging
- 短信服务 API
- 小区广播消息传递 API
- J2ME 消息传递应用程序
- 部署无线消息传递
无线消息传递系统 — 概述
无线消息传递的思想为 J2ME 开拓了全新的前景。由无线消息传递支持的 J2ME 应用程序能独立于平台来访问无线通信资源,如全球移动通信系统(Global System for Mobile Communication,GSM)网络(一种允许跨国通信的移动电话系统)的短信服务(SMS)和小区广播服务(CBS)。
在继续研究 J2ME 无线消息传递工作原理之前,我将简要地讨论 GSM 网络中的 SMS 和 CBS 消息传递系统。
GSM 短信服务(SMS)
SMS 是 GSM 网络中移动电话、传真机和/或 IP 地址之间简短文本消息的传递。消息不得超过 160 个字母数字字符且不包含图像或图形。这一服务的主要特性是迅速、价廉并能保证消息会到达目标用户(哪怕他在信号覆盖范围之外或已关掉电话)。
消息一经发送,就会由短信服务中心(SMSC)接收,该中心必须随即将消息发送到适当的移动设备。为了做到这一点,SMSC 会向归属位置寄存器(home location register,HLR)发送 SMS 请求以找到漫游用户。一旦 HLR 接收到该请求,就会以用户的状态(如 1. 不活动或活动,和 2. 漫游位置)来响应 SMSC。
如果响应是
不活动,则 SMSC 将保留消息一段时间。当用户使用其设备时,HLR 会向 SMSC 发送一个 SMS 通知,而 SMSC 则尝试进行发送。
SMSC 以短信发送点对点(Short Message Delivery Point-to-Point)格式将消息发送到 GSM 消息发送系统。该系统会寻呼设备,若设备作出响应,则发送消息。
SMSC 会收到验证,即消息已经由最终用户接收,然后将该消息归类为
已发送,并且将不再尝试发送它。
图 1说明了 SMS 发送机制。
图 1. SMS 发送系统
GSM 小区广播服务
GSM 小区广播服务允许将消息发送到当前位于某个特定小区的每个移动台(Mobile Station,MS),如移动电话、传真机和/或 IP 地址。在一段时间内会不时重复小区广播消息,从而使在第一次发送之后才进入小区的 MS 也能接收到消息。可以用二进制数据或 ASCII 文本的形式最多发送 15 页的数据,每页最多有 93 个字符;测试装置仅提供对 ASCII 消息的支持。小区广播消息按主题分类,给每条消息都分配了通道号、消息代码、更新号和语言。
-
通道号是标识消息主题的头部号(如‘气象报告’或‘交通信息’)。
-
消息代码标识特定的消息,这样当 MS 收到的消息的消息代码与以前收到的相同时,会意识到这是一条重复消息,可能就不会显示给用户了。
-
更新号用来标识消息的特定版本。这对于报告动态情况比较有用,在这样的情况下,消息可能正在报告某个事件(如前方道路施工),但事件的详情经常改变(例如,塞车的长度)。在某个小区中停留一段时间的 MS 将接收带相同消息代码的消息,但会接收作为同一消息更新版本的更新号;然而,进入该小区的 MS 将仅接收最新版本的消息(以及此后的任何后继版本)。
-
语言指明消息所用的语言。更改此参数不会使消息的原文得到翻译。
SMS 是一对一和一对几的消息传递系统,而 CBS 则可以在某一个地区提供一对多消息传递。
无线消息传递系统
可以将这一系统看作一个三层体系结构,由接口层(Interface Layer)、实现层(Implementation Layer)和传输层(Transport Layer)组成。
接口层构成了一组通用的消息传递接口,它们独立于所有消息传递协议。这些接口提供消息的基本定义,定义发送和接收消息的基本功能,以及提供向 MIDlet 应用程序通知进入消息的机制。
实现层包含这样的类:它们实现每个接口层以访问无线消息传递,如 GSM 移动设备上的 SMS 或 CBS 功能。例如,就 SMS 而言,这一层提供了用于 SMS 消息的消息连接的实现,以及具有文本或二进制属性的 SMS 消息的实现。实现层还执行用于底层协议的消息分段和并置。然后,MIDlet 可以在
MessageConnection 中指定应该将某条消息拆分成几段。
传输层包含这样的类:它们实际实现了将消息传送到移动设备的协议。
这一三层机制如
图 2所示。
图 2. 无线消息传递系统体系结构
通用消息传递 API — javax.wireless.messaging
此 API 由 javax.wireless.messaging 包定义,该包定义了所有用于发送和接收无线消息的接口。以下是一个接口列表:
Message。它提供消息的基本定义,它充当一个容器来容纳消息的地址、有效负载及关于发送和阻塞的标志。它是
TextMessage 和
BinaryMessage 的超接口(superinterface),后两个分别是带文本有效负载属性和带二进制有效负载属性的消息对象。Message 的结构如
图 3所示。
图 3. 消息的结构
MessageConnection 。
它提供接收和发送消息的基本功能。它包含一个发送和接收消息的方法、一个创建新 Message 对象的工厂(factory)方法和一个计算发送某指定 Message 对象所需底层协议段数量的方法。
通过调用
Connector.open() 可将这个类实例化。
在客户机方式连接中,只能发送消息。通过将标识着目的地址的字符串传递到
Connector.open() 方法来创建客户机方式连接。该方法返回
MessageConnection 对象。
clientConn = (MessageConnection)Connector.open("sms://
+18643630999:5000");
|
在服务器方式连接中,可以发送或接收消息。通过将标识本地主机上端点(取决于协议的标识符,例如,端口号)的字符串传递给
Connector.open() 方法来创建服务器方式连接。
serverConn = (MessageConnection)Connector.open("sms://:5000");
|
MessageListener 。
它提供了向 MIDlet 应用程序通知有进入消息的基本机制。当可以读取新到消息时,它允许 MIDlet 接收一个回调。
短信服务 API
com.sun.midp.io.j2me.sms 包提供用于短信服务消息传递系统的 API,并允许 MIDlet 访问 GSM 移动设备上的 SMS 功能。
该包的主要组件 ―
MessageObject 和
Protocol ― 支持 SMS 消息的发送和接收。
MessageObject 。
MessageObject 是 SMS 消息的实现。在实现层,javax.wireless.messaging.Message 接口是作为缓冲区实现的。
MessageObject 处理消息缓冲区的创建和缓冲区之外的输入/输出操作。此外,它有两个子类 ―
TextObject 和
BinaryObject 。这些类实现具有文本或二进制有效负载的 SMS 消息。
Protocol 。
它实现发送 SMS 消息所需的与低层传输(Transport)机制的消息连接。在这一过程中,它检查所有的运行时配置参数,并处理与无效 URL 语法、安全性违规、I/O 违规和无效参数有关的异常。
Protocol 还处理使用数据报或串行端口连接的消息发送与接收。
小区广播消息传递 API
com.sun.midp.io.j2me.cbs 包提供用于小区广播消息传递系统的 API,并允许 MIDLlet 访问 GSM 移动设备上的 CBS 功能。
该包的主要组件 com.sun.midp.io.j2me.cbs.Protocol 支持 CBS 消息的接收。
CBS 与 SMS 的不同之处在于:
- URL 连接字符串不支持指定的主机,而且
- 它仅用于入站协议。有 CBS 能力的 MIDlet 可接收消息,但不能发送它们。
J2ME 消息传递应用程序
在这一节里,我将演示一个 WMAServer 示例,它等候进入的 SMS 消息,然后将它们显示在电话屏幕上。javax.microedition.lcdui 包提供了一组功能,用于实现应用程序的用户界面。
WMAServer MIDlet 通过将标识本地主机上端点(取决于协议的标识符,例如,端口号)的字符串传递给
Connector.open() 方法来创建服务器方式连接。
为了能收到进入消息的通知,MIDlet 在
MessageConnection 实例
serverConn 处注册一个
MessageListener 对象。
serverConn.setMessageListener(MessageListener ml);
|
它还在
MessageListener 接口中实现
notifyIncomingMessage() 。当进入消息到达
MessageConnection 时,就调用
notifyIncomingMessage() 方法。应用程序必须使用
MessageConnection 的
receive() 方法来检索该消息。
WMAServer 应用程序从进入消息读取文本或二进制的有效负载数据,然后将其存储在字符串对象中供以后显示。
public void notifyIncomingMessage(MessageConnection conn) {
Message msg = null;
// Try reading (maybe block for) a message
try {
msg = conn.receive();
}
catch (Exception e) {
// Handle reading errors
System.out.println("processMessage.receive " + e);
}
// Process the received message
if (msg instanceof TextMessage) {
TextMessage tmsg = (TextMessage)msg;
msgReceived = tmsg.getPayloadText();
}
else
{
// process received message
if (msg instanceof BinaryMessage) {
BinaryMessage bmsg = (BinaryMessage)msg;
byte[] data = bmsg.getPayloadData();
// Handle the binary message...
msgReceived = data.toString();
}
}
|
当必须释放连接资源和相关联的侦听器对象时,应用程序提供
destroyApp() 方法。
public void destroyApp(boolean unconditional) {
try {
if (serverConn != null) {
serverConn.setMessageListener(null);
serverConn.close();
}
}
catch (IOException e) {
// Handle the exception...
e.printStacktrace();
}
|
以下是完整的应用程序代码:
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.io.*;
import javax.wireless.messaging.*;
import java.io.*;
import java.lang.*;
import java.util.*;
//A first MIDlet with simple text and a few commands.
public class WMAExample extends MIDlet
implements CommandListener, MessageListener {
//The exit commands
private Command exitCommand;
//Command to get Message
private Command getMsgCommand;
//The display for this MIDlet
private Display display;
Form displayForm;
String msgReceived;
MessageConnection serverConn;
public WMAExample() {
display = Display.getDisplay(this);
exitCommand =
new Command("Exit", Command.SCREEN, 1);
getMsgCommand =
new Command("Get", Command.SCREEN, 1);
}
// Start the MIDlet by creating two command buttons
public void startApp() {
displayForm = new Form("Get Message");
displayForm.addCommand(exitCommand);
displayForm.addCommand(getMsgCommand);
displayForm.setCommandListener(this);
displayForm.setItemStateListener(this);
display.setCurrent(displayForm);
try
{
serverConn = (MessageConnection)Connector.open
("sms://:5000");
// Register the listener for inbound messages.
serverConn.setMessageListener(this);
}
catch (IOException ioExc)
{
System.out.println("Server connection could
not be obtained");
ioExc.printStackTrace();
}
}
public void notifyIncomingMessage(MessageConnection
conn) {
Message msg = null;
// Try reading (maybe block for) a message
try {
msg = conn.receive();
}
catch (Exception e) {
// Handle reading errors
System.out.println("processMessage.receive "
+ e);
}
// Process the received message
if (msg instanceof TextMessage) {
TextMessage tmsg = (TextMessage)msg;
msgReceived = tmsg.getPayloadText();
}
else
{
// process received message
if (msg instanceof BinaryMessage) {
BinaryMessage bmsg = (BinaryMessage)msg;
byte[] data = bmsg.getPayloadData();
// Handle the binary message...
msgReceived = data.toString();
}
}
// Pause is a no-op because there are no background
// activities
public void pauseApp() { }
// Destroy must cleanup everything not handled
// by the garbage collector.
public void destroyApp(boolean unconditional) {
try {
if (serverConn != null) {
serverConn.setMessageListener(null);
serverConn.close();
}
}
catch (IOException e) {
// Handle the exception...
e.printStacktrace();
}
}
// Respond to commands.
public void commandAction(
Command c, Displayable s) {
if (c == exitCommand) {
destroyApp(false);
notifyDestroyed();
}
if (c == getMsgCommand) {
try
{
displayForm.append(msgReceived);
display.setCurrent(displayForm);
}
catch (Exception exc)
{
exc.printStackTrace();
}
}
}
}
|
接下来是一个 WMA 客户机示例,该客户机向 GSM 移动电话发送一条 SMS 消息。WMA 客户机只能发送消息。它通过向 Connector.open() 方法传递一个标识目的地地址的字符串(一个有效的手机号码)来创建
MessageConnection 。试试:
{
clientConn = (MessageConnection)Connector.open("sms://
+18643630999:5000");
}
catch (IOException ioExc)
{
System.out.println("Client connection could not be obtained");
ioExc.printStackTrace();
}
|
它随后通过设置有效负载和目的地地址来创建有效的
TextMessage 对象,并通过
MessageConnection 发送消息。
TextMessage tmsg =
(TextMessage)clientConn.newMessage
(MessageConnection.TEXT_MESSAGE);
tmsg.setAddress("sms://18643630999:5000");
tmsg.setPayloadText(msgToSend);
clientConn.send(tmsg);
|
以下是 WMA 客户机的完整应用程序代码:
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.io.*;
import javax.wireless.messaging.*;
import java.io.*;
import java.lang.*;
import java.util.*;
//A first MIDlet with simple text and a few commands.
public class WMAClient extends MIDlet
implements CommandListener, MessageListener {
//The exit commands
private Command exitCommand;
//Command to get Message
private Command sendMsgCommand;
//The display for this MIDlet
private Display display;
Form displayForm;
String msgToSend="Can you hear me?";
MessageConnection clientConn;
public WMAClient() {
display = Display.getDisplay(this);
exitCommand =
new Command("Exit", Command.SCREEN, 1);
sendMsgCommand =
new Command("Send", Command.SCREEN, 1);
}
public void notifyIncomingMessage(MessageConnection conn)
{
}
// Start the MIDlet by creating the TextBox and
// associating the exit command and listener.
public void startApp() {
displayForm = new Form("Send Message");
displayForm.addCommand(exitCommand);
displayForm.addCommand(sendMsgCommand);
displayForm.setCommandListener(this);
displayForm.setItemStateListener(this);
display.setCurrent(displayForm);
try
{
clientConn = (MessageConnection)Connector.open("sms://
+18643630999:5000");
}
catch (IOException ioExc)
{
System.out.println("Client connection could
not be obtained");
ioExc.printStackTrace();
}
}
// Pause is a no-op because there is no background
// activities or record stores to be closed.
public void pauseApp() { }
// Destroy must cleanup everything not handled
// by the garbage collector.
public void destroyApp(boolean unconditional) { }
// Respond to commands.
public void commandAction(
Command c, Displayable s) {
if (c == exitCommand) {
destroyApp(false);
notifyDestroyed();
}
if (c == sendMsgCommand) {
try
{
TextMessage tmsg =
(TextMessage)clientConn.newMessage
(MessageConnection.TEXT_MESSAGE);
tmsg.setAddress("sms://18643630999:5000");
tmsg.setPayloadText(msgToSend);
clientConn.send(tmsg);
}
catch (Exception exc)
{
exc.printStackTrace();
}
}
}
}
|

 |

|
部署无线消息传递
部署有无线消息传递能力的 MIDlet 时,要始终遵照以下三个步骤:
- 从
http://java.sun.com/products/wma下载 WMA 1.0 参考实现。它兼容 J2ME 工具箱 V1.0.4 和更高版本。
- 将 wma.jar(WMA 参考实现)从 <WMA Root dir>/ wma1_0/lib 复制到 <J2ME Root dir>/lib。
- 用 SMS 和 CBS 特定项更新 <J2ME Root dir>/lib 目录中的 internal.config。
在数据报仿真方式中,WMA 1.0 RI 仿真传输的方式是:将 SMS 消息封装在数据报中,并基于下列项形成实际的客户机或服务器地址:
# Default values for SMS internal implementation.
com.sun.midp.io.j2me.sms.Impl: com.sun.midp.io.j2me.sms.DatagramImpl
com.sun.midp.io.j2me.sms.DatagramHost: localhost
# Values will be same for a MIDLet which is both a Client and Server
com.sun.midp.io.j2me.sms.DatagramPortIn: 56789
com.sun.midp.io.j2me.sms.DatagramPortOut: 34567
#
# Permissions to use specific SMS features
com.sun.midp.io.j2me.sms.permission.receive: true
com.sun.midp.io.j2me.sms.permission.send: true
#
# Permissions to use specific CBS features
com.sun.midp.io.j2me.cbs.permission.receive: true
#
# Permissions to use connection handlers
javax.microedition.io.Connector.sms: true
javax.microedition.io.Connector.cbs: true
#
# Default SMS Service Center address
wireless.messaging.sms.smsc: +18643299089
############# WMA ##################
|
要将 WMAClient 和 WMAServer 类构建到可运行的 .jad 文件中,请创建以下批处理文件:
@echo off
rem This batch file builds and preverifies the code for the demos.
rem it then packages them in a JAR file appropriately.
echo *** Creating directories ***
echo *** (this stage may produce already exist errors. Ignore them). ***
mkdir ..\tmpclasses
mkdir ..\classes
echo *** Compiling source files ***
javac -bootclasspath ..\..\..\lib\midpapi.zip -d ..\tmpclasses -classpath
..\tmpclasses;f:\wtk104\lib\wma.jar ..\src\*.java
echo *** Preverifying class files ***
..\..\..\bin\preverify -classpath ..\..\..\lib\midpapi.zip;..\tmpclasses;
f:\wtk104\lib\wma.jar -d ..\classes ..\tmpclasses
echo *** Jaring preverified class files ***
jar cmf MANIFEST.MF WMAClient.jar -C ..\classes .
echo *** Jaring resource files ***
jar umf MANIFEST.MF WMAClient.jar -C ..\res .
|
要运行生成的 .jad 文件,请创建以下批处理文件:
@echo off
rem This file runs the WMAExample.jad/jar file in the emulator.
@echo on
cd ..\..\..\bin
emulator -classpath ..\apps\WMAClient\bin\WMAClient.jar;f:\wtk104\lib
\wma.jar
-Xdescriptor:..\apps\WMAClient\bin\WMAClient.jad
|
结论
J2ME 应用程序为无线消息传递开拓了一个全新领域 ― GSM。通过使用 MIDlet,现在以独立于系统且可移植的方式在 GSM 兼容手机之间通信已成为可能。
参考资料
关于作者  | |  | Soma Ghosh 是计算机科学和工程专业的毕业生,她在过去七年时间里开发了各种电子贸易和联网方面的 Java 应用程序。她相信无线贸易代表着业界不远的将来,最近她迷上了现有桌面组件和模型的无线倡议。Soma 目前是 Entigo 的一名应用程序开发人员,这家公司在实际电子商务解决方案和 B2B 销售与服务方电子贸易产品方面是倡导者和业界领导者。可以通过
sghosh@entigo.com 与 Soma 联系。
|
对本文的评价
|