dev2dev 首页 > 资源中心 > 技术文章
Java ME的SIP API简介
时间:2007-08-06
作者:Emmanuel Proulx
浏览次数:
本文关键字:WebLogic, SIP, SIP Server, Session Initiation Protocol, JSR180, API, J2ME, JavaME, 会话初始化协议, Developing, Develop, Coding, Code, Programming, 开发, Program, Phone, Cell, Cellular, Mobile, Telephone, Telecom, Telco, 编码, Telecommunication, WebLogic Communications Platform, 代码, Emmanuel Proulx, SIP服务器, 编程, 移动电话, 电信 |
|
本文将提供一个易于使用的方法来开发使用SIP的Java ME应用程序。同时还将检查随Java Wireless Toulkit发行的用于Java ME (JSR 180)的SIP API。本文还讨论了使用此技术的各种方法。您将看到能够运行于移动电话或仿真器的一个真正的SIP应用程序。
简介:SIP + Java =卓越
移动电话和可连接到Internet的PDA越来越受到人们的欢迎。我的所有朋友都使用它们,并且结合使用了大量新的应用程序,。其中许多程序可以“连网”,不论是客户端/服务器还是点对点设备。 开发可移动的网络应用程序时,需要选择通讯协议。开发者可打开套接字并创建一个完全私有的协议。可使用具有私有API的SOAP,也可使用完全基于标准的方法。鉴于以下原因,我建议使用后者:
- 在包含库的情况下更易进行开发。
- 可提供更多控制,例如:除了根据下载的KB数量外还可根据交互类型收费。
- 移动运营商可阻止非标准协议。
- 可与各种设备进行互操作。
这就是我建议使用SIP进行移动网络编程的原因。SIP是移动运营商使用的标准连接协议。此外,它所使用的库也易于查找和使用。有关SIP简介,请参见介绍性文章: SIP简介,第1部分:SIP初探 (中文版)和 SIP简介,第2部分:SIP SERVLET(中文版) 使用Java ME为SIP编程非常简单。最新移动库构成了丰富的编程环境,这使得应用程序的开发变得轻而易举。 本文将介绍为移动电话构建简单的messenger移动电话客户端的方式。该方式使用SIP协议并使用Java ME库进行构建。此应用程序可单独运行,也可将其配置为使用SIP注册器,如BEA WebLogic SIP Server。
先决条件
要从本文中获取最大收益,必须在开发环境中安装以下工具:
此外,还必须了解一点Java ME知识。有关这些软件的帮助信息,请阅读附录。
MEssenger应用程序
我决定通过开发即时消息传递客户机应用程序来演示SIP在Java ME中的使用。此应用程序虽然简单,但可演示发送消息(REGISTER、MESSAGE)、处理响应和处理收到的消息等功能。 我将此应用程序命名为MEssenger(其发音为“mee-senger”,与Java ME中的“ME”一样,此处的“ME”表示Micro Edition)。它具有很简单的GUI(两页)。主页可用于收发消息。第二个页面用于配置应用程序。本文没有包含其他有趣的功能。 MEssenger的导航界面如图1所示。输入目标SIP地址和消息并选择menu中的Send后即可发送消息。该应用程序最近的五个事件可显示在下半个屏幕中。配置页面中可输入注册信息。  图1. MEssenger导航
在深入研究应用程序的代码前,我们先看一下应用程序的设计。
设计
我们将要编写的应用程序由以下五种类和接口构成:
| |
|
| 类 |
说明 |
| MEssengerMIDlet |
该Java ME应用程序的“主”类。它可创建并显示MenuManager示例。 |
| MenuManager |
此类包含页面和导航事件。它还可以实例化SipManager类。此类可实现MessageListener接口。 |
| SipManager |
此类包含通信行为;它可收发消息、安排注册。 |
| ErrorAlert |
显示错误的实用类。 |
| MessageListener |
SipManager使用的接口,请求MenuManager显示消息。这将拆分两个类,可在不使用MenuManager的其他应用程序中重用SipManager。 |
正如我先前说过,本文的目的不是介绍Java ME,因此接下来我将重点介绍SipManager类。有关其他类的详细信息,请参阅文本包含的源代码。
关于Java ME的SIP API
使用Java ME进行SIP编程有些像套接字编程。它将显示打开和关闭客户机和服务器连接、数据流以及线程等概念。我将展示示例所需的所有不同类。首先我将列出此API的一些关键类:
| |
|
| 类 |
说明 |
| Connector |
创建各种连接对象的工厂。对于SIP连接,只需使用以“sip:”开头的地址,Connector就可创建SipClientConnection或SipServerConnection 对象。 |
| SipClientConnection |
此类用于发送不会反复出现的SIP消息,如INVITE和MESSAGE。 |
| SipClientConnectionListener |
此接口必须由需要处理SIP响应的类来执行。 |
| SipServerConnectionListener |
此接口必须由计划接收SIP请求的类来执行。 |
| SipServerConnection |
此类可读取收到的消息。 |
| SipRefreshHelper |
该实用类管理反复发出的SIP消息(如REGISTER和SUBSCRIBE)。 |
| SipRefreshListener |
实现该接口可处理反复发出的消息的响应。 |
使用这些类可以完成三种典型的“操作”。本文将依次介绍各操作:
在介绍这些操作前,需要做一些基础工作。我们先来看看如何创建SipManager。
创建SipManager
虽然不必用此方法设计应用程序,我决定将整个SIP消息封装到一个单独的类中,即SipManager。正如前面提到的一样,这是一个可重用的类,没有假定其执行环境。 在现有MIDlet项目中创建新类。称为SipManager。现在使用Java编辑器开始编码。SipManager将实现以下接口:
public class SipManager implements SipServerConnectionListener,
SipRefreshListener, SipClientConnectionListener {
在显示信息时,单个构造函数将保存调用方的引用。它还发起注册(如果此功能已开启)并开始侦听到来的消息。我们将这称为“连接”。稍后我们会讨论连接问题。
public SipManager(MessageListener messageListener) throws IOException {
this.messageListener = messageListener;
reconnect();
}
SipManager包含许多字段。由于时间原因这里并不介绍这些琐碎代码。在这些字段中,某些字段是可在MEssenger configuration页面中进行修改的参数:
| |
|
| 类 |
说明 |
| Register |
布尔值,如果SIP客户机将自己注册为注册器,则为真。 |
| Username |
字符串,用于客户端SIP地址的标识符。例如:此标识符与sip:username@10.0.0.3:5060中的username部分相对应。 |
| Port |
整型,SIP客户端使用的端口。通常为5060,但如果在同一机器上运行多个SIP客户机和一个服务器,则需要使用不同地址。 |
| Registrar |
字符串,注册器地址,包括端口。例如:如果SIP地址是sip:username@10.0.0.3:5060,则为10.0.0.3:5060。 |
| Expires |
整型,注册持续的秒数。 |
当然,所有这类参数均有获取者和设置者。 SipManager还包含其他私有成员,如SipConnectionNotifier对象,它可接收消息、要使用的地址、以及反复发送的请求的标识符。稍后我们会讨论此问题。
发送一个请求
使用SIP可执行的最简单的操作是发送单个消息。图2说明了这一操作:  图2.发送一个请求
如图所示,发送消息的过程分为两部分。第一步是准备和发送消息。第二步是处理响应。我们来看一下执行此操作的代码。首先使用SipManager.sendMessage()方法执行第一步:
public void sendMessage(final String destination, final String message) {
Thread t = new Thread() {
public void run() {
SipClientConnection connection = null;
OutputStream output = null;
try {
connection = (SipClientConnection) Connector
.open(destination);
connection.setListener(SipManager.this);
connection.initRequest("MESSAGE", null);
connection.setHeader("From", registeredAddress);
connection.setHeader("To", destination);
connection.setHeader("Content-Type", "text/plain");
connection.setHeader("Content-Length", String
.valueOf(message.length()));
output = connection.openContentOutputStream();
output.write(message.getBytes());
output.close();
output = null;
} catch (Throwable e) {
messageListener.notifyMessage("Error sending to "
+ destination + ": " + e.getMessage());
e.printStackTrace();
try {
if (output != null) {
output.close();
}
if (connection != null) {
connection.close();
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
};
t.start();
}
您将注意到该方法开始了一个新线程。在此示例应用程序中的其他也会出现这种情况。为什么会这样呢?因为通讯需要耗费时间,而此时用户不希望GUI反应迟钝。此外,GUI线程在等待通信结束时会发生死锁,且通讯会触发GUI变更。 此示例代码相对简单。我将打开一个客户机连接,使用它接收响应,初始化请求类型并设置大量强制的标头。请求所需的大部分SIP标头会自动填充默认值。然后打开输出流并写入信息,最后关闭流。此时并没有关闭连接;还需等待响应到达。 值得注意的是:内容是可选的。请求可以为空。在此情况下,发送消息的方法是SipClientConnection.send(),而不只是关闭流。其他方法可用于自定义请求。包括:
- initCancel():创建CANCEL请求。代替initRequest()。
- initAck():创建ACK请求。代替initRequest()。
- setRequestUri():变更默认请求URI值。
- addHeader():用于插入重复的标头,例如:联系人。
- setCredentials():用于添加验证标头。
对于待处理的响应,SipManager必须实现SipClientConnectionListener。这包含一个方法,即notifyResponse()。响应到达后系统会自动调用此方法。实现将首先检查与响应相关的请求,然后显示消息:
- OK(在消息成功发送的情况下)。
- Error(在发送消息时出错的情况下)。
最后,关闭连接。
public void notifyResponse(SipClientConnection connection) {
try {
connection.receive(0);
String method = connection.getMethod();
if (method.equals("MESSAGE")) {
int status = connection.getStatusCode();
if (status == 200) {
messageListener.notifyMessage("Sent OK");
} else {
messageListener.notifyMessage("Error sending: " + status
+ " " + connection.getReasonPhrase());
}
return;
}
/* Registration code goes here. */
} catch (Throwable e) {
messageListener.notifyMessage("Error sending: " + e.getMessage());
} finally {
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
此操作结束。它真的很简单。让我们接着查看下一个操作。
接收请求
处理传入的请求有两种方法。第一种方法是同步法,即阻截当前线程以等待要到达的请求。我认为这不是最好的方法,但如果用户知道接收请求的时间或大概的时间范围,则此方法就很有效。由于此方法使用有限,因此这里不准备介绍此技术。 第二种方法是打开“永久”服务器连接,在消息异步到达时收到通知。这是首选技术,并且我打算在此使用它。 图3显示了应用程序处理请求的方式:  图3.接收请求
与发送请求一样,接收请求也分为两步。第一步是在服务器连接中注册监听程序来监听到来的消息。第二步是收到请求到来的通知并发送响应。此代码片段将完成第一步:
public void reconnect() {
Thread t = new Thread() {
public void run() {
doClose();
try {
sipConnection = (SipConnectionNotifier) Connector
.open("sip:" + port);
} catch (Throwable e) {
e.printStackTrace();
}
try {
sipConnection.setListener(SipManager.this);
contactAddress = "sip:" + username + "@"
+ sipConnection.getLocalAddress() + ":"
+ sipConnection.getLocalPort();
} catch (Throwable e) {
e.printStackTrace();
}
/* Registration code goes here. */
};
t.start();
}
注意Connector.open()的参数使用语法的方式: sip:port
不是应该使用sip:username@registraraddress:port吗?使用实际的SIP地址将创建客户机连接。在端口号后使用sip:或sips:将创建服务器连接。(创建服务器连接还有其他方法,但这些方法与了解MEssenger的工作方式无关。有关详细信息,请参阅SipConnection的Javadoc页面。)
接口SipConnectionNotifier很有趣。在此使用它来注册到来请求的监听程序。还可用它来检索设备地址。但是它并非有传言中的那样好,目前就我所知还没有实现的方法。(我也无法解释非SIP API不能实现的原因。)通过其acceptAndOpen()方法,还可将其用于阻塞和等待到来的请求。 此服务器连接打开之后,其会自动通知SipManager有请求消息到来。然后读取消息,并使用SipServerConnection对象发送相应的响应。方式如下:
public void notifyRequest(SipConnectionNotifier notifier) {
SipServerConnection connection = null;
InputStream input = null;
try {
connection = notifier.acceptAndOpen(); //Shouldn't block
String size = connection.getHeader("Content-Length");
int length = Integer.parseInt(size);
if (length == 0) {
connection.initResponse(200);
connection.send();
return; //nothing else to do...
}
byte buffer[] = new byte[length];
int readSize;
input = connection.openContentInputStream();
readSize = input.read(buffer);
String from = connection.getHeader("From");
SipAddress sipAddress = new SipAddress(from);
from = sipAddress.getDisplayName();
if (from != null)
from = from.trim();
if ((from == null) || (from.equals("")))
from = sipAddress.getURI();
String message = "From " + from + ": ";
message += new String(buffer, 0, readSize);
messageListener.notifyMessage(message);
//All done, reply OK.
connection.initResponse(200);
connection.send();
} catch (Throwable e) {
e.printStackTrace();
} finally {
try {
if (input != null)
input.close();
if (connection != null)
connection.close();
} catch (Throwable e) {
e.printStackTrace();
}
}
}
参数SipConnectionNotifier是SipServerConnection对象的工厂。注意如何使用SipServerConnection接收请求和返回响应。方法SipServerConnection类似于SipClientConnection,包括获取和设置标头和内容的方法,当然被initResponse(int statusCode)替换的init...()方法除外。此外,还可使用setReasonPhrase(String reason)替换响应中状态代码旁的默认文本。
注意:关闭SipServerConnection不代表关闭了创建它的SipConnectionNotifier。这只表示当前操作结束。
现在我们来看一下最后一种操作。
发送重复请求
REGISTER和SUBSCRIBE之类的请求是在特定间隔时间反复发送的请求。使用用于Java ME的SIP API中的刷新机制后,此作业可轻松完成。
重复请求包含的步骤如图4和图5所示。图4看起来类似于发送单个请求操作,但有一点不同。大家是否能发现不同之处?

图4.首次注册
不同之处在于调用方法SipClientConnection.enableRefresh()。此方法用于自动刷新请求和为刷新事件指定侦听程序。返回的标识符稍后可用于停止刷新任务。我将稍加讨论。首个REGISTER消息的响应会被发送到notifyResponse()方法。

图5.后续注册
SipRefreshHelper在请求到期前会使用某种计时器计划请求更新。后续请求的响应被发送到之前提供的RefreshListener。
我们来看一下与图5对应的代码。之前我对代码进行了几行注释,如下所示:
/* Registration code goes here. */
此注释标记了必须插入注册代码片段的位置。第一个片段从reconnect()方法内发送第一个REGISTER消息。我将其标为粗体,如下所示:
public void reconnect() {
Thread t = new Thread() {
public void run() {
// First half hidden for brevity
registeredAddress = "sip:" + username + "@" + registrar;
if (!register)
return;
try {
SipClientConnection registerConnection=createRegisterConnection();
registerConnection.setListener(SipManager.this);
refreshIdentifier = registerConnection
.enableRefresh(SipManager.this);
registerConnection.send();
registerConnection.close();
} catch (Throwable e) {
e.printStackTrace();
}
}
};
t.start();
}
代码本身一目了然。注意enableReferesh()方法的使用。作为方法notifyResponse()的一部分,下一段代码将作为第一个REGISTER消息的响应被调用:
public void notifyResponse(SipClientConnection connection) {
try {
// First half hidden for brevity
if (method.equals("REGISTER")) {
int status = connection.getStatusCode();
if (status == 200) {
messageListener.notifyMessage("Registration OK");
} else {
messageListener.notifyMessage("Error registering: "
+ status + " " + connection.getReasonPhrase());
}
return;
}
} catch (Throwable e) {
messageListener.notifyMessage("Error sending: " + e.getMessage());
} finally {
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
注册代码的最后一个代码段实现RefreshListener接口。它由一个refreshEvent()方法组成:
public void refreshEvent(int refreshID, int statusCode, String reasonPhrase) {
if (statusCode == 200) { //OK
messageListener.notifyMessage("Re-registered OK.");
}
else { //ERROR!
messageListener.notifyMessage("Error registering: " + statusCode);
SipRefreshHelper.getInstance().stop(refreshIdentifier);
}
}
此代码只显示了有关注册状态的消息,并且在出错情况下,将停止刷新计时器。
清理代码
这个示例基本完成。惟一缺少的是执行清理操作的代码,它将关闭连接并停止刷新计时器。在应用程序关闭时可调用此代码。
public void close() {
Thread t = new Thread() {
public void run() {
doClose();
}
};
t.start();
}
protected void doClose() {
if (contactAddress == null)
return; //No need to unregister and close connection; there wasn't a connection.
try {
if (register)
SipRefreshHelper.getInstance().stop(refreshIdentifier);
} catch (Throwable e) {
e.printStackTrace();
}
try {
if (sipConnection != null) {
sipConnection.close();
sipConnection = null;
}
} catch (Throwable e) {
e.printStackTrace();
}
}
先停止刷新任务。这将发送未注册消息(Expires标头为0的REGISTER消息)。然后关闭服务器连接。在新线程中执行所有操作,以便不会中断GUI线程。
结束语
小但实用的Messenger现在已经完成。要查看其实际操作,可参见下面的图6:

图6. MEssenger实际操作
或者,还可直接在移动电话上运行此应用程序!
使用注册器
如果要使用注册,则需要使用注册器。BEA WebLogic SIP Server附带了注册器和代理。本节将介绍如何配置和使用它们。
在编写本文时,此代理还不能处理SIP MESSAGE消息。我必须配置此代理,以使其可以处理此类消息。只需编辑文件C:\bea\sipserver30\samples\sipserver\examples\src\registrar\WEB-INF\sip.xml(此文件夹是默认安装文件夹;可以使用选择的任何安装文件夹)并添加以下标签,即可完成配置:
<servlet-mapping>
<servlet-name>proxy</servlet-name>
<pattern>
<equal>
<var>request.method</var>
<value>MESSAGE</value>
</equal>
</pattern>
</servlet-mapping>
完成此操作后,使用以下步骤构建并部署注册器应用程序:
- 创建环境变量WL_HOME,指向SIP服务器文件夹。例如,此操作可通过在命令窗口中键入以下内容来完成:
set WL_HOME=c:\bea\sipserver30
(此文件夹是默认安装文件夹,可以使用安装SIP服务器时使用的文件夹。)
- 向类路径添加weblogic.jar。例如,此操作可通过在命令窗口中键入以下内容来完成:
set classpath=%CLASSPATH%;%WL_HOME%\server\lib\weblogic.jar
- 现在可以开始构建了。转到注册器源文件夹:
cd %WL_HOME%\samples\sipserver\examples\src\registrar
- 接着使用Ant进行构建:
ant build
- 创建WebLogic SIP Server域,以便在其中运行应用程序。
- 最后,将此应用程序部署到运行的服务器上:
ant deploy。
现在即可将MEssenger注册到SIP服务器了。
MEssenger还可与前面文章提到的ChatRoomServer servlet兼容。
下载
- MEssenger.zip (7 KB):访问本文介绍的应用程序的全部源代码。
总结
本文演示了使用Java ME进行SIP编程是多么简单。借助简单的MEssenger应用程序,还演示了许多有用的通信模式实现。简单的库与灵活的SIP相结合可开发出不计其数的应用程序。
现在,我朋友和我的移动电话都是启用IM的电话,我们经常使用它们聊天。让我们尽情享受移动电话带来的乐趣吧!
参考资料
- JSR 180
- SIP简介,第1部分:SIP初探(中文版)
- SIP简介,第2部分:SIP SERVLET(中文版)
- 有关注册器示例文档,请参见默认路径C:\bea\sipserver30\samples\sipserver\examples\src\registrar\readme.html
附录
本注释介绍了安装和配置Java Wireless Toolkit和EclipseME的提示。旨在帮助大家在Java ME平台下开发SIP应用程序做准备。
Java Wireless Toolkit的安装提示
Java Wireless Toolkit (WTK)是一套可用于开发用于移动电话和类似设备的应用程序的工具。它包含大量库(包括用于Java ME的SIP API的实现)和一个仿真器,所有工具包装在一个易于安装的工具包中。这样便于在SIP开发环境下作出轻松选择。
有关最新Java Wireless Toolkit的下载,请参见Java ME下载页面:
http://java.sun.com/javame/downloads
可导航到下载页面。在下载安装程序前,必须注册(免费)。在此还必须使用下载中心的用户名和密码登录。
Windows安装程序名类似于j2me_wireless_toolkit-x_y-windows.exe(其中,x和y分别表示主次版本号)。将文件下载到硬盘上。然后运行执行文件并按照屏幕上显示的步骤进行操作。Quick Time Player选项是可选的,它可帮助用户在仿真器中播放媒体文件。如果未显示Quick Time Player而用户又希望安装它,请转到此地址。
http://www.apple.com/quicktime/download/standalone.html
EclipseME的安装提示
Eclipse ME是用于Eclipse的插件,它有助于轻松开发Java ME应用程序。它与Java Wireless Toolkit完全集成。设置Eclipse ME分两步进行。第一步是安装EclipseME。第二步是对其进行配置。该部分将介绍第一步。
获取EclipseME最简便的方法是使用Eclipse Software Updates。先启动Eclipse,然后转到菜单Help > Software Updates > Find and Install。选择“Search for new features to install”,然后单击Next。在下一页上单击按钮New Remote Site。输入以下信息:
- Name: EclipseME
- URL: http://www.eclipseme.org/updates
单击Finish。现在Eclipse即可在更新站点查找EclipseME。在下一个对话框中选择EclipseME并继续操作直到完成安装。
单击Install继续操作。安装完EclipseME后,必须重新启动Eclipse。
EclipseME的配置提示
前面我说过EclipseME是与Java Wireless Toolkit集成的。虽然如此,但要让它们协调运作还必须执行一些配置操作。以下是需要执行的步骤:
- J2ME首选项:在Eclipse中,转到Preferences对话框(菜单Window > Preferences)。导航到J2ME类别。必须在WTK Root字段中输入Java Wireless Toolkit的位置。
- 导航到Device Management类别。设备列表为空。EclipseME可搜索所需设备。单击Import。再次进入WTK文件夹,并选择所有设备。单击Finish。各设备即被导入列表。将要使用的一个设备选作默认值。
- 调试设置:需要对某些设置进行调试,以使Java Wireless Toolkit能够在调试器中工作。必须在Java > Debug 类别中设置以下选项:
- Suspend execution on uncaught exceptions:不选择。
- Suspend execution on compilation errors:不选择。
- Debugger timeout (ms): 15000(15秒)。
如果没有这些选项,调试则无法执行。
EclipseME配置提示
安装现在结束。我们要测试一下程序是否能正常运行。
- 创建J2ME项目:在Eclipse中,转到菜单File > New > Project。选择类别J2ME > J2ME Midlet Suite。单击Next。输入项目名称。单击Next。选择部署文件(JAD文件)的名称。单击Finish。
- 创建包。
- 创建MIDlet:转到菜单File > New > Other。导航到J2ME > J2ME Midlet。单击Next。输入类的名称。单击Finish。
- 完成操作后,查看生成的MIDlet代码。
了解Java ME编程
有许多在线指南可供参考。请参见以下内容:
| 作者简介 |
|
Emmanuel Proulx 是一位J2EE和Enterprise JavaBeans方面的专家,也是一位获得认证的WebLogic Server 7.0工程师。 |
作者其它文章
|