The SIP Servlet Programming Model



 作者:Nasir Khan JCJ思考者日记网-束洋洋个人博客

摘要

本文介绍了SIP Servlet编程模型,这是一种以SIP Servlet规范为依据,用于编写SIP应用程序的模型。 文章涵盖了SIP servlet容器和SIP servlet API的细节。 通过阅读本文,您可以对SIP servlet容器的设计动机有一定的理解和认识。 同时了解各种不同的API结构,以及如何利用这些结构创建强大的SIP应用程序。JCJ思考者日记网-束洋洋个人博客

简介

本文详述了SIP servlet编程模型和SIP servlet API。 想了解关于SIP servlet的入门知识,请阅读Emmanuel Proulx撰写的发表在Dev2Dev上的文章(含两部分):SIP简介,第1部分:SIP初探 (中文版,Dev2Dev,2006年4月)和SIP简介,第2部分:SIP SERVLET(中文版,Dev2Dev,2006年4月)。 要阅读本文,需对SIP协议有基本的理解。 熟悉HTTP servlet模型当然更好,但并非阅读本文的前提条件。 除了SIP协议本身之外,本文还解释了独立于其他标准或规范的概念。 文中涵盖了SIP servlet编程模型,API(SSAPI),以及提供该API的容器。 我们首先将介绍SIP servlet模型的基本知识以及该模型最适用的对象。 然后探究API的重要工件,容器如何使这些工件有效,以及容器该完成哪些任务。再通过实例演示SIP servlet应用程序能做些什么。注意,本文使用的术语SSAPI和SIP Servlet规范是可以互换的。JCJ思考者日记网-束洋洋个人博客

SIP Servlet规范

首先回顾一下为什么SIP Servlet规范对于SIP Java开发人员来说是一个极具吸引力的主张,以及SSAPI的价值主张是什么。 然后将介绍JSR 116 规范对SSAPI规定的一些目标。JCJ思考者日记网-束洋洋个人博客

SIP信令

SIP servlet规范允许应用程序执行一套相当完整的SIP信令。 SIP协议定义了不同类型的高级SIP角色,如User Agents(明确地说,UA即UA Client、UA Server和Back to Back UA B2BUA<尚有争议>)、Proxy、Registrar、Redirect Server等。 SSAPI是一种强大的API,它允许把上述这些角色编写成SIP servlet应用程序。JCJ思考者日记网-束洋洋个人博客

SIP是可扩展的协议,通过增加新特性可以扩展其基本协议,这是SIP的优势之一。 事实上,诸多此类RFC都为基本SIP RFC(3261)定义了扩展。 这种扩展机制在很大程度上是通过在容器功能和应用程序之间把SIP处理分开来实现的。 容器执行大多数基本协议处理,而一些高级任务则由应用程序执行。 这种巧妙的任务分割使SSAIP极为强大且具有灵活性。JCJ思考者日记网-束洋洋个人博客

简单性

容器处理一些"非本质"复杂性,如网络、事务、对话管理、via头字段和路由处理。 不同的应用程序对这些处理任务的需求也不同。而让每个应用程序分别都包含这些处理功能显然是多余的。 容器管理所有这些任务,而应用程序则提供一些高级的功能。JCJ思考者日记网-束洋洋个人博客

部署在SIP servlet容器上的代理应用程序就是一个例子。 在代理过程中,该应用程序会在请求中添加自已的via头字段。 Via头字段是基本SIP协议要求添加的,它显示了请求所经过的网络实体。 via头字段也包含了充当事务标识符的分支标识符。 容器维护事务和与事务相关的状态机,因此在请求中插入via头字段的工作也由容器完成。JCJ思考者日记网-束洋洋个人博客

下游的SIP实体接收到代理应用程序转发过来的请求后,按原路发回一个响应。 容器接收到这个响应之后,移除它在原始请求中插入的via头字段并处理响应。 注意,应用程序完全不用担心对via头字段的管理,这减轻了应用程序开发人员的负担。 一个容器的某些实例很注意一些普通但基本的协议细节,本文将介绍其中的一部分。JCJ思考者日记网-束洋洋个人博客

融合应用程序

Sip Servlet规范允许应用程序的构建跨越不同的协议/规范。 这是SSAPI最杰出的价值主张之一。 Sip Servlet规范还严密地符合Java EE规范,希望托管SIP servlet应用程序的容器也能使其应用程序具有Java EE的属性——其中最值得一提的就是HTTP Servlet容器。JCJ思考者日记网-束洋洋个人博客

如图1所示,SSAPI规范以servlet规范为基础,并且平行于HTTP Servlet规范。JCJ思考者日记网-束洋洋个人博客

The SIP Servlet Specification, family tree 
图1。 SIP Servlet 规范系谱图JCJ思考者日记网-束洋洋个人博客

融合应用程序能够同时具备SIP和HTTP的功能,存在大量这样的实例。 然而,事实上,JSR116并没有足够充分地考虑到当前这种形式的融合应用程序。JCJ思考者日记网-束洋洋个人博客

应用合成

Sip servlet规范允许几个应用程序针对相同的请求或响应同时执行各自的任务,因此应用程序能够彼此独立地执行。这是SSAPI另一个非常强大的属性。它保证了应用程序开发人员可以提供独立于其他应用程序的附加特性。然后再把这些应用程序结合起来为一次呼叫提供服务。 这里的合成指的是容器的合成。 SSAPI对于这一点的规定并不充分,为不同的实现留下了空间来实现这个功能。 希望这些不足在规范的下一修订版(JSR 289)中会被解决。JCJ思考者日记网-束洋洋个人博客

电信级(Carrier grade)

应用程序数据可以存储在容器管理的会话对象中,这种对象可用于状态复制和故障转移(failover)。 几乎所有能够执行一些有用功能的应用程序都具有一些不同的请求和响应之间的状态。 有的状态是SIP协议本身需要的。 事务状态机处理其服务器端和客户端事务状态,还有对话状态机。 因此,容器有消息上下文的概念(用于封装SIP级状态)以及会话概念(SSAPI结构)。 然而,应用程序能够在容器维护的Session对象中保存自身状态。 电信级(carrier grade)容器复制这个状态,以使呼叫能对一个容器实例容错。JCJ思考者日记网-束洋洋个人博客

如您所见,该规范清楚地定义了SIP servlet容器和SIP servlet应用程序的作用域和行为。 下一部分将探究容器功能。JCJ思考者日记网-束洋洋个人博客

SIP Servlet容器

我们已经知道,SSAPI规范不仅详细说明了API,并且还描述了容器功能。 容器是托管(含有)SIP应用程序的服务器。 容器执行许多SIP功能(各种RFC详细说明了这些SIP功能),因此减轻了应用程序的负担。然而,这样也把应用程序公开(通过SSAPI)给了SIP级消息,应用程序能对后者执行各种操作。 因此,通过编写应用程序并将其部署在容器上,可以提供各种电信和多媒体服务。JCJ思考者日记网-束洋洋个人博客

SSAPI容器是什么?

图2显示了SIP servlet容器的逻辑层。 底部的五层被称作SIP栈,RFC3261详细说明了其功能。作为基于事务的协议,SIP拥有一个定义明确的事务层。 SIP请求后面总是会紧接着一个或多个临时响应和一个最终响应(不包括ACK,ACK没有响应)。事务机用于追踪临时和最终响应。JCJ思考者日记网-束洋洋个人博客

The SIP Servlet Container Blocks
图2。 SIP servlet容器构成JCJ思考者日记网-束洋洋个人博客

SIP servlet规范定义了SIP对话的概念。 对话是在两个SIP端点之间的端到端(point-to-point)会话,由对话标识符唯一识别。 并不是所有的SIP请求都创建对话。 创建对话的请求拥有定义明确的建立和撤除对话的机制(即:INVITE、SUBSCRIBE/NOTIFY和REFER请求)。JCJ思考者日记网-束洋洋个人博客

图2中的SIP栈并非严格符合RFC3261——其差别是:事务层上面还应有一个事务用户(Transaction User,TU)层,此外,对话管理层在3261中并不是一个明确的层。图中所示和RFC中描述的层是逻辑层,并没有把它们转化成物理实现。JCJ思考者日记网-束洋洋个人博客

因为对话大致对应于、SipSession对象,SipSession对象也和SipApplicationSession(稍后讨论)有关,所以对话层是SIP servlet容器中一个高可见度的要素。 上图中,事务用户(RFC3261中的TU)层实际上在对话管理层和大容器块之间被分开了。 容器的主要目的是托管根据容器所实现的SSAPI编写的SIP servlet应用程序。 容器向应用程序公开对象(例如SipServletRequest、SipServletResponse、各种类型的会话)和工具(例如如计时器和日志记录程序)。因此可以很便捷地开发功能强大的应用程序。JCJ思考者日记网-束洋洋个人博客

协议

SIP是基于文本的协议,所以易于理解(RFC3261非常精细地定义了SIP协议和基本协议),但是编写SIP应用程序并不是一项容易的工作。 设计SSAPI初衷是使应用程序开发人员能轻松地建立SIP应用程序。 一方面,SSAPI允许访问SIP请求中的所有SIP头字段;另一方面,它不需要应用程序为纠正协议行为理解或修改SIP头字段。 实际上,对于应用程序来说,一些头字段是严格禁止访问的。 SSAPI定义了所谓的系统头字段(System Header),由容器管理――即:From、To、Call-ID、CSeq、Via、Route(不包括pushRoute)和Contact头字段。 应用程序可以对Record-Route头字段添加属性。JCJ思考者日记网-束洋洋个人博客

Contact头字段是消息中的系统头字段而不是REGISTER请求或响应中的头字段,也不是3xx 或485响应中的头字段。 此外,对于执行可靠临时响应扩展的容器,可以考虑使用Rack和RSeq系统头字段。 简而言之,系统头字段管理原本需要很多SIP级活动,而容器帮助应用程序完成了很多这方面的处理。JCJ思考者日记网-束洋洋个人博客

 JCJ思考者日记网-束洋洋个人博客

From/To/Call-ID/CSeq头字段共同标识一个SIP对话。 我们知道,一些SIP方法(如INVITE和SUBSCRIBE)用于建立一个对话。 SSAPI容器追踪对话状态以及和对话有关的数据,因此为应用程序减去了巨大的负担。 SSAPI容器被指定用于管理Record-Route、Contact和Via头字段,是因为网络监听点、故障管理、多重自引导支持和传送开关都由容器执行。 依靠修改Request-URI和/或添加(pushRoute)Route头字段,应用程序仍能参与容器发出请求的路由决策。由于使用网络级功能(如路由)的应用程序不需要管理网络级资源,从而简化了SSAPI容器上的编程。JCJ思考者日记网-束洋洋个人博客

API

SSAPI基于servlet规范,因此大量沿用了Java EE标准。 这种沿用的最大优势之一就是对容器特性(如安全性、映射和环境资源)的声明式使用。 但对于API自身来说,其最重要的优势是在直观结构之后抽象了大量的复杂SIP任务。JCJ思考者日记网-束洋洋个人博客

Proxy接口就是很好的例子,它表示SIP中的代理功能。 RFC3261提供了对代理行为的详细解释。代理可以是有状态或无状态的;代理在接收到3xx类响应后能自动向响应中的Contact地址(可能是一个或多个)发送请求;代理能通过记录路由节点(Record-Route)确保后续请求按原路径发送;代理可以充当代理多重目的地的分流代理;代理可以并发执行或顺序执行。 所有这些行为都是Proxy对象上的简单属性。 容器管理代理服务器处理所有的低级细节,如查明目标集(基于Resquest-URI或Route头字段)、申请RFC规则(当上游或下游存在一个严格的路由器时)、创建多重客户端事务、关联响应以及选择最佳响应。JCJ思考者日记网-束洋洋个人博客

下一节将结合实例讨论SSAPI的一些其他重要接口和结构。JCJ思考者日记网-束洋洋个人博客

掌握API

本节将讨论在SSAPI中详细说明的各种接口。 对应用程序开发人员来说,SSAPI是一个相当简单的API。 SSAPI定义了访问SIP请求和响应消息的接口, SipServletRequest和SipServletResponse接口用于完成此目的。 SIP也利用了SIP URI和SIP地址;通过简易类(URI, SipURI, TelURL, Address)访问SIP头字段来实现这些接口。 由于SIP对话是会话的一种形式,因此还存在一个处理会话的对象类。 这个会话的概念符合API中的SipSession和SipApplicationSession对象。 本文下节将讨论SipServlet接口,后者是从容器到应用程序的入口点。JCJ思考者日记网-束洋洋个人博客

SipServlet对象

SipServlet类在servlet基本包中扩展GenericServlet类,服务方法向doRequest() 或doResponse()分派SIP消息,doRequest()或doResponse()又为请求(例如doInvite()和doSubscribe())调用一个doXXX()方法或为响应(例如doSuccessResponse()和doErrorResponse())调用一个doXXX()方法。JCJ思考者日记网-束洋洋个人博客

部署描述符中定义了一个servlet映射元素,利用该部署描述符,开发人员能够在调用servlet之前定义好规则。 JSR116中明确定义了映射规则的语法。下例中的映射元素确保只有当请求是INVITE并且Request-URI的主机部分包含字符串bea.com时才会调用servlet定义规则。JCJ思考者日记网-束洋洋个人博客

折叠复制内容到剪贴板
  1. <pattern>  
  2.   <and>  
  3.     <equal>  
  4.       <var>request.method</var>  
  5.       <value>INVITE</value>  
  6.    </equal>  
  7.     <contains ignore-case="true">  
  8.       <var>request.from.uri.host</var>  
  9.       <value>bea.com</value>  
  10.     </contains>  
  11.   </and>  
  12. </pattern>  

通常来说,并发的请求只能访问一个SipServlet对象,因此这显然不是特定于呼叫/会话的数据结构。 通常要使用doXXX()方法在呼叫中控制事务逻辑。 仔细考虑如下代码段:JCJ思考者日记网-束洋洋个人博客

折叠复制内容到剪贴板
  1. package test;  
  2.   
  3. import javax.servlet.sip.SipServlet;  
  4. import javax.servlet.sip.SipServletRequest;  
  5. import java.io.IOException;  
  6.   
  7. public class SimpleUasServlet extends SipServlet {  
  8.    protected void doInvite(SipServletRequest req)   
  9.       throws IOException {  
  10.      req.createResponse(180).send();  
  11.      req.createResponse(200).send();  
  12.   }  
  13.   protected void doBye(SipServletRequest req) throws IOException {  
  14.     req.createResponse(200).send();  
  15.     req.getApplicationSession().invalidate();  
  16.   }  
  17.  }  

这是一个简单的UAS servlet,在接收到一个传入INVITE请求时将被调用(由类似于以上定义的规则触发)。 容器通过调用doInvite()方法来调用应用程序。 应用程序选择发送一个180响应(第8行),之后再发送一个200响应(第9行)。注意到,应用程序对UAC发送过来的ACK消息并没有反应。 此时,容器在接收到ACK消息后即忽略它。 (若是有状态代理,应用程序会对其执行代理功能) 应用程序只做自己必须做的事。JCJ思考者日记网-束洋洋个人博客

SIP工厂

从名字可以看出,这个类用于创建各种SSAPI对象,如 Request、SipApplicationSession,和Address对象。 担当UA的应用程序使用这个类创建新请求,通过工厂创建的请求拥有一个新Call-ID(除了B2BUA特定的方法,在此方法中应用程序可以在上游段选择重用Call-ID),并且To头字段中不含有标签。 通过ServletContext的javax.servlet.sip.SipFactory属性能够检索工厂对象。JCJ思考者日记网-束洋洋个人博客

SIP消息

存在两个SIP消息类: SipSevletRequest和SipServletResponse,分别代表SIP请求(例如INVITE, ACK, INFO)和SIP响应(例如1xx, 2xx,)。 这些消息通过SipServlet类中定义的各种doXXX()方法传送到应用程序。 SIP是一种异步协议,因此当doRequest()被调用时,应用程序不一定要响应请求。 由于对原始请求对象有访问权,应用程序可以稍后再响应请求。JCJ思考者日记网-束洋洋个人博客

SipServletRequest和SipServletResponse对象派生自基本SipServletMessag对象,后者提供了一些通用附件/增件方法,例如getHeader(), getContent()和setContent()。 SipServletRequest为请求处理定义了一些有用的方法。 SipServletRequest.createResponse()方法创建一个SipServletResponse类的实例,代表对创建它的请求的响应。JCJ思考者日记网-束洋洋个人博客

类似地,SipServletRequest.createCancel()对先前发送的请求创建一个CANCEL请求。 请注意,当UAC决定不再继续进行呼叫并且没有接收到对原始请求的响应时,将发送CANCEL请求。接收到一个200响应或未接收到一个100响应时就发送CANCEL请求是不正确的。不过幸运的是,SSAPI将会做出补救。UAC应用程序能创建和发送CANCEL请求。 容器将确保只有当接收到1xx类响应并且没有接收到任何>200的响应时才发送CANCEL请求。JCJ思考者日记网-束洋洋个人博客

SipServletRequest.getProxy()用于获得相关Proxy对象,使应用程序执行各种代理操作。 SipServletRequest.pushRoute(SipURI)方法允许UAC或代理通过SipURI标识的服务器路由请求。 这个方法的作用是在Route头字段列表的顶部为请求添加一个Route头字段。JCJ思考者日记网-束洋洋个人博客

另一个有用的方法是SipServletRequest.isInitial()。 应用程序对初始和后续请求的处理不一样,因此理解这些概念很重要。 例如,当应用程序接收到一个Re-INVITE请求时,也会把它传送给servlet的doInvite()方法,但isInitial()将会返回false。JCJ思考者日记网-束洋洋个人博客

初始请求通常是对话以外的请求,容器没有关于它的任何信息。 当容器接收到一个初始请求时,将通过其创建的机制确定使用哪个应用程序进行调用。 这可能会涉及到查找Servlet映射规则。 我们知道,一些请求用于创建对话——因此在创建对话之后接收到的任何请求都属于后续请求。JCJ思考者日记网-束洋洋个人博客

与SIP中的对话结构紧密相关的是SSAPI中的SipSession对象,这点我们将在下节讨论。 createAck()是SipServletResponse类中的一个具有特定用途的方法,它为接收到的2xx响应(INVITE事务产生)创建ACK请求。容器本身将为INVITE事务中的非2xx响应创建ACK请求。JCJ思考者日记网-束洋洋个人博客

SipSession

SipSession大致可对应于一个SIP对话。对于UA,它维护在RFC中指定的对话状态,以在对话中正确创建一个后续请求。 如果应用程序担当UA(UAC或B2BUA)并且在处理完初始请求之后需要在对话中发送一个后续请求(如Re-INVITE或BYE),则应使用SipSession.createRequest()方法而不是某个SipFactory方法,否则将会把请求创建在对话之外。JCJ思考者日记网-束洋洋个人博客

应用程序在SipSession中存放了容器维护的状态,除此之外还存放了需要维护的特定于会话的状态。 应用程序能够在SipSession对象上设定/取消属性;并能根据不同的调用访问这些属性。 SipSession也提供了SipSession.setHandler(String nameOfAServlet),用于指派应用程序中特定的servlet为SipSession获取后续请求。JCJ思考者日记网-束洋洋个人博客

SipApplicationSession

逻辑上,SipApplicationSession是应用程序的一个实例。 应用程序可能拥有一个或多个与之有关的协议会话。 在JSR 116中,这些协议会话可以是SipSession或HttpSession。应用程序存储在应用程序级有效的数据作为SipApplicationSession的属性。还需特别注意,SipApplicationSession或相关SipSession上设定的任何属性只针对特定应用程序可见。 SSAPI定义的机制允许同一呼叫调用多个应用程序,该属性称为应用合成。 SipApplicationSession提供的getSessions()方法返回与这个应用会话相关的协议会话。 图3显示了SSAPI中不同会话的容器分层结构。JCJ思考者日记网-束洋洋个人博客

SIP Servlet 编程模型图-3JCJ思考者日记网-束洋洋个人博客

图3。 SSAPI中的会话。JCJ思考者日记网-束洋洋个人博客

encodeUri(URI)是SipServletApplication接口中最有趣的方法之一。 该方法用于把SipApplication标识符编码到URI的参数中去。 如果容器接收到含有该URI编码的新请求,即使是不同的呼叫,也会将SipApplicationSession与该请求关联。 这一看似索然无味的方法具备将两个迥然相异的呼叫连接起来的能力,您可以采用很多别出心裁的方式来使用它。 SipApplicationSession也与应用会话计时器相关,这方面的话题在下一节讨论。JCJ思考者日记网-束洋洋个人博客

Proxy接口

SIP RFC 3261详细说明了代理行为。 简要地说,代理用于向目标转发请求。 然而,代理有不同的性质。 根据是否需要维护SIP事务状态,代理可以是有状态或无状态的。 代理可以决定关键路由节点(record-route),这样代理就能为对话接收后续请求。否则,在决定初始路由后,代理将放弃正在进行的对话。 代理能并行(同时)地或顺序(一个接着一个)地将请求发向多个目的地。 所有代理行为的复杂细节都很好地隐藏在了一个易用的proxy接口之后。 因此路由代理非常简单,就像如下代码段一样:JCJ思考者日记网-束洋洋个人博客

折叠复制内容到剪贴板
  1. protected void doInvite(SipServletRequest req)  
  2.    throws ServletException, IOException {  
  3.       Proxy p = req.getProxy();  
  4.       SipURI uri = (SipURI) req.getRequestURI().clone();  
  5.       uri.setPort(5081);  
  6.         p.proxyTo(uri);  
  7. }  

本例中,servlet代理用户将请求发送到相同URI,但是所用端口不同。JCJ思考者日记网-束洋洋个人博客

应用程序计时器

应用程序可以使用SSAPI提供的计时器服务。 TimerService接口可从ServletContext检索,并作为属性使用。 TimerService定义了一个creatTimer(SipApplicationSession appSession, long delay, boolean isPersistent, java.io.Serializable)方法,用于启动应用级计时器。 可以看到,SipApplicationSession与计时器相关联。 当计时器触发应用程序时,定义好的TimerListener被调用,ServletTimer对象放弃计时。通过这些便可以取回SipApplicationSession。 这为计时器终止提供了正确的上下文。JCJ思考者日记网-束洋洋个人博客

示例

接下来为大家演示一些示例代码,展示如何应用前面学习过的结构。 首先是一个融合应用程序。 如果您还记得,融合应用程序涉及到多个协议(本例使用SIP和HTTP)。JCJ思考者日记网-束洋洋个人博客

折叠复制内容到剪贴板
  1. <html>  
  2. <body>  
  3.  <%  
  4.    if (request.getMethod().equals("POST")) {  
  5.     javax.servlet.sip.SipFactory factory =   
  6.       (javax.servlet.sip.SipFactory)  
  7.    application.getAttribute(javax.servlet.sip.SipServlet.SIP_FACTORY);  
  8.         
  9.     javax.servlet.sip.SipApplicationSession appSession =  
  10.        factory.createApplicationSession();  
  11.      
  12.     javax.servlet.sip.Address to =   
  13.       factory.createAddress("sip:localhost:5080");  
  14.    javax.servlet.sip.Address from =   
  15.       factory.createAddress("sip:localhost:5060");  
  16.      
  17.    javax.servlet.sip.SipServletRequest invite =  
  18.        factory.createRequest(appSession, "INVITE", from, to);  
  19.      
  20.    javax.servlet.sip.SipSession sess = invite.getSession(true);  
  21.    sess.setHandler("sipClickToDial");  
  22.    //invite.setContent(content, contentType);  
  23.    invite.send();  
  24.    }  
  25.  %>  
  26.      
  27. <p>  
  28. Message sent ...  
  29. </body>  
  30. </html>  

这是一个JSP页面的例子,可以通过HTTP URL访问它。 本例中的JSP代码需要像SIP servlet一样调用sipClickToDial()。 这个HTTP servlet在一个较高的级别上通过工厂创建了一个SIP请求,并将其发送至一个SIP URI。 这是一个点击拨号(click-to-dial)应用程序的构架,在Web页面上单击鼠标即可发起依次SIP呼叫。JCJ思考者日记网-束洋洋个人博客

当发送到这个HTTP servlet的是HTTP POST请求时,将会调用SipFactory(第5-6行)。接着创建一个应用会话(第7-8行)。应用程序所有未来的SIP和HTTP的交互都将这个应用会话为中心件。 其目的是发送一个SIP请求(第13-14 行),但在此之前需创建From和To头字段用于形成该INVITE请求。 16行为SipSession(与刚创建的INVITE请求相关)分配了一个处理器,这样可以确保接收到这个INVITE请求的UAS发送的响应会被分派到SIP servlet,以便进行处理。JCJ思考者日记网-束洋洋个人博客

然而,至本文撰写之日止,一旦控制从创建SipApplicationSession 和相关SipSession的HTTP servlet返回,就没有任何机制能从HTTP的领域内访问SipApplicationSession。 简单地说,在相同HTTP会话的上下文中,任何后续HTTP请求都无权访问SipApplicationSesstion。JCJ思考者日记网-束洋洋个人博客

我将用另一个简单的例子说明。 在这个例子中,应用程序接收到一个SUBSCRIBE请求,并发出一个NOTIFY请求。 随后应用程序等待NOTIFY接收者的响应。如果在三秒钟之内没有接收到成功响应(2xx级响应),应用程序将采取一些行动(如更新数据库,记录尝试丢失)。JCJ思考者日记网-束洋洋个人博客

折叠复制内容到剪贴板
  1. public class Sample_TimerServlet extends SipServlet  
  2.   implements TimerListener {  
  3.     
  4.   private TimerService timerService;  
  5.   private static String TIMER_ID = "NOTIFY_TIMEOUT_TIMER";  
  6.     
  7.   public void init() throws ServletException {  
  8.     try {  
  9.       timerService =   
  10.         (TimerService)getServletContext().getAttribute  
  11.           ("javax.servlet.sip.TimerService");  
  12.     }     
  13.     catch(Exception e) {  
  14.      log ("Exception initializing the servlet "+ e);  
  15.     }  
  16.   }  
  17.     
  18.   protected void doSubscribe(SipServletRequest req)  
  19.        throws ServletException, IOException {  
  20.     req.createResponse(200).send();  
  21.     req.getSession().createRequest("NOTIFY").send();  
  22.     ServletTimer notifyTimeoutTimer =   
  23.       timerService.createTimer(req.getApplicationSession(), 3000,   
  24.                falsenull);  
  25.     req.getApplicationSession().setAttribute(TIMER_ID,   
  26.              notifyTimeoutTimer);  
  27.   }  
  28.     
  29.   protected void doSuccessResponse(SipServletResponse res)   
  30.        throws javax.servlet.ServletException, java.io.IOException {  
  31.     if (res.getMethod().equals("NOTIFY")) {  
  32.       ServletTimer notifyTimeoutTimer =        
  33.         (ServletTimer)(res.getApplicationSession().  
  34.              getAttribute(TIMER_ID));  
  35.       if (notifyTimeoutTimer != null) {  
  36.         notifyTimeoutTimer.cancel();  
  37.         res.getApplicationSession().removeAttribute(TIMER_ID);  
  38.       }  
  39.     }  
  40.   }  
  41.     
  42.     
  43.   public void timeout(ServletTimer timer) {  
  44.     // This indicates that the timer has fired because a 200 to  
  45.     // NOTIFY was not received. Here you can take any timeout   
  46.     // action.   
  47.     // .........  
  48.     timer.getApplicationSession().removeAttribute  
  49.          ("NOTIFY_TIMEOUT_TIMER");  
  50.   }     
  51. }  

上例中,servlet自已实现了TimerListener,所以超时将被通报给servlet。 首先应从ServletContext获得TimerService(第7至第9行),然后把获取SUBSCRIBE请求的计时器设定为3秒钟(第20行)。计时器可随时设定。 请注意,可以为计时器附加一个对象,此后便可将计时器作为标识符或可调用消息使用。 本例把定时器标识为字面值。 发送完NOTIFY之后,应用程序创建定时器,并在SipApplicationSession中保存其引用以供以后使用(22行)。此时如果接收到响应NOTIFY的200响应,便可调用定时器引用并取消定时器(第25行);如果未在3秒钟之内接收到200响应,定时器就会触发,容器将调用timeout()回调。 (第36行)。JCJ思考者日记网-束洋洋个人博客

结束语

SIP是一种构建VoIP网络的新兴协议。 SIP已经得到3GPP (3rd Generation Partnership Project)的最终认定,成为IMS架构的基本协议。SIP servlet编程模型技术越来越多地应用于应用服务器,原因主要在于其易用性和强大的力量。 此外,SIP servlet规范更加贴近Java EE标准,也是创建融合应用程序的首选。 希望本文能够在SIP servlet编程模型这个方面为您打下坚实的基础,并帮助您开始动手使用这些技术。JCJ思考者日记网-束洋洋个人博客

参考资料

  • JSR 116 - SIP Servlet规范 1.0
  • JSR 289 - SIP Servlet规范 1.1 (WIP)
  • JSR 53 - SIP Servlet规范 2.3
  • JSR 154 - SIP Servlet规范 2.6 (WIP)
  • RFC 3261 - 基本SIP协议规范

Nasir Khan 是BEA Weblogic SIP Server产品的架构工程师。他在设计和构建可升级企业Java应用程序方面有着十二年的经验。JCJ思考者日记网-束洋洋个人博客

 

(转载本站文章请注明作者和出处 思考者日记网|束洋洋个人博客 ,请勿用于任何商业用途)

『访问 思考者日记网404页面 寻找遗失儿童』

告知
  •     本站90%以上文章均属原创,部分转载已加上原作者出处。 如需转载本站文章请您务必保留本站出处!
  •     打广告评论者请自重,请为广大网友提供一个健康干净的网络空间。
  • 感谢主机屋提供网站空间;
  • 感谢万网阿里云提供域名解析;
  • 感谢EmpireCMS提供CMS系统;
  • 感谢bootstrap展示本站前端页面;
  • 感谢Glyphicons Halflings提供字体;
  • 感谢大家一直以来对本站的喜爱,感谢大家!
近期文章 建议与反馈