Java ME的SIP API简介

来源:互联网 发布:激战2 mac 国服 编辑:程序博客网 时间:2024/06/05 20:23

Java MESIP API简介

时间:2007-08-06
作者:Emmanuel Proulx

 

  本文将提供一个易于使用的方法来开发使用SIPJava ME应用程序。同时还将检查随Java Wireless Toulkit发行的用于Java ME (JSR 180)SIP API。本文还讨论了使用此技术的各种方法。您将看到能够运行于移动电话或仿真器的一个真正的SIP应用程序。

简介:SIP + Java =卓越

  移动电话和可连接到InternetPDA越来越受到人们的欢迎。我的所有朋友都使用它们,并且结合使用了大量新的应用程序,。其中许多程序可以连网,不论是客户端/服务器还是点对点设备。

  开发可移动的网络应用程序时,需要选择通讯协议。开发者可打开套接字并创建一个完全私有的协议。可使用具有私有APISOAP,也可使用完全基于标准的方法。鉴于以下原因,我建议使用后者:

  • 在包含库的情况下更易进行开发。
  • 可提供更多控制,例如:除了根据下载的KB数量外还可根据交互类型收费。
  • 移动运营商可阻止非标准协议。
  • 可与各种设备进行互操作。

  这就是我建议使用SIP进行移动网络编程的原因。SIP是移动运营商使用的标准连接协议。此外,它所使用的库也易于查找和使用。有关SIP简介,请参见介绍性文章: SIP简介,第1部分:SIP初探 (中文版)和 SIP简介,第2部分:SIP SERVLET(中文版)

  使用Java MESIP编程非常简单。最新移动库构成了丰富的编程环境,这使得应用程序的开发变得轻而易举。

  本文将介绍为移动电话构建简单的messenger移动电话客户端的方式。该方式使用SIP协议并使用Java ME库进行构建。此应用程序可单独运行,也可将其配置为使用SIP注册器,如BEA WebLogic SIP Server

先决条件

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

  • 最新版的Eclipse
  • Java Wireless Toulkit (JWT)
  • 配置使用JWTEclipseME插件

  此外,还必须了解一点Java ME知识。有关这些软件的帮助信息,请阅读附录

MEssenger应用程序

  我决定通过开发即时消息传递客户机应用程序来演示SIPJava ME中的使用。此应用程序虽然简单,但可演示发送消息(REGISTERMESSAGE)、处理响应和处理收到的消息等功能。

   我将此应用程序命名为MEssenger(其发音为“mee-senger”,与Java ME中的“ME”一样,此处的“ME”表示Micro Edition)。它具有很简单的GUI(两页)。主页可用于收发消息。第二个页面用于配置应用程序。本文没有包含其他有趣的功能。

  MEssenger的导航界面如图1所示。输入目标SIP地址和消息并选择menu中的Send后即可发送消息。该应用程序最近的五个事件可显示在下半个屏幕中。配置页面中可输入注册信息。

  Java ME的SIP API简介 图-1

  图1. MEssenger导航

  在深入研究应用程序的代码前,我们先看一下应用程序的设计。

设计

  我们将要编写的应用程序由以下五种类和接口构成:

 

 

说明

MEssengerMIDlet

Java ME应用程序的类。它可创建并显示MenuManager示例。

MenuManager

此类包含页面和导航事件。它还可以实例化SipManager类。此类可实现MessageListener接口。

SipManager

此类包含通信行为;它可收发消息、安排注册。

ErrorAlert

显示错误的实用类。

MessageListener

SipManager使用的接口,请求MenuManager显示消息。这将拆分两个类,可在不使用MenuManager的其他应用程序中重用SipManager

  正如我先前说过,本文的目的不是介绍Java ME,因此接下来我将重点介绍SipManager类。有关其他类的详细信息,请参阅文本包含的源代码。

关于Java MESIP API

  使用Java ME进行SIP编程有些像套接字编程。它将显示打开和关闭客户机和服务器连接、数据流以及线程等概念。我将展示示例所需的所有不同类。首先我将列出此API的一些关键类:

 

 

说明

Connector

创建各种连接对象的工厂。对于SIP连接,只需使用以“sip:”开头的地址,Connector就可创建SipClientConnectionSipServerConnection 对象。

SipClientConnection

此类用于发送不会反复出现的SIP消息,如INVITEMESSAGE

SipClientConnectionListener

此接口必须由需要处理SIP响应的类来执行。

SipServerConnectionListener

此接口必须由计划接收SIP请求的类来执行。

SipServerConnection

此类可读取收到的消息。

SipRefreshHelper

该实用类管理反复发出的SIP消息(如REGISTERSUBSCRIBE)。

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说明了这一操作:

  Java ME的SIP API简介 图-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显示了应用程序处理请求的方式:

  Java ME的SIP API简介 图-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的工作方式无关。有关详细信息,请参阅 SipConnectionJavadoc页面。)

  接口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();

    }

  }

}

  参数SipConnectionNotifierSipServerConnection对象的工厂。注意如何使用 SipServerConnection接收请求和返回响应。方法SipServerConnection类似于 SipClientConnection,包括获取和设置标头和内容的方法,当然被initResponse(int statusCode)替换的init...()方法除外。此外,还可使用setReasonPhrase(String reason)替换响应中状态代码旁的默认文本。

  注意:关闭SipServerConnection不代表关闭了创建它的SipConnectionNotifier。这只表示当前操作结束。

  现在我们来看一下最后一种操作。

发送重复请求

  REGISTERSUBSCRIBE之类的请求是在特定间隔时间反复发送的请求。使用用于Java MESIP API中的刷新机制后,此作业可轻松完成。

  重复请求包含的步骤如图4和图5所示。图4看起来类似于发送单个请求操作,但有一点不同。大家是否能发现不同之处?

  Java ME的SIP API简介 图-4

  图4.首次注册

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

  Java ME的SIP API简介 图-5

  图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标头为0REGISTER消息)。然后关闭服务器连接。在新线程中执行所有操作,以便不会中断GUI线程。

结束语

  小但实用的Messenger现在已经完成。要查看其实际操作,可参见下面的图6

  Java ME的SIP API简介 图-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>

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

  1. 创建环境变量WL_HOME,指向SIP服务器文件夹。例如,此操作可通过在命令窗口中键入以下内容来完成:

  set WL_HOME=c:/bea/sipserver30

  (此文件夹是默认安装文件夹,可以使用安装SIP服务器时使用的文件夹。)

  1. 向类路径添加weblogic.jar。例如,此操作可通过在命令窗口中键入以下内容来完成:

  set classpath=%CLASSPATH%;%WL_HOME%/server/lib/weblogic.jar

  1. 现在可以开始构建了。转到注册器源文件夹:

  cd %WL_HOME%/samples/sipserver/examples/src/registrar

  1. 接着使用Ant进行构建:

  ant build

  1. 创建WebLogic SIP Server域,以便在其中运行应用程序。
  2. 最后,将此应用程序部署到运行的服务器上:

  ant deploy

  现在即可将MEssenger注册到SIP服务器了。

  MEssenger还可与前面文章提到的ChatRoomServer servlet兼容。

下载

  1. MEssenger.zip (7 KB):访问本文介绍的应用程序的全部源代码。

总结

  本文演示了使用Java ME进行SIP编程是多么简单。借助简单的MEssenger应用程序,还演示了许多有用的通信模式实现。简单的库与灵活的SIP相结合可开发出不计其数的应用程序。

  现在,我朋友和我的移动电话都是启用IM的电话,我们经常使用它们聊天。让我们尽情享受移动电话带来的乐趣吧!

参考资料

  1. JSR 180
  2. SIP简介,第1部分:SIP初探(中文版)
  3. SIP简介,第2部分:SIP SERVLET(中文版)
  4. 有关注册器示例文档,请参见默认路径C:/bea/sipserver30/samples/sipserver/examples/src/registrar/readme.html

附录

  本注释介绍了安装和配置Java Wireless ToolkitEclipseME的提示。旨在帮助大家在Java ME平台下开发SIP应用程序做准备。

  Java Wireless Toolkit的安装提示

  Java Wireless Toolkit (WTK)是一套可用于开发用于移动电话和类似设备的应用程序的工具。它包含大量库(包括用于Java MESIP API的实现)和一个仿真器,所有工具包装在一个易于安装的工具包中。这样便于在SIP开发环境下作出轻松选择。

  有关最新Java Wireless Toolkit的下载,请参见Java ME下载页面:

  http://java.sun.com/javame/downloads

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

  Windows安装程序名类似于j2me_wireless_toolkit-x_y-windows.exe(其中,xy分别表示主次版本 号)。将文件下载到硬盘上。然后运行执行文件并按照屏幕上显示的步骤进行操作。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。输入以下信息:

  1. Name: EclipseME
  2. URL: http://www.eclipseme.org/updates

  单击Finish。现在Eclipse即可在更新站点查找EclipseME。在下一个对话框中选择EclipseME并继续操作直到完成安装。

  单击Install继续操作。安装完EclipseME后,必须重新启动Eclipse

  EclipseME的配置提示

  前面我说过EclipseME是与Java Wireless Toolkit集成的。虽然如此,但要让它们协调运作还必须执行一些配置操作。以下是需要执行的步骤:

  1. J2ME首选项:在Eclipse中,转到Preferences对话框(菜单Window > Preferences)。导航到J2ME类别。必须在WTK Root字段中输入Java Wireless Toolkit的位置。
  2. 导航到Device Management类别。设备列表为空。EclipseME可搜索所需设备。单击Import。再次进入WTK文件夹,并选择所有设备。单击Finish。各设备即被导入列表。将要使用的一个设备选作默认值。
  3. 调试设置:需要对某些设置进行调试,以使Java Wireless Toolkit能够在调试器中工作。必须在Java > Debug 类别中设置以下选项:
  4. Suspend execution on uncaught exceptions:不选择
  5. Suspend execution on compilation errors:不选择
  6. Debugger timeout (ms): 1500015秒)。

  如果没有这些选项,调试则无法执行。

EclipseME配置提示

  安装现在结束。我们要测试一下程序是否能正常运行。

  1. 创建J2ME项目:在Eclipse中,转到菜单File > New > Project。选择类别J2ME > J2ME Midlet Suite。单击Next。输入项目名称。单击Next。选择部署文件(JAD文件)的名称。单击Finish
  2. 创建包。
  3. 创建MIDlet:转到菜单File > New > Other。导航到J2ME > J2ME Midlet。单击Next。输入类的名称。单击Finish
  4. 完成操作后,查看生成的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/

 作者简介

 

Emmanuel Proulx 是一位J2EEEnterprise JavaBeans方面的专家,也是一位获得认证的WebLogic Server 7.0工程师。