鄧楊凡
(中國(guó)電子科技集團(tuán)公司第三十研究所,四川 成都 610041)
會(huì)話初始協(xié)議(Session Initialization Protocol,SIP)是由互聯(lián)網(wǎng)工程任務(wù)組(Internet Engineering Task Force,IETF)制定的多媒體通信協(xié)議,具有結(jié)構(gòu)靈活、易于實(shí)現(xiàn)、便于擴(kuò)展等特點(diǎn)[1]。利用SIP 協(xié)議可以實(shí)現(xiàn)用戶定位,檢查終端用戶的位置,用于通信。此外,還可以檢查用戶參與會(huì)話的意愿程度,在呼叫方和被叫方同時(shí)建立會(huì)話參數(shù),實(shí)現(xiàn)會(huì)話管理,包括會(huì)話的傳輸和終止、修改會(huì)話參數(shù)以及請(qǐng)求服務(wù)。
目前主流的開(kāi)源SIP 協(xié)議棧均采用C 語(yǔ)言開(kāi)發(fā),隨著版本不斷迭代,其功能越來(lái)越強(qiáng)大,同時(shí)也存在一定不便之處,如功能、結(jié)構(gòu)越來(lái)越復(fù)雜,初學(xué)者的學(xué)習(xí)成本增加。同時(shí),由于C 語(yǔ)言在各平臺(tái)版本庫(kù)之間存在差別,針對(duì)每個(gè)平臺(tái)需要進(jìn)行獨(dú)立的交叉編譯。文章基于Go 語(yǔ)言設(shè)計(jì)并實(shí)現(xiàn)了一種可以快速部署實(shí)現(xiàn)的SIP協(xié)議棧,由于采用原生Go語(yǔ)言進(jìn)行開(kāi)發(fā),不采用任何C 語(yǔ)言的庫(kù),跨平臺(tái)部署時(shí)僅需修改幾行配置文件即可,無(wú)須安裝各種編譯器和庫(kù)文件。
SIP 是一種應(yīng)用層協(xié)議,它獨(dú)立于傳輸層和物理層,可以通過(guò)不同的傳輸協(xié)議進(jìn)行傳輸,如用戶數(shù)據(jù)報(bào)協(xié)議(User Datagram Protocol,UDP)、傳輸控制協(xié)議(Transmission Control Protocol,TCP)以及安全傳輸層(Transport Layer Security,TLS)協(xié)議。其中,UDP 是目前最常用的協(xié)議。
SIP 協(xié)議采用松耦合的分層關(guān)系結(jié)構(gòu),如圖1所示。

圖1 SIP 協(xié)議棧層級(jí)結(jié)構(gòu)
SIP 協(xié)議棧最底層是語(yǔ)法編碼層,該層的編碼方式是增強(qiáng)型的巴克斯范式(Augmented Backus-Naur Form,ABNF)。第2 層是傳輸層,它定義了客戶端與服務(wù)端之間的請(qǐng)求與響應(yīng)。第3 層是事務(wù)層,主要完成會(huì)話事務(wù)的管理。事務(wù)層采用有限狀態(tài)機(jī)機(jī)制,包括客戶端事務(wù)和服務(wù)端事務(wù),一個(gè)事務(wù)由客戶端事務(wù)發(fā)送給服務(wù)端事務(wù)的請(qǐng)求和服務(wù)端事務(wù)發(fā)送對(duì)應(yīng)該請(qǐng)求的響應(yīng)組成。SIP 協(xié)議棧的最上層是事務(wù)用戶層,除了無(wú)狀態(tài)的代理,每個(gè)SIP 實(shí)體都是事務(wù)用戶。當(dāng)一個(gè)事務(wù)用戶希望發(fā)送請(qǐng)求時(shí),就創(chuàng)建一個(gè)客服端事務(wù)實(shí)例用于發(fā)送此請(qǐng)求。
SIP 消息是基于文本的消息,采用UTF-8 字符集。SIP 將消息分為請(qǐng)求和響應(yīng)2 種類(lèi)型,請(qǐng)求消息和響應(yīng)消息都采用RFC2822 規(guī)定的通用消息格式,包括起始行、一個(gè)或多個(gè)消息頭、一個(gè)空行以及一個(gè)可選消息體。SIP 消息的起始行分為請(qǐng)求行(Request-Line)和狀態(tài)行(Status-Line)。其中,請(qǐng)求行是請(qǐng)求消息的起始行,狀態(tài)行是響應(yīng)消息的起始行。請(qǐng)求消息包含請(qǐng)求行、消息頭、空行以及消息體,響應(yīng)消息包括狀態(tài)行、消息頭、空行以及消息體。RFC3261 規(guī)定的6 種請(qǐng)求消息如表1 所示。

表1 SIP 請(qǐng)求消息
SIP 響應(yīng)消息的第一行由狀態(tài)碼構(gòu)成,表示服務(wù)端的響應(yīng)狀態(tài)。RFC3261 規(guī)定了nXX(n從1 到6)的狀態(tài)碼定義,XX 用于對(duì)響應(yīng)狀態(tài)的進(jìn)一步描述。相關(guān)的響應(yīng)狀態(tài)編碼及其含義如表2 所示。

表2 SIP 響應(yīng)消息類(lèi)型
根據(jù)對(duì)SIP 層級(jí)結(jié)構(gòu)和消息結(jié)構(gòu)的分析,結(jié)合工程實(shí)際需求,按照邏輯功能將SIP 協(xié)議棧分為不同的模塊,如圖2 所示。

圖2 SIP 協(xié)議棧模塊劃分
傳輸模塊邏輯上處于SIP 協(xié)議棧的最底層,主要實(shí)現(xiàn)信令傳輸和媒體傳輸。傳輸模塊能夠收發(fā)SIP信令和實(shí)時(shí)傳輸協(xié)議(Real Time Transport Protocol,RTP)流媒體數(shù)據(jù),保證SIP 消息的完整性和正確的消息順序,根據(jù)消息類(lèi)型進(jìn)行分發(fā)及錯(cuò)誤處理[2]。
傳輸模塊將編解碼模塊組包后的有效請(qǐng)求載荷通過(guò)特定端口進(jìn)行發(fā)送,并接收相應(yīng)的響應(yīng)消息,提取有效載荷后交由編解碼模塊進(jìn)行解析。SIP 消息的傳輸流程如圖3 所示。

圖3 消息傳輸流程
SIP 可以采用UDP、TCP 以及TLS 等傳輸協(xié)議作為傳輸承載。文章設(shè)計(jì)的SIP 協(xié)議棧采用UDP 作為傳輸協(xié)議,Go 語(yǔ)言原生提供net 包實(shí)現(xiàn)UDP 通信。以主叫方為例,上層用戶接口調(diào)用模塊發(fā)起一次呼叫時(shí),即客戶端向服務(wù)端發(fā)送“Invite”消息前,程序使用net 包下的Dial 函數(shù)發(fā)起與SIP 服務(wù)端的連接,收到連接響應(yīng)后,調(diào)用net 包中的“Listen Packet”創(chuàng)建一個(gè)UDP 服務(wù),并提取返回結(jié)果中的UDP 服務(wù)端口值,將其作為Invite 消息中“media.port”參數(shù)的值,然后通過(guò)建立的UDP 通道發(fā)送該消息。服務(wù)端用于RTP 傳輸?shù)腢DP 服務(wù)端口的創(chuàng)建與之類(lèi)似。UDP 傳輸建立的相關(guān)流程如圖4 所示。

圖4 UDP 傳輸?shù)湫土鞒?/p>
圖4 中,①和②分別是SIP 協(xié)議棧作為客戶端與服務(wù)端進(jìn)行RTP 傳輸協(xié)商并分配端口等待被叫端接聽(tīng)后進(jìn)行RTP 流媒體傳輸?shù)倪^(guò)程。
編解碼模塊提供針對(duì)SIP消息的解析與編碼能力。SIP 協(xié)議類(lèi)似于超文本傳輸協(xié)議(Hypertext Transfer Protocol,HTTP)協(xié)議,都是按行解析。SIP 消息的第一行標(biāo)識(shí)該消息的類(lèi)型、消息目的地及協(xié)議版本。從第二行開(kāi)始,每一行的格式為“字段名:空格 字段值”(冒號(hào)后固定有一個(gè)或多個(gè)空格)[3]。
根據(jù)以上分析,為了驗(yàn)證平臺(tái)的實(shí)際情況,設(shè)計(jì)一套多級(jí)流水線的文本消息處理流程。第1 級(jí)以空行為分割符,將消息分割為消息頭(Message Header)和消息體(Message Body)。第2 級(jí)解析消息頭與消息體中的回車(chē)換行符,以此標(biāo)志作為分割符,將消息分割為多條字段。第3 級(jí)以“:”作為分割符,獲取字段名稱(chēng)。第4 級(jí)以“:”“@”等標(biāo)志作為分割符,過(guò)濾出字段的參數(shù)值。4 級(jí)流水線結(jié)構(gòu)的解碼模型如圖5 所示。

圖5 4 級(jí)流水線結(jié)構(gòu)的解碼模型
針對(duì)SIP(包含SDP 和RTP)的編碼可以看作解析的逆向過(guò)程,編碼模塊根據(jù)接收消息的內(nèi)容,結(jié)合當(dāng)前網(wǎng)絡(luò)狀態(tài),按照SIP 消息的標(biāo)準(zhǔn)生成消息頭和消息體,組合成相應(yīng)的載荷。文章設(shè)計(jì)的協(xié)議棧支持INVITE、BYE、RING 以及ACK 等多種消息的生成和解析,其消息編解碼模塊根據(jù)消息頭判斷消息類(lèi)型,同時(shí)采用多級(jí)流水的模式進(jìn)行解析。
實(shí)現(xiàn)上述編解碼時(shí),接收消息為16 進(jìn)制的數(shù)組,此時(shí)使用Go 語(yǔ)言原生包“bytes”中的“splite”函數(shù),利用“0x0d,0x0a”作為分割關(guān)鍵數(shù)組,將收到的消息進(jìn)行分割,分割后的消息為一段包含關(guān)鍵字的字符串。此外,分割后第一段為消息類(lèi)型或“Status Line”的值。獲取到接收消息的類(lèi)型后,調(diào)用相應(yīng)的函數(shù)對(duì)相關(guān)消息進(jìn)行解析,獲取消息中“Call-ID”“From”“To”等關(guān)鍵參數(shù)的值進(jìn)行處理存儲(chǔ)。
消息編碼利用SIP 協(xié)議文本性質(zhì)的特點(diǎn),采用關(guān)鍵字填充模式進(jìn)行消息組包,通過(guò)對(duì)關(guān)鍵字的填充來(lái)生成消息載荷。SIP 消息組包的典型編程實(shí)現(xiàn)如圖6所示。

圖6 SIP 消息組包的編碼實(shí)現(xiàn)
數(shù)據(jù)處理模塊接收編解碼模塊解析的數(shù)據(jù)并對(duì)其進(jìn)行處理,以供編解碼模塊生成響應(yīng)消息時(shí)使用。數(shù)據(jù)處理器根據(jù)編解碼模塊解析出的消息類(lèi)型,調(diào)用對(duì)應(yīng)的處理函數(shù)對(duì)對(duì)方支持的媒體格式和本協(xié)議棧支持的媒體格式進(jìn)行對(duì)比處理,選擇其中的交集作為對(duì)端通信的媒體格式。
SIP 消息通過(guò)文本形式進(jìn)行信息交互,收發(fā)雙方每次交付都會(huì)產(chǎn)生大量參數(shù),其中大部分參數(shù)在整個(gè)會(huì)話過(guò)程中都要使用。在支持多線程的情況下,相關(guān)參數(shù)的存儲(chǔ)量大幅增加,這就要求協(xié)議棧能夠?qū)崟r(shí)創(chuàng)建一個(gè)存儲(chǔ)空間來(lái)存儲(chǔ)上述數(shù)據(jù),同時(shí)相關(guān)函數(shù)可以方便地對(duì)其訪問(wèn)[4]。
結(jié)合Go 語(yǔ)言的特點(diǎn),會(huì)話中的數(shù)據(jù)采用結(jié)構(gòu)體的形式進(jìn)行存儲(chǔ)。通過(guò)HashMap 的形式,采用一次會(huì)話中的唯一值“Call-ID”作為鍵值進(jìn)行檢索。會(huì)話開(kāi)始時(shí)創(chuàng)建一條索引關(guān)系,會(huì)話結(jié)束或者會(huì)話中止時(shí)則刪除。中間可采用指針形式對(duì)索引中的值進(jìn)行修改。數(shù)據(jù)存儲(chǔ)模塊除了存儲(chǔ)一次會(huì)話中的SIP 參數(shù)信息,還存儲(chǔ)一次SIP 會(huì)話中的會(huì)話狀態(tài)信息。狀態(tài)信息采用SIP 消息中的“Status Code”作為主要參數(shù),同時(shí)加入部分自定義狀態(tài)。例如,協(xié)議棧作為客服端發(fā)送INVITE 消息后,則將狀態(tài)設(shè)置為“Wait-100”,同時(shí)設(shè)置一個(gè)計(jì)時(shí)器,開(kāi)始等待“100 Trying”(INVITE消息的響應(yīng)消息)。
數(shù)據(jù)處理存儲(chǔ)采用HashMap 的形式實(shí)現(xiàn),Go 語(yǔ)言提供了“sync.map”庫(kù)用作并發(fā)環(huán)境下實(shí)現(xiàn)效率高且線程安全的map。map 采用“Call ID”作為鍵值,索引內(nèi)容以結(jié)構(gòu)體的形式存在。存儲(chǔ)呼叫信息的結(jié)構(gòu)體如圖7 所示。

圖7 存儲(chǔ)呼叫信息的結(jié)構(gòu)體
存儲(chǔ)數(shù)據(jù)的結(jié)構(gòu)體采用值類(lèi)型的形式進(jìn)行更新,每收到一條消息,就執(zhí)行一次判斷。如果當(dāng)前消息不是“BYE”,就提取“Call-ID”值,并同步更新上述的“sync.map”列表;如果收到的消息為“BYE”消息,就形成相應(yīng)的log,并刪除當(dāng)前消息“Call-ID”對(duì)應(yīng)的結(jié)構(gòu)體。
有限狀態(tài)機(jī)是整個(gè)協(xié)議棧的控制核心,該模塊提供對(duì)協(xié)議棧運(yùn)行的總體調(diào)度能力[5]。該模塊通過(guò)存儲(chǔ)模塊中存儲(chǔ)的“Status”作為狀態(tài)轉(zhuǎn)換的狀態(tài)值,通過(guò)一次會(huì)話中的唯一鍵值“Call ID”作為索引,獲取每次消息的“Status Code”或關(guān)鍵字(如BYE、ACK 等)作為狀態(tài)變換的更新。典型的有限狀態(tài)機(jī)結(jié)構(gòu)如圖8 所示。

圖8 有限狀態(tài)機(jī)結(jié)構(gòu)
有限狀態(tài)機(jī)的初始狀態(tài)為WAITING,若無(wú)主叫請(qǐng)求或收到被叫請(qǐng)求,則保持此狀態(tài)。此時(shí)根據(jù)主動(dòng)發(fā)出會(huì)話請(qǐng)求和收到會(huì)話請(qǐng)求,狀態(tài)機(jī)會(huì)有2 種狀態(tài)走向。
若由SIP 協(xié)議棧發(fā)起會(huì)話,則狀態(tài)機(jī)由WAITING轉(zhuǎn)為INVITE,并開(kāi)啟一個(gè)定時(shí)器。若在一定時(shí)間內(nèi)沒(méi)有收到響應(yīng)消息,則重新跳轉(zhuǎn)至WAITING 狀態(tài),并拋出一個(gè)異常記錄;若在一定時(shí)間內(nèi)收到“100 Trying”回復(fù),則進(jìn)入下一個(gè)狀態(tài),等待被叫方回復(fù)“180 Ring”響應(yīng)消息;若收到“180 Ring”消息后,狀態(tài)機(jī)轉(zhuǎn)為RINGING 狀態(tài)并等待200 OK 消息,狀態(tài)機(jī)轉(zhuǎn)為ACK 狀態(tài)并向?qū)Χ税l(fā)送“ACK”響應(yīng),同時(shí)打開(kāi)會(huì)話并啟動(dòng)RTP 多媒體傳輸;若收不到相應(yīng)的響應(yīng),則重新轉(zhuǎn)到WAITING 狀態(tài),重新等待會(huì)話開(kāi)始。
若協(xié)議棧收到會(huì)話請(qǐng)求時(shí),狀態(tài)機(jī)由WAITING轉(zhuǎn)為RINGING。此時(shí)協(xié)議棧開(kāi)啟定時(shí)機(jī)制,等待用戶接收或中止該請(qǐng)求。若目標(biāo)用戶接受請(qǐng)求,則將狀態(tài)機(jī)置為OK 狀態(tài),回復(fù)“200 OK”響應(yīng),等待對(duì)方最終請(qǐng)求為ACK 后,狀態(tài)機(jī)由OK 狀態(tài)轉(zhuǎn)換為ACK,開(kāi)啟會(huì)話和RTP 媒體傳輸;若用戶放棄該請(qǐng)求,則將狀態(tài)重置為WAITING 狀態(tài),等待新的呼叫。
終止會(huì)話時(shí),請(qǐng)求方或響應(yīng)方發(fā)送BYE 請(qǐng)求,等待200 OK 消息,此時(shí)將狀態(tài)機(jī)由ACK 置為BYE狀態(tài)。當(dāng)一方接受BYE 請(qǐng)求后,狀態(tài)機(jī)重新復(fù)位到WAITING 狀態(tài),同時(shí)發(fā)送200 OK 響應(yīng)并釋放會(huì)話,此時(shí)一個(gè)會(huì)話結(jié)束。
采用端到端的形式模擬一次SIP 呼叫與語(yǔ)音通話過(guò)程,利用虛擬機(jī)搭建服務(wù)端與客戶端。服務(wù)端系統(tǒng)采用Linphone,客戶端采用文章設(shè)計(jì)的協(xié)議棧系統(tǒng),讀取已經(jīng)生成的WAV 文件作為傳輸話音。實(shí)驗(yàn)簡(jiǎn)易部署結(jié)構(gòu)如圖9 所示。

圖9 實(shí)驗(yàn)簡(jiǎn)易部署結(jié)構(gòu)
圖9 中,主叫端為基于SIP 協(xié)議棧開(kāi)發(fā)的SIP 客戶端軟件,其IP 地址為“192.168.46.147”,分配的主叫號(hào)碼為“22222222222”;被叫端采用Linphone軟件,其IP 地址為“192.168.46.142”,分配的被叫號(hào)碼為“11111111111”。實(shí)驗(yàn)中SIP 信令抓包結(jié)果如圖10 所示。

圖10 實(shí)驗(yàn)中SIP 信令抓包結(jié)果
由圖10 可知,基于SIP 協(xié)議棧開(kāi)發(fā)的SIP 客戶端成功發(fā)起并處理了一次呼叫的全部信令流程。本次呼叫中傳輸?shù)腞TP 數(shù)據(jù)流如圖11 所示。

圖11 實(shí)驗(yàn)中傳輸?shù)腞TP 數(shù)據(jù)流
文章基于Go 語(yǔ)言設(shè)計(jì)并實(shí)現(xiàn)了一種輕型的SIP協(xié)議棧,在跨平臺(tái)部署時(shí)無(wú)須安裝各種編譯器和庫(kù)文件,應(yīng)用簡(jiǎn)單便捷。通過(guò)在目標(biāo)主機(jī)上進(jìn)行實(shí)驗(yàn)驗(yàn)證,該協(xié)議棧具備主動(dòng)發(fā)起呼叫并處理相關(guān)RTP 媒體流的能力,具有較好的應(yīng)用價(jià)值。