科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道Java ME的SIP API简介

Java ME的SIP API简介

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

本文将提供一个易于使用的方法来开发使用SIP的Java ME应用程序。SipManager还包含其他私有成员,如SipConnectionNotifier对象,它可接收消息、要使用的地址、以及反复发送的请求的标识符。

来源:dev2dev 2007年10月15日

关键字:

  • 评论
  • 分享微博
  • 分享邮件

  本文将提供一个易于使用的方法来开发使用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。

  先决条件

  要从本文中获取最大收益,必须在开发环境中安装以下工具:

  最新版的Eclipse

  Java Wireless Toulkit(JWT)

  配置使用JWT的EclipseME插件

  此外,还必须了解一点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(此文件夹是默认安装文件夹;可以使用选择的任何安装文件夹)并添加以下标签,即可完成配置:

  

  

  proxy

  

  

  request.method

  MESSAGE

  

  

  

  完成此操作后,使用以下步骤构建并部署注册器应用程序:

  创建环境变量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下载页面:

  可导航到下载页面。在下载安装程序前,必须注册(免费)。在此还必须使用下载中心的用户名和密码登录。

  Windows安装程序名类似于j2me_wireless_toolkit-x_y-windows.exe(其中,x和y分别表示主次版本号)。将文件下载到硬盘上。然后运行执行文件并按照屏幕上显示的步骤进行操作。Quick Time Player选项是可选的,它可帮助用户在仿真器中播放媒体文件。如果未显示Quick Time Player而用户又希望安装它,请转到此地址。

  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编程

  有许多在线指南可供参考。请参见以下内容:

  http://www.javaworld.com/javaworld/jw-05-2005/jw-0502-midlet.html

  http://today.java.net/pub/a/today/2005/02/09/j2me1.html

  http://today.java.net/pub/a/today/2005/05/03/midletUI.html

  http://developers.sun.com/techtopics/mobility/midp/articles/wtoolkit/

  http://developers.sun.com/techtopics/mobility/midp/articles/tutorial2/

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章