内容


IBM WebSphere 开发者技术期刊

WebSphere Application Server V6.1 中的会话发起协议——第 2 部分

开发聚合应用程序

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: IBM WebSphere 开发者技术期刊

敬请期待该系列的后续内容。

此内容是该系列的一部分:IBM WebSphere 开发者技术期刊

敬请期待该系列的后续内容。

摘自 IBM WebSphere 开发者技术期刊

引言

Internet 协议 (IP) 聚合通常是指将各种网络类型转移到 IP。由于目前数据网络主要在 IP 上,这对于如何将语音和视频网络转移到 IP 将有更多的工作要做。

在企业和家庭中,IP 电话技术和 IP 语音 (VoIP) 正在成为主流技术。数年来,诸如 Avaya、Cisco、Nortel 和 Alcatel 之类的公司已开发了大量的 IP 电话技术产品。这些产品最初主要部署在呼叫和联系中心,但是,现在越来越普遍地部署在公司中。与此同时,IP 电话技术已通过电话服务(如 AT&T CallVantage 和 Vonage)或通过 Skype(现在是 Ebay 的一部分)、AOL IM、Google Talk 和许多其他供应商提供的计算机接口而应用到家庭中。

在视频方面,市场也开始向 IP 转移。IP 电视 (IPTV) 已开始广泛使用,像 AT&T 之类的大公司正在向特定城市提供产品,Microsoft 和 Nortel 在 IPTV 基础结构产品方面投入了大量的资金。与此同时,移动 TV 网络也在升温,像 ESPN 和 NFL 这样的分支机构开始通过 Sprint PCS 提供移动服务。

IP 上语音和视频聚合的一个主要基础部分是会话发起协议 (SIP)。SIP 是为帮助协商两个对等端之间的交互通信会话而设计的。SIP 是被称为 IP 多媒体子系统 (IMS) 的新一代电信网络的基础。另外,许多 IP 电话技术供应商现在都把 SIP 作为 IP 电话技术部署的控制协议。随着企业语音网络正在快速向 IP 电话技术转移,创建能够利用聚合网络的应用程序,进而创建功能强大的应用程序的机会也在增多。其他基础结构(如移动和协作服务)现在也在大量利用或即将利用 SIP。这些技术将促进开发新应用程序的可能性,从而使工作者的工作效率更高。

由于 SIP 是在超文本传输协议 (HTTP) 之后建模的,所以许多用户希望知道如何组合这些技术,以获得更好的用户体验。由于通常将 HTTP 设计为向每个用户传输图形信息的协议,并且 SIP 没有图形部分,所以许多人将这两种技术组合在一起,使 SIP 流程可以使用 HTTP 进行其传输,来提供图形界面。这可能包括启动电话呼叫或即时消息会话、监视这些会话的状态或终止这些会话。将 SIP 与 HTTP 聚合的另一个重要方面是使用 Web 服务。这将包括构建依赖于 Web 服务的 SIP 应用程序或依赖于 SIP 的 Web 服务,从而促进新的创新功能。

IBM WebSphere Application Server 在 6.1 版本中添加了对 SIP Servlet 1.0 规范 JSR 116 的支持。WebSphere Application Server V6.1 还包括对 HTTP Servlet、Portlet、JSP 和 JSF 的支持。由于对 HTTP 或基于 Web 的图形技术的强健支持,许多公司开发了在 WebSphere Application Server 上部署的 Web 应用程序。增加 SIP Servlet 规范使您能够轻松组合这些技术,以获得 Web 之外的丰富体验。SIP Servlet 1.0 规范中的一个主要概念是聚合应用程序。一般的观点是一个应用程序可以包含来自多个协议的会话,并使用这些协议创建丰富的用户体验。当前版本尤其如此,一个应用程序可以包括 HTTP 和 SIP 功能,并且会话可能是相互交错的。

下面是一些能够聚合的应用程序的示例:

  1. 从任何显示的电话号码启动电话呼叫。网页上到处存在电话号码,用户可以单击电话号码,开始电话呼叫。对于显示客户概要或内部对等端的应用程序,这个便捷的方法可确保不会拨错号码,从而提高工作效率。

  2. 通过电话、即时消息或电子邮件与某人联系。每个电子邮件地址都提供一个热链接菜单,通过该菜单可以呼叫某人、传递即时消息或发送电子邮件。使用 SIP 可以启动呼叫和即时消息会话。还可以使用 SIP 检查这些用户是否在线。

  3. 主动从网页监视电话会议。如果某人的移动电话有噪音,或线路有许多静电噪音,则可以快速查明该线路,并可以通过网页消除噪音。

可以提高工作效率的聚合应用程序的示例很多。通过开发公共资产,可以更容易地创建上述应用程序,执行诸如两个给定地址之间的电话呼叫。可能需要针对环境自定义某些资产。与此同时,公司在开始创建产品来帮助集成聚合应用程序。

本文中的应用程序

在本文中,我们将介绍聚合 HTTP 和 SIP 应用程序的开发。HTTP 应用程序将显示采用两个 SIP 用户代理服务器 (UAS) 的地址和端口号的网页,并启动它们之间的连接。它还可以在任何时候终止连接。可以将此应用程序称为 CallLauncher。为了方便起见,本文在下载文件中还包含了此应用程序。

设置开发环境

本文中的集成开发环境 (IDE) 是构建在开放源码 Eclipse v3.1.2 技术之上的 WebSphere Application Server Toolkit (AST) V6.1,它还是 IBM Rational Application Developer 的基础(请参见参考资料)。

先决条件

本文假设您对 IP 协议(特别是 HTTP 和 SIP)有基本的了解,还假定您非常熟悉 Java™ 语言。具有 WebSphere Application Server、Java Platform、Enterprise Edition (Java EE) 和 HTTP Servlet 的背景知识有助于理解本文中涉及的所有概念。

本文假设您已阅读了第 1 部分:SIP 简介

编写聚合应用程序

下面将详细介绍创建聚合应用程序的步骤。主要步骤包括:

  1. 创建 SIP 项目
  2. 创建 SIP Servlet
  3. 创建 HTTP Servlet
  4. 编写 HTTP 和 SIP Servlet 的源代码
  5. 在应用服务器上部署应用程序
  6. 运行应用程序

A. 在 AST 中创建 SIP 项目

  1. 启动将在其下开发源的项目。首先,在 Application Server Toolkit (AST) 中,选择 File => New => Project

  2. 在 New Project 对话框中,选择您要创建的项目类型。选择 SIP => Converged Project,然后选择 Next(图 1)。

    图 1. 创建新的项目
    图 1. 创建新的项目
    图 1. 创建新的项目
  3. 在 New SIP/HTTP Project 对话框中命名此新的项目(图 2)。在 Project Name 字段中,输入项目的名称。这里,我们将应用程序称为 CallLauncher。单击 Next

    图 2. 命名聚合项目
    图 2. 命名聚合项目
    图 2. 命名聚合项目
  4. Select Project Facets 对话框允许您配置需要让此项目能够支持的元素。对于我们此处所述的聚合应用程序,将需要 Web 选项:

    • Dynamic Web Module
    • WebSphere Web (Co-existence)
    • WebSphere Web(Extended)

    加上:

    • Java
    • SIP Module

    选择这些选项后,单击 Next

    图 3. 添加 Java 支持
    图 3. 添加 Java 支持
    图 3. 添加 Java 支持
  5. 在 Web Module 对话框中,请配置以下主要信息。首先,配置 Context Root,它是 URL 的基础,应用服务器在该基础上承载 Web Module。在我们的示例中,Context Root 是 CallLauncher。这意味着要调用我们创建的 HTTP Servlet,我们必须首先寻址应用服务器及其上下文的根。例如,它可能类似于:

    http://application.server.example.com:9080/CallLauncher/MyServlet

    其中,在此上下文根中,MyServlet 是 Servlet 的名称,9080 是应用服务器侦听的端口,application.server.example.com 是运行应用服务器的服务器的主机名。接受此面板上其他选项的缺省值。完成项目创建时,单击 Finished

    图 4. 配置 Web 模块设置
    图 4. 配置 Web 模块设置
    图 4. 配置 Web 模块设置

B. 创建 SIP Servlet

在我们的示例中,CallLauncher 示例可用于以下这些任务:

  • 处理通过 HTTP Servlet 启动的第一个用户代理服务器 (UAS-1) 的 SIP 协议响应。
  • 从第一个服务器成功获取响应之后,启动对第二个用户代理服务器 (UAS-2) 的请求。

图 5 显示了 CallLauncher Servlet 的初始呼叫流程。绿线上方的所有内容由 HTTP Servlet 处理,绿线下方的所有内容由 SIP Servlet 处理。同时需要这两个 Servlet 才能完成此应用程序的目标。

图 5. CallLauncher Servlet 流程
图 5. CallLauncher Servlet 流程
图 5. CallLauncher Servlet 流程

接下来,我们将创建执行以上操作的 SIP Servlet:

  1. 选择 File => New => Other => SIP => SIP Servlet => SIP Servlet,然后选择 Next

    图 6. 创建 SIP Servlet
    图 6. 创建 SIP Servlet
    图 6. 创建 SIP Servlet
  2. 在下一个对话框(图 7)中,您可以命名 SIP Servlet,并选择与其关联的项目。选择 CallLauncher 项目,并在 Java 包 com.ibm.sip.test 中将 Servlet Class 命名为 CallLauncherSIP。完成之后,单击 Next

    图 7. 指定类文件目标
    图 7. 指定类文件目标
    图 7. 指定类文件目标
  3. 如果您的 SIP Servlet 需要任何其他映射或初始化参数,则可以将它们添加到图 8 所示的对话框中。我们无需对此应用程序设置任何内容,因此请单击 Next

    图 8. 输入特定于 Servlet 部署描述符的信息。
    图 8. 输入特定于 Servlet 部署描述符的信息。
    图 8. 输入特定于 Servlet 部署描述符的信息。
  4. 在下一个对话框(图 9)中,您可以为您要实现的特定方法创建方法存根。对于此应用程序,我们将实现对 BYE (doBye) 和任何响应 (doResponse) 的侦听。我们的应用程序不能通过它(如图 5 所示)获取任何请求消息,所以这些响应都是非常重要的。完成后,单击 Finish

    图 9. 添加修饰符、接口和方法存根
    图 9. 添加修饰符、接口和方法存根
    图 9. 添加修饰符、接口和方法存根
  5. 现在 Servlet 创建好了。

在开始编辑 Servlet 中的功能之前,我们需要先创建 HTTP Servlet。

C. 创建 HTTP Servlet

HTTP Servlet 用于生成允许您输入每个 UAS 主机名和端口的网页,并且只需单击,就可以启动二者之间的呼叫。它还能够终止呼叫。由于 HTTP Servlet 是非常通用的组件,所以此 Servlet 侧重于与 SIP 的交互,没有丰富的 Web 体验。

  1. 要创建 HTTP,请选择 File => New => Other => Web => Servlet,然后选择 Next(图 10)。

    图 10. 创建新的 Servlet
    图 10. 创建新的 Servlet
    图 10. 创建新的 Servlet
  2. 在下一个窗口中,为 Class name 指定 CallLauncherHTTP,为 package 指定 com.ibm.http.test,然后单击 Next(图 11)。

    图 11. 为新的 Servlet 指定类文件目标
    图 11. 为新的 Servlet 指定类文件目标
    图 11. 为新的 Servlet 指定类文件目标
  3. 接下来的面板将类似于我们创建 SIP Servlet 时采用的步骤。我们没有设置任何初始化参数,仅选择在 GET (doGet) 请求时得到通知。

D. 为 HTTP 和 SIP Servlet 编写源代码

要为这些 Servlet 创建源,您可能需要打开每个 Servlet。您应在左侧看到 Dynamic Web Projects 下的 CallLauncher 项目。如果您接受前面三个部分中的缺省值,则在该项目下,应有一个 src 目录。每个 Servlet 都将显示在该 src 目录下。双击每个 Servlet 可以在主面板中将其更改为编辑模式(图 12)。

图 12. 编辑 Servlet
图 12. 编辑 Servlet
图 12. 编辑 Servlet

编写 HTTP Servlet

  1. 将实现 HTTP Servlet 来执行几个特定的任务。图 13 显示了将在此 Servlet 中创建的代码的基本流程。实际的 HTML 和表单将是最基本的,但是它们将显示与 SIP 所需的交互。

    图 13. HTTP Servlet 基本流程
    图 13. HTTP Servlet 基本流程
    图 13. HTTP Servlet 基本流程

    我们将分步说明与初始请求一起使用的代码。通常,我们将创建一个表单,该表单包括 To 和 From 主机名和端口的输入。必须代表其中一个用户启动呼叫。表单输入为:

    • toSipAddress
    • toSipPort
    • fromSipAddress
    • fromSipPort。
    resp.setContentType("text/html");
    resp.getWriter().print("Call Launcher Sample Application <BR><hr>");
    resp.getWriter().print("create new call: <BR>");
    resp.getWriter().print("<form>");
    resp.getWriter().print("To (host): <input type=\"text\" name=\"toSipAddress\">");
    resp.getWriter().print(" Port: <input type=\"text\" name=\"toSipPort\">");
    resp.getWriter().print("<br>");
    resp.getWriter().print("From (host): <input type=\"text\" name=\"fromSipAddress\">");
    resp.getWriter().print(" Port: <input type=\"text\" name=\"fromSipPort\">");
    resp.getWriter().print("<input type=\"submit\" value=\"create call\">");
    resp.getWriter().print("</form>");
  2. 设置了用户代理服务器地址后,下一个主要步骤是创建进行电话呼叫的功能。我们需要将一些代码添加到 Servlet,以获取对此应用程序的 SipFactory 的访问。javax.servlet.sip.SipFactory 接口 API 提供了从应用程序中创建更多请求和会话的访问权。为 SipFactory 声明一个变量,并导入该变量。然后,在 Servlet 初始化中,设置 SipFactory:

    import javax.servlet.sip.SipFactory;
    …
     private SipFactory _sipFactory = null;
     public void init() throws ServletException {
    	 _sipFactory = (SipFactory) 
           getServletContext().getAttribute(SipServlet.SIP_FACTORY);
    	 if (_sipFactory == null) {
    		 System.out.println("CallLauncherHTTP: Error no SipFactory in context");
    	 }
     }
  3. 由于能够访问 SipFactory,因此 Servlet 现在可以启动呼叫。要启动呼叫,我们将需要访问 SipApplicationSession。WebSphere Application Server 可以从 HttpSession 访问 SipApplicationSession。有两个特定于 WebSphere Application Server 的独立 API,它们扩展了规范的 API,用于提供对聚合应用程序的 SipApplicationSession 的访问。第一个 API 来自 IBMSession,WebSphere Application Server 内部的 HttpSession 可以强制转换为 IBMSession。从 IBMSession,可以获取将其强制转换为 SipApplicationSession 的 IBMApplicationSession。我们将此代码添加到 doGet 方法的开头:

    HttpSession httpSession=req.getSession();
    IBMSession extSession = (IBMSession)httpSession;
    IBMApplicationSession ibmAppSession = extSession.getIBMApplicationSession();
    SipApplicationSession appSession = (SipApplicationSession)ibmAppSession ;
  4. 我们需要添加的下一代码片段将从我们前面创建的表单获取表单变量。我们可以从请求获取一些简单的参数来执行此操作:

    String toAddressStr = req.getParameter("toSipAddress");
    String toPortStr = req.getParameter("toSipPort");
    String fromAddressStr = req.getParameter("fromSipAddress");
    String fromPortStr = req.getParameter("fromSipPort");
    String appSessionId = req.getParameter("appid");
  5. 要在创建初始表单和使用服务器启动呼叫之间进行选择,我们需要查看是否设置了表单变量:

    if((toAddressStr!=null) &&( fromAddressStr!=null)){

    还有其他代码块在其中跟随初始表单代码。

  6. 现在我们已经有了地址,并准备好启动呼叫,我们还需要设置 SIP 请求。在 SIP 请求中创建将在 To 和 From 字段中包括的 SIP URI。按照 RFC 3261,To 和 From 字段中将不包括端口。可以从 SipFactory 创建 SipURI 对象:

    SipURI to = _sipFactory.createSipURI("user1",toAddressStr);
    SipURI from = _sipFactory.createSipURI("user2",fromAddressStr);
  7. 由于此 HttpServlet 仅发送第一个 SIP 请求,SIP Servlet 发送第二个 SIP 请求,所以我们需要存储 From 地址,以便稍后第二条呼叫线路可以访问它:

    appSession.setAttribute("FROM_ADDRESS", fromAddressStr);
    appSession.setAttribute("FROM_PORT", fromPortStr);
  8. 接下来,我们将构建 SIP INVITE 请求,并将其发送到第一台服务器。可以使用 SipFactory 和 To 及 From 地址创建 SipServletRequest。然后,在我们发出请求之前,创建另一个 SipURI,以便向其附加端口。设置了端口后,我们就可以发送 Invite 请求:

    SipServletRequest invite = _sipFactory.createRequest(appSession,"INVITE",from,to);
    SipURI requestUri = _sipFactory.createSipURI("user1",toAddressStr);
    if (toPortStr != null)
    requestUri.setPort(Integer.parseInt(toPortStr));
    invite.setRequestURI(requestUri);
    invite.send();
  9. 因为这是 HTTP 请求的结果,所以我们需要将内容返回到用户指示的状态,并包括一个 Terminate 按钮,以便终止呼叫:

    resp.getWriter().print("creating a call to: ["+to.toString()+
    "] from: ["+from.toString()+"]");
    resp.getWriter().print("<form>");
    resp.getWriter().print("<input type=\"hidden\" name=\"appid\" value=\"");
    resp.getWriter().print(appSession.getId());
    resp.getWriter().print("\">");
    resp.getWriter().print("<input type=\"submit\" value=\"terminate\">");
    resp.getWriter().print("</form>");
  10. HTTP Servlet 的下一个主要部分是编写在按下 Terminate 按钮时要执行的终止功能。这个简单的 HTTP 交互可以假设如果请求中不包括请求参数,并且已经使用会话 ID 对SipApplicationSession 进行了初始化,那么呼叫可以存在。因此,我们可以添加一个简单 else-if 语句,来检查以下条件:

    else if((appSessionId!=null) && (!appSessionId.equals(""))){
  11. 要终止呼叫,我们需要将 BYE 发送到 UAS。要执行此任务,请在 SipApplicationSession 中选取第一个 SipSession,并为每个会话创建可以关闭连接的 BYE。将为每个 UAS 创建一个 SipSession,一个用于 HTTP Servlet 完成的 INVITE,另一个用于 SIP Servlet 将执行的 INVITE。这里,我们将遍历每个会话,并将 BYE 发送到每个 UAS:

    for (Iterator iter = appSession.getSessions("SIP"); iter.hasNext();) {
    SipSession session = (SipSession) iter.next();
    session.createRequest("BYE").send();
    }
  12. 最后,需要将终止状态返回给用户:

    resp.setContentType("text/html");
    resp.getWriter().print("Call Launcher Sample Application <BR><hr>");
    resp.getWriter().print("terminated call["+appSessionId+"]");

编写 SIP Servlet

  1. 通过选择 CallLauncher => SIP Deployment Descriptor => SIP Servlets => CallLauncherSIP 访问 SIP 部署描述符 (sip.xml)。图 14 显示了该情况。

    图 14. SIP 部署描述符
    图 14. SIP 部署描述符
    图 14. SIP 部署描述符
  2. 确保将 SIP Servlet 映射到 INVITE 方法。

  3. SIP Servlet 有多个功能(图 15 和 16)。第一个功能是在第一个请求成功发出后,通过将 INVITE 请求发送到 UAS 而连接第二个 UAS。此呼叫流程中的 doBye 将对以下情况进行处理:在按 HTTP 页面上的 Terminate 按钮之前一个用户代理启动了 BYE。

    图 15. doResponse 流程
    图 15. doResponse 流程
    图 15. doResponse 流程
    图 16. doBye 流程
    图 16. doBye 流程

    现在,我们处理将第二个 UAS 连接到该呼叫的流程。由于 HTTP Servlet 启动了 INVITE,所以 SIP Servlet 主要负责接受来自该 INVITE 请求的响应,并发送第二个 INVITE。要创建请求,我们需要访问 SipFactory。我们将获取指向 SipFactory 的引用,这非常类似于对 HTTP Servlet 执行的操作:

     private SipFactory _sipFactory;
    public void init() throws ServletException {
    _sipFactory = (SipFactory) getServletContext().getAttribute(SIP_FACTORY);
    if (_sipFactory == null) {
    throw new ServletException("No SipFactory in context");
    }
    }
  4. 一旦有了 SipFactory,我们就可以开始在 doResponse() 方法中编写 INVITE 逻辑。为了了解我们正处于哪一段流程,我们将每个会话的请求连接在一起。要查看这是否是对 INVITE 的响应,我们需要检查导致此响应的请求:

    SipServletRequest req = resp.getRequest();
  5. 将请求连接在一起可以断定是否发送了一个或两个请求。作为我们呼叫 peer.req 的属性检查第二个请求,保存 peer.req 的代码为:

    SipServletRequest req2 = (SipServletRequest) req.getAttribute("peer.req");
  6. 正如图 15 中的流程图指出的,下一步将检查这是否是对 INVITE 的响应。之后,我们需要立即查看它是否为 HTTP Servlet 发送的第一个 INVITE 或 SIP Servlet 发送的第二个 INVITE 的响应。我们将使用 req2 了解响应之间的差异:

    if (resp.getMethod().equalsIgnoreCase("INVITE")){
    if (req2 == null) {
    if (resp.getStatus() == 200) {
  7. 由于这是一个包含的示例,所以我们将不检查来自 UAS 的响应。通常,我们利用这一点来查看来自第一个 UAS 的响应是否成功。相反地,我们将立即开始设置第二个呼叫线路。在创建 INVITE 请求时,我们将反向执行 To 和 From 地址,因此第一步是从第一个请求获取 To 和 From 地址:

    SipURI to = (SipURI) req.getTo().getURI();
    SipURI from = (SipURI) req.getFrom().getURI();
  8. 接下来,我们将获取 SipApplicationSession,并创建 INVITE 请求。注意,与 HTTP Servlet 中的类似呼叫相比,我们反向执行了 To 和 From 地址:

    SipApplicationSession appSession = resp.getSession().getApplicationSession();
    SipServletRequest invite =
    _sipFactory.createRequest(
    resp.getApplicationSession(),
    "INVITE",
    to,
    from);
  9. 为正确地连接,并将其发送到第二个 UAS,我们需要地址和端口。我们可以将该地址存储在 HTTP Servlet 中的 SipApplicationSession 中。这里,我们将获取以下属性:

    StringfromAddress = (String)appSession.getAttribute("FROM_ADDRESS");
    StringfromPort = (String)appSession.getAttribute("FROM_PORT");
  10. 我们现在已经准备好设置第二个 UAS 的其余请求。创建请求 URI 并设置端口;我们在 INVITE 请求上设置此端口,但不立即发送它。因为 SIP Servlet 是异步的,所以我们需要将发送该请求作为此 Servlet 中执行的最后操作;否则,可能会发生同步问题:

    SipURI requestUri = _sipFactory.createSipURI("user2",fromAddress);
    if (fromPort != null)
    requestUri.setPort(Integer.parseInt(fromPort));
    invite.setRequestURI(requestUri);
  11. 我们已准备好设置带有我们已接收的会话内容描述的请求。通常,这类描述是会话描述协议 (SDP) 格式:

    copyContent(resp, invite);
  12. 创建 copyContent 方法,以便将此会话描述简单地从一个 SIP 消息复制到另一个 SIP 消息:

    void copyContent(SipServletMessage msg1, SipServletMessage msg2) {
    try {
    if (msg1.getContentType() != null) {
    msg2.setContent(msg1.getRawContent(), msg1.getContentType());
    }
    } catch (IOException ex) {
    log("Error: " + ex);
    }
    }
  13. 为了将请求连接在一起,我们在各自的会话中相互设置会话和请求;这是为了方便,但实际上并不需要,因为会话位于同一 SipApplicationSession 中:

    invite.setAttribute("peer.req", req);
    invite.setAttribute("peer.resp", resp);
    SipSession dialog = resp.getSession();
    SipSession dialog2 = invite.getSession();
    dialog.setAttribute("peer", dialog2);
    dialog2.setAttribute("peer", dialog);
  14. 将邀请发送到第二个 UAS:

    invite.send();
  15. 返回到图 15 中的流程图,我们需要对以下情况进行处理:响应来自第二个 UAS INVITE,而不是第一个 UAS INVITE。首先,在询问请求的 if 的范围中添加 else 语句,以查看请求是否通过 peer.req 连接模式进行连接。如果我们接收了来自第二个 UAS 的响应,则需要将 ACK 发送到第一个 UAS。具体来说,流程将类似图 17。ACK 是会话开始前的最后一步。

    图 17. 带有 ACK 的 SIP 流程
    图 17.  带有 ACK 的 SIP 流程
    图 17. 带有 ACK 的 SIP 流程
  16. 对于此特定代码,我们将 ACK 发送到第二个 UAS,然后再发送到第一个 UAS。将 ACK 发送到第一个 UAS 时,将会话描述发送回第一个 UAS 非常重要:

    else {
    SipServletRequest ack = resp.createAck();
    ack.send();
    SipServletResponse peerResp =
    (SipServletResponse) resp.getRequest().getAttribute(
    "peer.resp");
    SipServletRequest ack2 = peerResp.createAck();
    copyContent(resp, ack2);
    ack2.send();
    }
  17. 请看图 15,我们将开始处理传递 INVITE 的流程和 BYE 的响应。具体来说,图 18 显示了由 CallLauncher 处理的 BYE 流程。

    图 18. 用于此方案的完整消息流程
    图 18.  用于此方案的完整消息流程
    图 18. 用于此方案的完整消息流程

    对于 BYE 响应,我们需要接受 BYE 响应,使用同一状态代码创建响应,并将其发送到对等 UAS:

    else if (resp.getMethod().equalsIgnoreCase("BYE")){
    int sc = resp.getStatus();
    if (req2 != null) {
         SipServletResponse resp2 = req2.createResponse(sc);
         resp2.send();
    }
    else
    log("CallLauncherSIP: doBye: error: peer req = null");
    }
  18. 需要实现的最后一个 SIP Servlet 是 doBye 方法。因为在不点击网页上 Terminate 按钮的情况下,两个 UAS 都可以启动 BYE,所以我们需要相应转发 BYE。在此方案中,BYE 流程类似于图 19 所示的流程。

    图 19. UAS 启动 BYE(而不是 HTTP 启动 BYE)的流程
    图 19.  UAS 启动 BYE(而不是 HTTP 启动 BYE)的流程
    图 19. UAS 启动 BYE(而不是 HTTP 启动 BYE)的流程

    当从 UAS 接收 BYE 时,我们需要先获取对等会话,以便在其上发送 BYE。我们先前在相应请求的属性中存储了该对等会话。这里,我们可以获取该对等会话:

    protected void doBye(SipServletRequest bye1) throws ServletException, IOException {
    SipSession dialog1 = bye1.getSession();
    SipSession dialog2 = (SipSession) dialog1.getAttribute("peer");
    if (dialog2 == null) {
    throw new ServletException("no peer SipSession; cannot forward BYE");
    }
  19. 获取对等会话后,我们就可以在对等会话上创建 SIP BYE 请求,以便将其发送到对等 UAS。我们需要复制 BYE 中包括的任何会话描述,以终止会话:

    SipServletRequest bye2 = dialog2.createRequest("BYE");
    bye2.setAttribute("peer.req", bye1);
    copyContent(bye1, bye2);
    bye2.send();

这是我们需要对这个简单 SIP Servlet 执行的所有操作。现在我们已经准备好测试代码。

E. 在应用服务器上部署应用程序

  1. 测试应用程序的方法共有两种。这里,要获取您可以在应用服务器上部署的包,则需要从 Application Server Toolkit 导出 Servlet Archive (SAR) 文件。选择 File => Export

  2. Export 对话框(图 20)中选择 SAR 文件,并单击 Next

    图 20. 导出 SIP 项目
    图 20. 导出 SIP 项目
    图 20. 导出 SIP 项目
  3. 从下一个对话框(图 21)中选择 CallLauncher 项目作为要导出的项目,然后选择您希望将 SAR 导出到的目录。在这之后,单击 Finish

    图 21. 导出 SIP 项目
    图 21. 导出 SIP 项目
    图 21. 导出 SIP 项目
  4. 导出应用程序后,转到应用服务器管理控制台(图 22)。在左栏中,转到 Applications => Install New Application

  5. 从 Local file system 选择适当的 SAR,将 /CallLauncher 指定为此应用程序的上下文根,然后单击 Next。对于这个简单的应用程序,使用所有缺省值就足够了。

    图 22. 管理控制台
    图 22. 管理控制台
    图 22. 管理控制台
  6. 现在部署好了应用程序。您可以使用左栏中的 Applications => Enterprise Applications 选项,并像启动和停止任何其他 WebSphere 应用程序一样,启动和停止 CallLauncher 应用程序。

F. 运行应用程序

部署了应用程序后,您需要启动两个 UAS 应用程序。出于测试目的,我们使用在免费开放源码工具 SIPp 中提供的 UAS。SIPp 配置的详细信息显示在附录 A 中。

  1. 假设在应用程序的安装过程中指定了上下文根,您可以通过在浏览器中输入 URL:http://<server>:<port>/CallLauncher/CallLauncherHTTP,在 Web 上访问此应用程序,其中 <server> 是主机名,<port> 是 Web 容器的端口(对于 WebSphere Application Server,缺省值为 9080),其余为上下文根和 Servlet 名称。应显示带有表单的简单 HTTP 页,类似于图 23。

    图 23. 示例应用程序
    图 23. 示例应用程序
    图 23. 示例应用程序
  2. 在初始网页上,输入两个 UAS 应用程序的地址和端口,然后按 create call。将显示一个页面,指示正在创建呼叫(图 24)。

    图 24. 示例应用程序结果页面
    图 24. 示例应用程序结果页面
    图 24. 示例应用程序结果页面
  3. 在这个结果页面,可以按 terminate 按钮终止呼叫。

结束语

随着即时消息、Internet 协议 (IP) 电话技术和 IP 视频等技术越来越广泛的使用,将掀起一场创建应用程序的新浪潮,这些应用程序将通过 IP 组合多种技术。使用 WebSphere Application Server 的工具创建这些聚合应用程序非常容易。WebSphere Application Server 为在一个应用程序中无缝利用会话发起协议 (SIP) 和超文本传输协议 (HTTP) 提供了简单的编程模型。

本系列的其他文章

附录:SIPp UAS 配置

SIPp 是 SIP 的免费开放源码工具。它是可以测试 CallLauncher 应用程序的示例 UAS。我们对此示例应用程序使用的配置如下。您可以访问 SIPp 文档,并直接从 SIPp 网页下载该工具

<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE scenario SYSTEM "sipp.dtd">
<!-- This program is free software; you can redistribute it and/or      -->
<!-- modify it under the terms of the GNU General Public License as     -->
<!-- published by the Free Software Foundation; either version 2 of the -->
<!-- License, or (at your option) any later version.                    -->
<!--                                                                    -->
<!-- This program is distributed in the hope that it will be useful,    -->
<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of     -->
<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the      -->
<!-- GNU General Public License for more details.                       -->
<!--                                                                    -->
<!-- You should have received a copy of the GNU General Public License  -->
<!-- along with this program; if not, write to the                      -->
<!-- Free Software Foundation, Inc.,                                    -->
<!-- 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA             -->
<!--                                                                    -->
<!--                 Sipp default 'uas' scenario.                       -->
<!--                                                                    -->
<scenario name="Basic UAS responder">
  <!-- By adding rrs="true" (Record Route Sets), the route sets         -->
  <!-- are saved and used for following messages sent. Useful to test   -->
  <!-- against stateful SIP proxies/B2BUAs.                             -->
  <recv request="INVITE" crlf="true">
  </recv>
  <!-- The '[last_*]' keyword is replaced automatically by the          -->
  <!-- specified header if it was present in the last message received  -->
  <!-- (except if it was a retransmission). If the header was not       -->
  <!-- present or if no message has been received, the '[last_*]'       -->
  <!-- keyword is discarded, and all bytes until the end of the line    -->
  <!-- are also discarded.                                              -->
  <!--                                                                  -->
  <!-- If the specified header was present several times in the         -->
  <!-- message, all occurences are concatenated (CRLF seperated)        -->
  <!-- to be used in place of the '[last_*]' keyword.                   -->
  <!--send>
    <![CDATA[
      SIP/2.0 180 Ringing
      [last_Via:]
      [last_From:]
      [last_To:];tag=[call_number]
      [last_Call-ID:]
      [last_CSeq:]
      Contact: <sip:[local_ip]:[local_port];transport=[transport]>
      Content-Length: 0
    ]]>
  </send-->
<pause milliseconds = "500"/>
  <send>
    <![CDATA[
      SIP/2.0 200 OK
      [last_Via:]
      [last_From:]
      [last_To:];tag=[call_number]
      [last_Call-ID:]
      [last_CSeq:]
      Contact: <sip:[local_ip]:[local_port];transport=[transport]>
      Content-Type: application/sdp
      Content-Length: [len]
      v=0
      o=user1 53655765 2353687637 IN IP4 127.0.0.1
      s=-
      t=0 0
      c=IN IP4 [media_ip]
      m=audio [media_port] RTP/AVP 0
      a=rtpmap:0 PCMU/8000
    ]]>
  </send>
  <recv request="ACK"
        optional="true"
        rtd="true"
        crlf="true">
  </recv>
  <recv request="BYE">
  </recv>
  <send>
    <![CDATA[
      SIP/2.0 200 OK
      [last_Via:]
      [last_From:]
      [last_To:];tag=[call_number]
      [last_Call-ID:]
      [last_CSeq:]
      Contact: <sip:[local_ip]:[local_port];transport=[transport]>
      Content-Length: 0
    ]]>
  </send>
  <!-- Keep the call open for a while in case the 200 is lost to be     -->
  <!-- able to retransmit it if we receive the BYE again.               -->
  <!-- pause milliseconds="1000"/ -->
  <!-- definition of the response time repartition table (unit is ms)   -->
  <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
  <!-- definition of the call length repartition table (unit is ms)     -->
  <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
</scenario>

下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=WebSphere
ArticleID=174687
ArticleTitle=IBM WebSphere 开发者技术期刊: WebSphere Application Server V6.1 中的会话发起协议——第 2 部分
publish-date=11152006