PJSIP開發手冊之SIP事件通知(十三)
第十三章 SIP特定的事件通知
SIP事件特定的通知在RFC3265“Session Initiation Protocol-SpecificEvent Notification”描述。這個核心協議定義了建立事件訂閱的兩種SIP方法,即SUBSCRIBE和NOTIFY,儘管其他方法也可以被定義來建立訂閱(如REFER)。
這章描述了PJSIP的設計和基於基本的Dialog框架來建立基本和通用的事件通知框架的實現。這章可以用來實現高層的事件包如presence和call transfer(使用REFER)。
PJSIP的事件通知框架的實現被打包成一個靜態庫pjsip-simple,在pjsip目錄下。為了使用它的功能,應用應該包括標頭檔案<pjsip_simple.h>和連線到pjsip-simple靜態庫。
基本概念
所有型別的JSIP事件通知會話表示為pjsip_evsub物件。這個物件管理訂閱的生命週期,和轉化到來的請求和響應到適合的回撥函式的呼叫。
PJSIP事件通知會話使用基本的Dialog框架來管理下層的Dialog和維護Dialog屬性(像請求目標,CSeq排序,等)。因為基本的Dialog的設計允許Dialog被多個會話共用,多個事件訂閱會話可能使用相同的Dialog,並且它也可以和invite會話共用這個Dialog。
為了訂閱一個事件通知,應用需要建立一個事件訂閱物件,指定下層的Dialog和接收訂閱事件的回撥函式。
到來的訂閱請求(如SUBSCRIBE或REFER)將到達一個Dialog或者應用,根據這個請求是否在一個Dialog內。應用必須檢測請求中的Event id並使用合適的包的API來處理訂閱。例如,當請求是REFER,應用呼叫pjsip_xfer_create_uas()來建立服務端訂閱。當SUBSCRIBE請求中的Event id是“presence“時,應用呼叫pjsip_pres_create_uas()來建立服務端訂閱。
事件包
事件包描述了事件訂閱的語義。在PJSIP中,在帶有指定的事件ID的會話被建立之前事件包必須首先被註冊到事件框架上。通過呼叫pjsip_evsub_register_pkg()可以把事件包註冊到這個框架。這個函式通常當這個PJSIP模組實現的這個事件包被初始化時。
這個事件包主要負責向NOTIFY請求提供訊息體。例如,PJSIP presence事件包建立所有外出的NOTIFY請求訊息體使用內容型別”application/pidf+xml”或者”application/xpidf+xml”。
頭部域
事件框架基於已註冊的事件包提供的資訊來管理外出請求的Accept,Allow-Events,Event,Expires和Subscription-State頭部域的內容。它也檢查到來請求中的Expires和Subscription-State頭部域的內容,並相應地更新它自己的狀態。
所有其他頭部域(和Dialog外的頭部域)必須被事件包或者應用處理。例如,refer事件包管理外出的REFER請求的Refer-To頭部域。
基本的操作
這個會話描述如何使用核心的PJSIP事件訂閱框架。正如你將在後面章節見到,高層的事件包(如presence和call transfer)的操作,將和核心事件框架的操作類似。
注意:為了節省空間,圖中省略了”pjsip_”頭。
客戶端初始化訂閱
客戶端通過構建和傳送SUBSCRIBE請求建立Dialog來初始化訂閱。客戶端應該將合適的證書放入這個Dialog中,這樣認證的挑戰可以被evsub模組自動處理。客戶端應該在Dialog中設定合適的路由集。
描述:
1. 應用(或者事件包)初始化客戶端訂閱通過先建立一個UAC Dialog(1a),接著建立這個客戶端訂閱會話(1b)。應用可以1a和1b步驟之間設定Dialog的證書和路由集。
2. 通過建立請求(2a)和傳送請求(2b),應用傳送初始的SUBSCRIBE(或者其他建立訂閱的方法,像REFER)。
3. 上面2步驟中的SUBSCRIBE請求的傳送將觸發on_evsub_state()回撥函式被呼叫。這甚至可能在evsub_send_request()函式返回之前發生。
4. 步驟4中應用接收on_tsx_state()中的任何transaction狀態的進度。這個回撥函式是可選的,並且只為提供資訊。如果這個請求被挑戰,並且證書已經在這個Dialog裡設定了,這個事件框架將使用正確的證書提交這個請求。
5. 當接收到2xx響應,on_evsub_state()回撥將被呼叫。應用可以通過呼叫pjsip_evsub_get_state()函式得到這個訂閱狀態,當接收到2xx響應時,這個函式返回PJSIP_EVSUB_STATE_ACCEPTED。當接收到非2xx最終響應時,這個訂閱狀態將被設定為PJSIP_EVSUB_STATE_TERMINATED。
服務端接收到來的訂閱
描述:
1.會話外的到來請求將總會到達應用,應用最終決定如何處理這個請求。對於到來的SUBSCRIBRE請求,如果應用想要認證這個請求,它可以有狀態或無狀態地傳送401/407響應(步驟1a)來響應這個請求。這必須在任何Dialog或者訂閱例項被建立之前完成。當應用滿意這個請求(步驟1b),它可以接著建立這個服務端訂閱例項(步驟2a和2b)。
如果這個請求在一個Dialog內(例如,REFER請求),應用接收這個Dialog的回撥函式
on_tsx_state()中的請求。在這種請況下,步驟1到2a是不需要的,並且應用直接執行步驟2b。
2.服務端的事件訂閱需要一個Dialog。應用可能為這個訂閱建立一個新的UAS Dialog(步驟2a),或者可能使用現有的Dialog(例如處理Dialog內的到來REFER請求)。應用接著建立服務端的事件訂閱(步驟2b),並傳入這個Dialog例項。
3.應用呼叫pjsip_evsub_accept()來發送響應(步驟3),並將傳入的狀態碼放入這個響應中。這個狀態碼必須是2xx的。
4.步驟3中傳送2XX響應將觸發on_evsub_state()回撥函式的呼叫(步驟4)。
服務端接著必須立刻傳送初始的NOTIFY請求,下面將會介紹。
服務端啟用訂閱(傳送NOTIFY)
服務端啟用服務端訂閱通過傳送NOTIFY請求(見下面的第5步)。如果服務端想要NOTIFY請求被認證,那麼它必須在建立UAS Dialog時設定證書。如果這個NOTIFY請求是一個挑戰,那麼只要這個Dialog中有一個正確的證書,那麼evsub模組將自動重新使用合適的證書提交NOTIFY請求(步驟6)。
客戶端接收NOTIFY請求
當接收到NOTIFY請求,應用可以通過在on_rx_notify()回撥函式中返回401來挑戰這個請求(見下面步驟5)。客戶端接著將等待NOTIFY請求的立即提交。預設地,訂閱框架等待NOTIFY請求重提交5秒,如果到了5秒還沒有收到NOTIFY請求它將傳送帶有0 Expires值的SUBSCRIBE請求來終止這個訂閱。
on_rx_notify()回撥函式是可選的。預設的是以200響應響應到來的NOTIFY請求。
注意如果應用應答這個NOTIFY請求以2xx響應的話,事件框架只更新它的狀態(根據到來的NOTIFY請求中的狀態)。
服務端終止訂閱
服務端終止訂閱通過傳送帶有Subscription-State為terminated的NOTIFY請求(下面步驟8)。服務端可能在它接收到建立這個會話的初始請求之後的任何時間傳送NOTIFY請求。
特別地,當這個訂閱超時後,服務端應該傳送Subscription-State為terminated的NOTIFY請求。
無論響應是什麼,只要傳送帶有Subscription-State為terminated的NOTIFY請求將觸發on_evsub_state()回撥函式的呼叫(步驟8b)。然而,當這個NOTIFY請求被挑戰時,這個框架將通過重新發送帶有合適的證書的請求來響應這個挑戰(如果這樣的證書存在)。
注意:另外,接收到481(Call/TransactionDoes Not Exist),408(Request Timeout),transaction超時,或者transport錯誤事件時也將終止這個服務端訂閱,並會觸發on_evsub_state()回撥函式。
客戶端接收訂閱終止
當接收到傳送帶有Subscription-State為terminated的NOTIFY請求,只有客戶端給服務端的響應是2xx時,on_evsub_state()回撥函式將被呼叫(下面步驟8)。
客戶端重新整理訂閱
當要重新整理這個訂閱時,事件框架將觸發on_client_refresh()回撥函式。應用必須通過呼叫pjsip_evsub_initiate()函式建立請求和pjsip_evsub_send_request()傳送請求來重新整理訂閱。
當PJSIP事件包中的presence或refer被使用時,這些包為這個回撥函式提供了預設的實現。這個預設的實現使用了最近的Expires值。因此如果應用正在使用這些包,它不需要實現這個回撥。
服務端檢查重新整理超時
當訂閱的expires期間內沒有接收到訂閱的重新整理,服務端訂閱將觸發on_server_timeout()回撥。應用必須通過傳送終止狀態的NOTIFY來終止訂閱。
當PJSIP事件包中的presence或refer被使用時,這些包為這個回撥函式提供了預設的實現。這個預設的實現通過帶傳送終止狀態和上次訊息體的NOTIFY請求來終止訂閱。因此如果應用正在使用這些包,它不需要實現這個回撥。
指南
模組管理
初始化事件通知模組並註冊這個模組到指定的Endpoint。這個函式必須在任何其他的事件訂閱函式之前被呼叫。
獲取事件通知模組的例項。
事件包管理
註冊事件包到事件訂閱框架。pkg_mod引數指定了事件包註冊到的模組。event_name指定了事件包的名字,如“presence”(RFC3856)。accept_cnt和accept引數指定描述可接受的媒體型別的陣列。Presence包可接收的媒體型別比如“application/pidf+xml”和”application/xpidf+xml”。
事件訂閱狀態
事件訂閱狀態表示為pjsip_evsub_state中的值,定義在<pjsip-simple/evsub.h>如下:
注意:
- 當狀態達到PJSIP_EVSUB_STATE_TERMINATED時,應用必須釋放與訂閱相關的資源,因為在這之後訂閱將被銷燬並且將沒有任何通
- PJSIP_EVSUB_STATE_UNKNOWN發生當伺服器傳送Subscription-State頭部域無法識別。
事件訂閱會話
PJSIP中的事件通知會話表示為pjsip_evsub結構。下面的函式用來查詢這個結構的屬性。
獲取事件訂閱狀態。
返回代表狀態的字串,或者當狀態是PJSIP_EVSUB_STATE_UNKNOWN時,返回伺服器傳送的狀態字串。
把使用者定義的資料mod_data放進mod_id位置中。
恢復之前設定在mod_id位置的使用者定義資料。
通用的事件訂閱回撥
通用的事件訂閱回撥使用者包含用來從事件框架或者正在使用的事件包接收到通知的函式回撥。
當應用正在使用一個包,應用通常要為這個包註冊這個回撥函式,而不是註冊回撥到事件框架。
通用的事件訂閱回撥函式宣告如下。
每個回撥函式的描述如下:
這個回撥函式當訂閱狀態改變時被呼叫。應用必須準備接收NULL事件和其他型別的除了PJSIP_EVENT_TSX_STATE的事件。
這個回撥函式是可選的,雖然通常應用想要實現這個函式。
這個回調當transaction狀態改變時被呼叫,對於屬於這個訂閱的transaction(即帶有可以建立訂閱和NOTIFYtransaction的請求)。
這個回撥是可選的,因為它只用來提供資訊。
這個回調當到來的SUBSCRIBE(或者任何第一個建立這個訂閱的方法)被接收之後被呼叫。它允許應用指定傳送什麼響應,並且要放什麼附加的頭部域和訊息體。
當應用正在使用PJSIP的事件包像presence或call transfer時,這個回撥是可選的;這個包的預設實現是傳送200(OK)和包含當前訂閱狀態的NOTIFY。
然而,如果應用實現這個回撥(即回撥的值不為NULL),它必須當接收到這個回撥時傳送NOTIFY請求。建議呼叫pjsip_evsub_last_notify()來建立NOTIFY請求,因為這個函式考慮未訂閱的請求和計算合適的過期時間間隔。
這個回調當客戶端/訂閱使用者接收到到來的NOTIFY請求。它允許應用指定傳送什麼響應,並且要放什麼附加的頭部域和訊息體。
這個回撥是可選的,當它沒有實現時,預設的是傳送200(OK)來響應到來的NOTIFY請求。
這個回調當客戶端重新整理這個訂閱被呼叫。
當應用正在使用PJSIP的事件包像presence或call transfer時,這個回撥是可選的;這個事件包將通過傳送時間間隔設定為當前/最近時間間隔的SUBSCRIBE請求來重新整理訂閱。
然而,如果應用實現這個訂閱(即回撥的值不為NULL),它必須自己傳送重新整理訂閱。
這個回調當伺服器在指定的訂閱時間段之後沒有接收到訂閱重新整理時被呼叫。
當應用正在使用PJSIP的事件包像presence或call transfer時,這個回撥是可選的;事件包將傳送NOTIFY來終止這個訂閱。
然而,如果應用實現這個訂閱(即回撥的值不為NULL),它必須自己處理超時,並且建議傳送狀態設定為終止的NOTIFY。
事件訂閱API
建立客戶端訂閱會話,使用dlg作為下層的dialog。event引數指定要使用的事件包,並且這必須在之前就已經註冊到這個事件框架了。option引數當前只被refer訂閱使用,並且其他型別的包這個引數應該設定為0。
建立服務端訂閱會話,,使用dlg作為下層的dialog。rdata引數指定到來的請求。option引數當前只被refer訂閱使用,並且其他型別的包這個引數應該設定為0。
強制銷燬事件訂閱。這個函式應該只在初始化失敗時被呼叫。對於通常情況,訂閱在終止狀態時被自動銷燬。
當dialog沒有其他usage時,這個函式可能銷燬下層的dialog。
呼叫這個函式來建立請求來初始化訂閱,重新整理訂閱,或請求訂閱終止。method引數必須是建立訂閱的方法,像SUBSCRIBE或REFER。如果引數為NULL,那麼SUBSCRIBE將被使用。expires引數將被放入請求的Expires頭部域。如果這個設定為0,這將是取消訂閱的請求。如果這個值是負數,預設的包中定義的過期時間間隔將被使用。
應用接著必須呼叫pjsip_evsub_send_request()來發送訂閱請求。
傳送2XX響應給SUBSCRIBE或REFER請求來接受到來的訂閱請求。st_code引數必須指定2xx。在hdr_list是可選的放入響應中的頭部域列表。
對於通知者,設定訂閱的狀態和建立NOTIFY請求給訂閱者。state_str引數是可選的,它只被用在當這個狀態設定為PJSIP_EVSUB_STATE_UNKNOWN。當訂閱的狀態設定為PJSIP_EVSUB_STATE_TERMINATED時,reason引數必須被設定。應用應該使用RFC3265中定義的值,如“noresource”,“timeout”,“giveup”,”rejected”,”probation”和”deactivated”。PJSIP不翻譯這個原因字串,它將只是把這個字串放到外出的NOTIFY請求中。
注意當NOTIFY請求被髮送時,這個訂閱的狀態將被設定。
對於通知者,建立一個NOTIFY請求來反映當前訂閱的狀態。這個函式通常被包實現者使用,而不是直接被應用使用。
傳送tdata中之前建立的外出請求。
輔助API
獲取和指定的transaction相關的事件訂閱例項。