【轉】sip協議介紹

會話發起協議(Session Initiation Protocol,SIP)是一種信令協議,它對於通信業有着重要的意義。本文從技術上對SIP進行了一般性的介紹,並說明了SIP如何爲通信解決方案提供重要支持。

簡介

  我曾經構想過一種軟件,它可以“浮於”應用程序之上,提供輔助作用。這不是一個啞的“幫助”系統,而是一個活動的技術支持代理,可以在Internet上進行討論。那時曾有人告訴我,“現有的工具、庫、協議或帶寬實現不了這樣的要求!”

  時代已經不同了!

  許多人家中已經擁有基於DSL、光纖和其他技術的寬帶網絡。存在大量高質量的工具和庫,無論是商業的還是開源的。標準驅動應用。現在是時候來實現上述創新思路了。

瞭解SIP

  首先,我將向您介紹SIP,也就是會話發起協議。SIP是一種輕量級的可擴展請求/響應協議,用於在兩個端點之間開始通信會話。這聽起來是不是很熟悉?SIP在概念上源自於HTTP和SMTP,但是它的目的卻不同。可以把SIP消息與CB(民用波段)隱語10-code和Q-signal進行比較。


圖1.用於管理CB呼叫的隱語

  在這個例子中,真正的消息包含在專用的呼叫協商消息中。

  SIP是IETF於1999年提出的,在2002年進行了修訂。RFC 3261對它進行了描述。本文中有關SIP的信息就選自RFC。對SIP存在很多擴展,這些擴展中的很多都能在SIP-related RFCs and drafts清單中找到。

  SIP有哪些優點呢?通常情況下,兩個端點使用它來協商一次“呼叫”。這裏的協商包括介質(文本、語音等)、傳輸(通常是RTP、Real Time Protocol)和編碼(codec)。一旦協商成功,兩個端點就會使用選中的方法相互交談——這就與SIP無關了。“呼叫”完成之後,SIP用於指示斷開連接。因此,SIP最好用作一種信令機制。SIP及其擴展還提供一些相關功能,比如即時消息傳遞、註冊和到場。

  SIP術語中的端點叫做用戶代理。它可以是“軟件電話”、即時消息收發器、IP電話,甚至是手機。服務器用戶代理提供集中式的服務,比如登記員、代理或應用服務器。

  聽起來SIP非常簡單,而且它也確實簡單。這種簡單性對於協議的穩定很重要,而且它也沒有降低協議的有用性,所以該協議得到了廣泛的應用。

  例如,考慮一下HTTP。協議本身的定義很少。但是使用它的方式多不勝數。SIP也是可以擴展的。存在大量針對SIP的擴展,它們涵蓋了很多應用。現在,我們進一步來考察SIP,並分析它爲什麼會如此重要。

SIP的重要性

  有人說,SIP對於通信,就像HTTP對於Web一樣。

  SIP對通信業產生了巨大的影響。從事蜂窩技術的公司已經決定爲了未來的應用,對SIP進行標準化。VoIP (Voice over IP)供應商、Internet電話和即時消息傳遞應用程序(例如,Microsoft MSN Messenger)都準備基於SIP進行標準化。

  目前已經存在一些信令協議和P2P技術。這就引發了一個問題:SIP相對於這些協議和技術有何優勢?SIP具有以下的明顯優點:

  • 穩定性。該協議已經使用了多年,現在十分穩定。
  • 速度。基於UDP的小型協議效率特別高。
  • 靈活性。這個基於文本的協議十分容易擴展。
  • 安全性。它提供像加密(SSL、S/MIME)和身份驗證這樣的功能。對SIP的擴展還提供其他安全性功能。
  • 標準化。隨着整個通信行業都在向SIP靠攏,SIP已經訊速成爲一種標準。其他技術可能具有SIP所沒有的優勢,但是它們沒有得到全球範圍內的採用。

  這意味着,如果您想讓您的應用程序與其他工具、設備和服務器進行互操作,SIP是最佳選擇。供應商對互操作性是很關注的,會定期開會對其產品進行測試。這些會議稱爲SIPit for SIP Interoperability Tests(以前叫做Bakeoff,是Pillsbury提出更改的)。

剖析SIP呼叫

  現在,讓我們更深入地瞭解這門技術。SIP通常基於UDP進行傳輸,但是SIP工具必須同時支持TCP。一條SIP消息由兩部分組成:

  • 信封(envelope),以頭字段的形式描述一個請求或請求的結果(響應)。
  • 有效負載(payload),即內容(可選),包含有關請求的數據。

  信封是文本格式,但是內容可以是文本,也可以是二進制格式。

  例如,讓我們具體分析一次典型的SIP呼叫。在這個場景中,用戶A想呼叫用戶B。圖2說明了這次呼叫:



圖2. 一次典型的SIP呼叫

  下面對所有的消息進行了解釋:

 

 

1. User Agent A發送一個SIP請求INVITE給User Agent B,表達User A想跟User B進行談話的願望。這個請求包含語音流協議的細節。payload中使用會話描述協議(Session Description Protocol,SDP)就是爲此目的。SDP消息包含一個清單,其內容爲User A支持的所有介質編碼。(這些編碼使用RTP進行傳輸。)

INVITE
sip:[email protected]
SIP/2.0
Via: SIP/2.0/UDP 10.20.30.40:5060
From: UserA <sip:[email protected]>;tag=589304
To: UserB <sip:[email protected]>
Call-ID: [email protected]
CSeq: 1 INVITE
Contact: <sip:[email protected]>
Content-Type: application/sdp
Content-Length: 141
 
v=0
o=UserA 2890844526 2890844526 IN IP4 10.20.30.40
s=Session SDP
c=IN IP4 10.20.30.40
t=3034423619 0
m=audio 49170 RTP/AVP 0
a=rtpmap:0 PCMU/8000

2. User Agent B讀取該請求,然後告訴User Agent A它已經收到請求。

SIP/2.0
100 Trying
From: UserA <sip:[email protected]>;tag=589304
To: UserB <sip:[email protected]>
Call-ID: [email protected]
CSeq: 1 INVITE
Content-Length: 0

3.當電話響鈴時,User Agent B發送臨時消息(響鈴)給User Agent A,這樣它就不會超時和放棄。

SIP/2.0
180 Ringing
From: UserA <sip:[email protected]>;tag=589304
To: UserB <sip:[email protected]>;tag=314159
Call-ID: [email protected]
CSeq: 1 INVITE
Content Length: 0

4.最終,User B決定接受呼叫。此時,User Agent B發送一個OK響應給User Agent A。在響應的payload中,還有另一條SDP消息。它包含一組兩個用戶代理都支持的介質編碼。此時,雙方正式處於呼叫中。使用200類型的響應可以接受所有類型的SIP請求。

SIP/2.0
200 OK
From: UserA <sip:[email protected]>;tag=589304
To: UserB <sip:[email protected]>;tag=314159
Call-ID: [email protected]
CSeq: 1 INVITE
Contact: <sip:[email protected]>
Content-Type: application/sdp
Content-Length: 140
 
v=0
o=UserB 2890844527 2890844527 IN IP4 10.20.30.41
s=Session SDP
c=IN IP4 10.20.30.41
t=3034423619 0
m=audio 3456 RTP/AVP 0
a=rtpmap:0 PCMU/8000

5. User Agent A最後使用一條ACK消息進行確認。對於這種請求類型來說,沒有重試和響應消息,即使消息丟失。ACK只在INVITE消息中使用。

ACK
sip:[email protected] SIP/2.0
Via: SIP/2.0/UDP 10.20.30.41:5060
Route: <sip:[email protected]>
From: UserA <sip:[email protected]>;tag=589304
To: UserB <sip:[email protected]>;tag=314159
Call-ID: [email protected]
CSeq: 1 ACK
Content-Length: 0

6..兩個用戶代理現在使用最後一條SDP消息中選定的方法進行連接。

RTP使用PCMU/8000編碼對在端口49170 & 3456上雙向傳輸的音頻數據進行打包。

7.在通信會話結束時,其中一個用戶掛斷。此時,這個用戶的用戶代理髮送一個新的請求BYE。這條消息可以由任一方發送。

BYE
sip:[email protected] SIP/2.0
Via: SIP/2.0/UDP 10.20.30.41:5060
To: UserB <sip:[email protected]>;tag=314159
From: UserA <sip:[email protected]>;tag=589304
Call-ID: [email protected]
CSeq: 1 BYE
Content-Length: 0

8.另一用戶的用戶代理接受該請求,然後使用一條OK消息作爲應答。呼叫連接至此斷開。

SIP/2.0
200 OK
To: UserB <sip:[email protected]>;tag=314159
From: UserA <sip:[email protected]>;tag=589304
Call-ID: [email protected]
CSeq: 1 BYE
Content-Length: 0

  SIP消息的第一行包含消息的類型和所使用的SIP版本(2.0)。在請求中,這一行還包含一個叫做SIP URI的地址。這代表消息的目的地。 
這個例子說明了如何使用請求消息INVITE、ACK和BYE,以及200 OK響應消息。SIP中還存在許多其他消息。下面給出一些請求:

消息

用法

INVITE

呼叫一個用戶代理,傳送一次呼叫。

ACK

確認呼叫。

BYE

終止呼叫。

CANCEL

終止還未OK的呼叫。

REGISTER

提供一項註冊服務,帶有一個聯繫地址和可以用來代替的別名。例如,在前面的例子中,地址sip:[email protected]就是sip:[email protected]的別名。然後,註冊服務器example.com就可以把呼叫轉發給地址10.20.30.40。

OPTIONS

詢問一個用戶代理的“能力”(例如,該用戶代理能夠識別的消息和編碼)。

  現在給出一些經常使用的響應消息:

消息

用法

100 Trying

消息已收到,但是最終用戶代理尚未進行處理。請等待。

180 Ringing

最終用戶代理已經收到消息,正在提示用戶。請等待。

200 OK

最終用戶已經接受消息。

301 Moved Permanently & 302 Moved Temporarily

用戶代理的地址已經改變,新的永久或臨時地址位於Contact字段中。

400 Bad Request

普通錯誤消息。客戶端不能識別消息。

401 Unauthorized & 407 Proxy Authentication Required

請使用證書重試。

404 Not Found

要聯繫的用戶不存在或尚未註冊。

408 Request Timeout

另一方沒有響應。這意味着SIP消息永遠不會OK。所有重試都將被丟棄。這並不意味着電話響太長時間(電話可以永遠響鈴)。

  消息使用類似的頭字段類型。下面給出其中的一些:

頭字段

用法

From

SIP請求的發送者。

To

SIP請求的接受者。這通常與SIP URI相同(可以是一個“別名”或一個實際地址)。

Contact

用戶代理的實際地址。

Call-ID

這並不是呼叫者的電話號碼。它惟一地代表兩個用戶代理之間的完整呼叫或對話。所有相關的SIP消息都使用同一個Call-ID。例如,當一個用戶代理收到一條BYE消息,根據Call-ID,它就知道要掛斷哪次呼叫。

CSeq

消息的順序編號。這在一次對話或一個Call-ID中是惟一的。這用於區別新的消息和“重試消息”。當一條初始消息沒有及時OK時,重試就會進行,並會定時發送。

Content-Type

消息內payload的MIME類型。

Content-Length

payload的大小,以字節爲單位。信封和payload之間由一空行隔開。

  還有一些與消息路由選擇功能相關的頭字段,如:Via、Route和Record-Route。許多頭字段提供像Accept、User-Agent和Supported這樣的功能。其他頭字段則提供像Authorization、Privacy和WWW-Authenticate這樣的安全性功能。還有很多其他的頭字段存在。此外,這些字段中許多都有縮寫語法(比如,From = f,To = t,等等)。

SIP的其他功能

  使用SIP及其擴展可以實現很多應用:

  • VoIP
  • 視頻會議
  • 針對文本和數據的即時消息傳遞,比如MSN Instant Messenger
  • 註冊(我在線!)
  • 到場(我的夥伴在不在?)
  • Click-to-talk(點擊通話,即點擊這裏便可與一個技術支持代理交談)
  • 應答機器/互動式語音應答(Interactive Voice Response,IVR)系統(“請輸入您的密碼。請記錄您的姓名。英文請按1,西班牙語請按2…”)
  • 網絡遊戲,比如Quake和一些手機遊戲(甚至基於語音和IM)
  • 基於手機的應用程序
  • 移動電子商務

  基本上,如果是兩個端點之間進行通信,SIP就能完成。 
但是,關於活動的Web技術支持代理的想法又如何呢?現在我們能否使用SIP來實現它?我們能否使用Java來實現它?乾脆點說,可以。

Java中的SIP

  我經常使用SIP。我可以負責任地說,Java爲SIP提供了絕佳的支持。Java技術的一個分支把與開發SIP應用程序相關的許多細節抽象出來,這對於SIP開發人員大有幫助。下面這些技術大部分位於JAIN (Java APIs for Integrated Networks)工作組中。

  • JAIN SIP API (JSR 32)
  • SIP Servlet API (JSR 116)
  • JAIN SIP Lite (JSR 125)
  • SIP API for J2ME (JSR 180)
  • JAIN SIMPLE Presence (JSR 164)
  • JAIN SIMPLE Instant Messaging (JSR 165)
  • JAIN SDP (JSR 141)
  • Java Media Framework for RTP (J2SE可選包,並非JAIN的)

其他相關技術有:

  如果您希望開發一個客戶端應用程序,就需要一個客戶端SIP引擎或者“堆棧”。在這裏可以找到一個優秀的開源Java SIP堆棧。它還支持SDP。如果不想自己開發SIP電話,您可以使用這個

結束語

  本文簡要介紹了SIP、它的使用場景,以及一些SIP語法。我們還了解了各種與SIP相關的Java技術。儘管本文不夠詳盡,我還是希望它能夠激發您的興趣,並促使您開始使用它。SIP的時代已經到來,現在使用它可以實現很多很酷的想法。 
在本系列文章的第2部分中,我將說明如何使用SIP Servlet API編寫一個聊天室應用程序。

參考資料

  原文出處:http://dev2dev.bea.com/pub/a/2005/09/introduction-sip-part-1.html


 作者簡介


Emmanuel Proulx 是一位J2EE和Enterprise JavaBeans方面的專家,也是一位獲得認證的WebLogic Server 7.0工程師


SIP簡介,第2部分:SIP SERVLET

時間:2006-04-19
作者:Emmanuel Proulx

本文關鍵字:SIPSession Initiation ProtocolJAINRFC3261WebLogic Communications Platform會話發起協議信令

http://dev2dev.bea.com.cn/techdoc/20060419772.html


摘要

  會話發起協議(Session Initiation Protocol,SIP)是一個重要的信令協議,它正在迅速被電信業採用以構建下一代應用程序。Java是用於SIP開發的極好平臺,尤其是在進行服務器端開發時。類似於HTTP servlet,SIP Servlet API使SIP服務的開發變得更輕鬆。本文將介紹SIP servlet技術,並提供一個帶註釋的例子。

簡介

  即時消息傳遞正在改變人們的生活。它是一個非常有用的工具,結合了電子郵件、Internet電話以及文件傳輸應用程序的優點。用戶甚至可以看到誰在線、誰的狀態爲“忙碌”。當然了,人們可以用它來長時間地進行不創造任何效益的聊天。但是,員工也可以利用它在老闆會見客戶時向他發送極爲重要的信息。

  所以,市場上出現如此多的不同種類的即時消息傳遞應用程序也就不足爲怪了。有這麼多的選擇應該是一件好事,可是如果員工使用的應用程序與老闆使用的不同,那又會怎麼樣呢?這將是一個大問題,因爲這些應用程序大多都使用專有的協議。

  SIP爲我們帶來了福音。SIP很有可能會成爲標準的即時消息傳遞協議。

  在本文中,我將開發一個簡單的SIP應用程序——一個允許SIP即時messenger (消息傳遞應用程序)彼此聯繫並互相傳播消息的聊天室服務器端。

SIP SIMPLE

  SIMPLE ,即SIP Instant Messaging and Presence Leveraging Extension(SIP即時消息和現場支持擴展)的縮寫,是一個工作組以及一組SIP擴展。其中的一個擴展是MESSAGE消息。可以用它來發送包含文本和二進制內容的任意組合的即時消息。這種消息使用起來非常簡單,這也是我決定使用它來開發第一個SIP應用程序的原因。

TextClient

  爲了測試我們的應用程序,我提供了一個小型SIP即時messenger應用程序(參見文章結尾處的“下載”部分)。該應用程序向其他messenger或服務器發送MESSAGE消息。用戶界面中包含了客戶端的地址、好友地址的輸入字段、一個文本消息以及一個提交按鈕。圖1顯示了正在運行的TextClient。


圖1. 運行中的TextClient

  要啓動TextClient,只需使用以下命令:

  java -jar textclient.jar dev2dev.textclient.TextClient username port
該命令使用JAIN SIP API參考實現作爲一個SIP協議棧。我們提供了該工具的源代碼,如果您希望瞭解更多,我推薦您讀一下源代碼。

ChatRoomServer

  下面是示例應用程序的需求

  聊天室是一個虛擬空間,不同的即時messenger應用程序可以在其中進行交互。傳入聊天室的消息將向聊天室中其他所有的人進行廣播。換句話說,所有的消息都可以被所有用戶看到。這意味着,當一個消息到達服務器端應用程序時,用戶的地址將被添加到一個列表中。然後消息將被髮送到該列表中的所有用戶。

  此外,還可以實現“命令”。命令以正斜槓(/)開頭,它不被廣播,而是由服務器自己處理,用於特定功能。我將實現的命令包括:

  • /join:默默地進入一個聊天室,不廣播任何消息。
  • /who:打印一份該聊天室所有用戶的列表。
  • /quit:離開聊天室,不再有消息傳入。

SIP Servlet API

  SIP Servlet API (JSR 116)是一個服務器端接口,它描述了一個SIP組件或服務的容器。這正適合用於開發ChatRoomServer。下載該規範,並解壓縮。生成的文件夾包括一些庫(servlet.jar、sipservlet.jar)以及文檔。我無法獲得運行示例SIP servlet的參考實現,所以我想您也不必費心去找它了。

  SIP servlet最核心的概念是包含。SIP服務是部署或運行在在一個容器或SIP應用服務器上的打包SIP servlet。容器提供了可供應用程序使用的許多服務,比如自動重試、消息調度和排隊、分流和歸併,以及狀態管理。應用程序中只需包含高級的消息處理和業務邏輯。這使SIP服務的開發成爲一件輕而易舉的事情。

  本文的目的不是要提供對SIP Servlet API技術的全面介紹。因此我只簡要概述了該API和示例代碼,更多信息請參見文章結尾處的“參考資料”部分。

服務器端代碼

  如果您曾經開發過HTTP servlet,那麼服務器端的代碼會讓您感到非常熟悉。如果您還不知道什麼是servlet,您應該首先了解一下。SIP Servlet規範是HTTP Servlet規範的擴展。其語法、容器行爲,甚至方法名都是相似的。

  下面我將詳細分析該例子。它主要由3個部分組成:

1.    生命週期方法

  這些方法在啓動或關閉servlet時被容器調用:

public class ChatRoomServer extends SipServlet {
 
/** Context attribute key to store user list. */
public static String THE_LIST="dev2dev.chatroomserver.userList";
 
/** Init parameter key to retrieve the chat room's address. */
public static String THE_NAME="dev2dev.chatroomserver.name";
 
/** This chat room server's address, retrieved from the init params. */
public String serverAddress;
 
/** This is called by the container when starting up the service. */
public void init() throws ServletException {
 super.init();
 getServletContext().setAttribute(THE_LIST,new ArrayList());
 serverAddress = getServletConfig().getInitParameter(THE_NAME);
}
 
/** This is called by the container when shutting down the service. */
public void destroy() {
 try
 {
  sendToAll(serverAddress, "Server is shutting down -- goodbye!");
 } catch (Throwable e)
 { //ignore all errors when shutting down.
  e.printStackTrace();
 }
 super.destroy();
}
...

  在初始化方法中,我創建了一個所有會話共享的全局屬性。這是用戶的列表。我還獲得了該聊天室的地址(servlet參數)以備將來使用。

2.    消息處理方法

  SIP servlet與HTTP servlet稍有不同。對於HTTP servlet,您處理傳入的請求,併發送響應消息。而對於SIP servlet,可以發送和接收請求和響應。我將說明如何做到這一點。

  當收到消息(請求或響應)時,容器將調用下面的方法。容器將按照下面圖表的順序調用這些方法,也可以重寫這些方法來根據消息的類型處理消息:

void service(ServletRequest,ServletResponse) 如果對其進行重寫,不要忘記調用super.service()。 其默認實現調用以下方法之一:

void doRequest(SipServletRequest)
如果對其進行重寫,不要忘記調用super.doRequest()。
其默認實現調用以下方法之一:

void doResponse(SipServletResponse)
如果對其進行重寫,不要忘記調用super.doResponse()。
其默認實現調用以下方法之一::

下列請求方法之一(自解釋):

1.     doAck(SipServletRequest)

2.       doBye(SipServletRequest)

3.       doCancel(SipServletRequest)

4.       doInfo(SipServletRequest)

5.       doInvite(SipServletRequest)

6.       doMessage(SipServletRequest)

7.       doNotify(SipServletRequest)

8.       doOptions(SipServletRequest)

9.       doPrack(SipServletRequest)

10.   doRegister(SipServletRequest)

11.   doRequest(SipServletRequest)

12.   doResponse(SipServletResponse)

13.     doSubscribe(SipServletRequest)

下列響應方法之一:

14.   doProvisionalResponse(SipServletResponse)—對應於1xx-類響應消息。

15.   doSuccessResponse(SipServletResponse)—對應於2xx-類響應消息。

16.   doRedirectResponse(SipServletResponse)—對應於3xx-類響應消息。

17.doErrorResponse(SipServletResponse)—對應於4xx-、5xx-以及6xx-類響應消息。

  例如,MESSAGE可以調用以下方法:

18. service(),傳入一個SipServletRequest(必須進行類型轉換)以及null

19. doRequest()

20. doMessage()

  通常只重寫最後一級的方法,除非使用了非標準的SIP消息,或者希望收集有關消息的統計信息。
下面是處理即時消息的代碼:

/** This is called by the container when a MESSAGE message arrives. */
protected void doMessage(SipServletRequest request) throws
        ServletException, IOException {
 
    request.createResponse(SipServletResponse.SC_OK).send();
 
    String message = request.getContent().toString();
    String from = request.getFrom().toString();
 
    //A user asked to quit.
    if(message.equalsIgnoreCase("/quit")) {
        sendToUser(from, "Bye");
        removeUser(from);
        return;
    }
 
    //Add user to the list
    if(!containsUser(from)) {
        sendToUser(from, "Welcome to chatroom " + serverAddress +
                ". Type '/quit' to exit.");
        addUser(from);
    }
 
    //If the user is joining the chat room silently, no message
    //to broadcast, return.
    if(message.equalsIgnoreCase("/join")) {
        return;
    }
 
    //We could implement more IRC commands here,
    //see http://www.mirc.com/cmds.html
    sendToAll(from, message);
}
 
/**
 * This is called by the container when an error is received
 * regarding a sent message, including timeouts.
 */
protected void doErrorResponse(SipServletResponse response)
        throws ServletException, IOException {
    super.doErrorResponse(response);
    //The receiver of the message probably dropped off. Remove
    //the receiver from the list.
    String receiver = response.getTo().toString();
    removeUser(receiver);
}
 
/**
 * This is called by the container when a 2xx-OK message is
 * received regarding a sent message.
 */
protected void doSuccessResponse(SipServletResponse response)
        throws ServletException, IOException {
    super.doSuccessResponse(response);
    //We created the app session, we have to destroy it too.
    response.getApplicationSession().invalidate();
}

  第一個方法在收到一個MESSAGE消息時被調用。最初以一條200 OK消息響應,表明收到了消息。然後它處理服務器命令,比如/join。最後,它調用一個業務邏輯方法來廣播傳入的消息。

  傳入的錯誤響應消息表明上一個請求失敗了。這可能意味着有一個用戶被斷開了。只需將該用戶從列表中移除即可。

  成功的響應消息表明上一個MESSAGE消息被即時messenger正確地接收了。因此不再需要該會話,可以將其刪除了。通常,MESSAGE消息是以無狀態的形式發送的,並不保存消息之間的連接信息。(對於INVITE消息來說,情況不是這樣的,它打開一個有狀態的會話直到發送BYE。)

3.    業務邏輯代碼

  其餘的代碼由helper方法組成。前兩個方法向即時messenger發送消息。要發送消息,使用一個工廠創建以下兩項:

0.    一個SipApplicationSession(稍後將詳細介紹)

1.    一個請求消息

  此時,可以隨心所欲地修改消息。在我們的例子中,我們在有效負載中添加即時消息文本。最後,發送該消息。

private void sendToAll(String from, String message)
        throws ServletParseException, IOException {
    SipFactory factory = (SipFactory)getServletContext().
        getAttribute("javax.servlet.sip.SipFactory");
 
    List list = (List)getServletContext().getAttribute(THE_LIST);
    Iterator users = list.iterator();
    while (users.hasNext()) { //Send this message to all on the list.
        String user = (String) users.next();
 
        SipApplicationSession session =
            factory.createApplicationSession();
        SipServletRequest request = factory.createRequest(session,
                "MESSAGE", serverAddress, user);
        String msg = from + " sent message: \n" + message;
        request.setContent(msg.getBytes(), "text/plain");
        request.send();
    }
}
 
private void sendToUser(String to, String message)
        throws ServletParseException, IOException {
    SipFactory factory = (SipFactory)getServletContext().
        getAttribute("javax.servlet.sip.SipFactory");
    SipApplicationSession session = factory.createApplicationSession();
    SipServletRequest request = factory.createRequest(session,
            "MESSAGE", serverAddress, to);
    request.setContent(message.getBytes(), "text/plain");
    request.send();
}
 
private boolean containsUser(String from) {
    List list = (List)getServletContext().getAttribute(THE_LIST);
    return list.contains(from);
}
 
private void addUser(String from) {
    List list = (List)getServletContext().getAttribute(THE_LIST);
    list.add(from);
}
 
private void removeUser(String from) {
    List list = (List)getServletContext().getAttribute(THE_LIST);
    list.remove(from);
}
 
}

部署描述符

  對於HTTP servlet,還必須編寫web.xml部署描述符。而在SIP servlet中,對應的文件是sip.xml,我們在其中列出SIP servlet、初始化參數以及映射(哪個SIP servlet處理哪些SIP消息)。關於該文件語法的更多信息,請參見SIP Servlet規範中15.5節的DTD。其語法類似於web.xml,但<servlet-mapping>標籤除外。它不會將一個URL模式映射到servlet,而是(基於字段和子字段的內容)描述一個條件,SIP請求必須滿足這個條件才能被映射到servlet。SIP Servlet規範第11節描述了所有的字段、子字段以及用於該映射的條件。

  注意,該映射只用於初始請求;同一個會話/對話中的後續請求由處理初始請求的同一servlet處理。
下面是用於ChatRoomServer的XML代碼:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sip-app
   PUBLIC "-//Java Community Process//DTD SIP Application 1.0//EN"
   "http://www.jcp.org/dtd/sip-app_1_0.dtd">
<sip-app>
   <servlet>
      <servlet-name>ChatRoomServer</servlet-name>
      <servlet-class>dev2dev.chatroomserver.ChatRoomServer</servlet-class>
      <init-param>
         <param-name>dev2dev.chatroomserver.name</param-name>
         <!-- This will be replaced by the build script -->
         <param-value>sip:chatroomname@serveraddress</param-value>
      </init-param>
   </servlet>
 
   <servlet-mapping>
      <servlet-name>ChatRoomServer</servlet-name>
      <pattern>
         <and>
            <equal>
               <var>request.uri.user</var>
               <!-- This will be replaced by the build script -->
               <value>chatroomname</value>
            </equal>
           <equal>
             <var>request.method</var>
             <value>MESSAGE</value>
           </equal>
         </and>
      </pattern>
   </servlet-mapping>
 
</sip-app>

  代碼看起來很複雜,其實並非如此。Servlet映射說明了:

  如果請求URI的用戶名部分等於chatroomname,則將傳入的MESSAGE請求映射到ChatRoomServer Servlet

  該聊天室名稱只是一個佔位符。在編譯過程中,會用實際的聊天室名稱替換關鍵字“chatroomname”。

  這麼做有什麼用呢?您可以將同樣的服務部署多次,每次都使用其獨有的聊天室名稱,而消息可以自動發送到相應的servlet。

構建、打包、部署

  需要對SIP servlet進行編譯,並將其打包到SAR(Servlet ARchives)文件中。這些文件在功能上等效於WAR文件,結構也相同。參見圖2:


圖2. SAR文件結構

  最後一步是部署,這根據SIP應用服務器的不同而不同。通常需要將SAR文件複製到一個部署文件夾中,然後部署應用程序。

  下面的Ant腳本可以幫助部署:

<project name="ChatRoomServer"  default="build"  basedir=".">
 
    <!-- Change this to specify the name of the chat room. In order to
         send messages to this chat room, simply deploy just4fun.sar, and
         use the address sip:[email protected]:5060. -->
    <property name="chatroomname" value="just4fun" />
 
    <!-- Change this to the address and port of the SIP server. -->
    <property name="serveraddress" value="10.0.2.69:5060" />
 
    <!-- Change this to the location of the SAR deployment folder. -->
    <property name="sar.deployment" value="" />
 
    <property name="src" value="${basedir}/src" />
    <property name="lib" value="${basedir}/lib" />
    <property name="tmp" value="${basedir}/tmp" />
 
    <path id="classpath">
        <fileset dir="${lib}"/>
    </path>
 
    <target name="build">
        <mkdir dir="${tmp}"/>
        <mkdir dir="${tmp}/WEB-INF"/>
        <mkdir dir="${tmp}/WEB-INF/classes"/>
        <mkdir dir="${tmp}/WEB-INF/lib"/>
        <javac debug="true" srcdir="${src}" destdir="${tmp}/WEB-INF/classes">
            <classpath refid="classpath"/>
        </javac>
        <copy todir="${tmp}/WEB-INF" file="${basedir}/sip.xml"/>
        <replace file="${tmp}/WEB-INF/sip.xml" token="chatroomname" value="${chatroomname}"></replace>
        <replace file="${tmp}/WEB-INF/sip.xml" token="serveraddress" value="${serveraddress}"></replace>
        <zip destfile="${basedir}/${chatroomname}.sar">
            <zipfileset dir="${tmp}"/>
        </zip>
        <copy file="${basedir}/${chatroomname}.sar" todir="${sar.deployment}"/>
    </target>
 
</project>

結果

  聊天室應用程序運行之後,試着通過運行兩個TextClient實例來訪問它。要確保運行在同一機器上的SIP應用程序使用的是不同的端口。下面的例子顯示了運行在同一機器上的3個應用程序:

  圖3顯示了結果。


圖3. TextClient與ChatRoomServer交互

複雜應用程序

  我知道本文中的例子相對於我們通常要構建的應用程序來說有點過於簡單了,現實中的大多數SIP應用程序都由大量代碼組成。

  會話和狀態:通常,SIP應用程序是一個狀態機(state machine),其中呼叫或會話都是長時間保持的(有狀態的),直到斷開。對於SIP servlet,呼叫是由SipApplicationSession表示的,它可以帶屬性(狀態)。在呼叫中,每個會話(呼叫的分支)由SipApplicationSession中的SipSession表示。(兩人間的back-to-back會話要使用一個SipApplicationSession和兩個SipSession。會議呼叫可能包含更多的SipSession。這些都可以帶屬性。容器會根據消息的上下文自動提供相應的會話對象。

  分層設計:最糟糕的是將所有的代碼放入單個的大型SIP servlet。應該按照相對獨立的層來設計複雜的應用程序。一個明顯的層就是包括連接池的數據庫層。也可以包含一個與SIP信令分離的業務邏輯層。另一個方面是有效負載分析,它應該被構建爲一個可重用的層。

  其他技術:存在許多先進的SIP servlet技術,包括請求代理、重定向和循環、會話超時管理、身份驗證、國際化、TCP支持、計時器、會話監聽程序以及錯誤管理。很明顯,本文並沒有涵蓋所有這些方面,但是您可以在SIP Servlet規範中找到相關內容。

  例子:可以參見“參考資料”部分,其中有可以幫助您瞭解更復雜的SIP編程的例子。

結束語

  標準促進了互操作性,從而促進了協作。而協作——不管它是用於朋友間的輕鬆聊天,還是用於重要的文件傳輸——都是一件好事。

  SIP是一個大有前途的電信標準,而SIP Servlet API則是輕鬆快速地開發服務器端SIP應用程序的極佳方式。在本文中,我們通過一個簡單的例子,概述了SIP servlet編程。希望通過本文,能夠幫助您在協作的道路上邁出一大步。

下載

參考資料

原文出處:http://dev2dev.bea.com/pub/a/2006/02/sip-servlet.html

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章