gnugk系統設計

作者姓名:黃志偉 高清视频会议MCU

gnugk系統設計


採用多執行緒架構,以平行處理各種請求與不同的工作。程式啟始時,完成初始化和必要的檢查之後,隨即分支出數個執行緒,處理不同的請求。主執行緒則成為RasServer,產生RasListener 監聽RAS的請求。在收到RAS請求後,RasServer 利用物件工廠產生對應的RasMsg物件,並且檢查是否有某個RAS處理器在等待(攔截)該訊息。若有的話就將該物件交由該處理器負責;否則,檢查該訊息是否與正在處理中的訊息重複,若是的話將它交給正在處理重複訊息的執行緒處理;否則,將該物件交給另一(新的)執行緒處理。該執行緒將呼叫RasMsg物件的Process虛擬函式來處理該訊息。TCPServer 執行緒處理所有TCP 連線請求,並將之分配到其餘執行緒處理;GkStatus執行緒處理狀態埠(status port)的輸入,分析是否為合法指令並做適當處理;在信號路由模式下,還會產生一個至數個ProxyHandler執行緒處理信號(call signalling)的改寫與轉送;HouseKeeping執行緒則檢查註冊和通話是否逾時,以及處理一些資源的回收與清除。

解決多執行緒衝突問題

我重新設計了管理註冊和通話的表格,使其能夠在多執行緒下運作無誤。外界需透過smarter pointer來存取表格中的記錄。只要有smarter pointer指向某一記錄,該記錄就不會被意外刪除。Smarter pointer以reference counting技術實作。只要counter大於零,就表示該記錄仍在使用中,負責cleanup的執行緒便不會刪除該筆記錄。

避免fork過多的執行緒

在路由模式(routed mode)下gatekeeper處理Q.931 訊息的轉送需要兩個TCPsockets。原本OpenH323GK 會開兩個執行緒分別去讀取兩個sockets,這表示一個通話至少耗用兩個執行緒。由於一個行程能fork的執行緒有上限,這就限制了通話的總數。我設計了新的ProxyThread 類別以解決此問題。所有的sockets會被分配到數個Handler中處理,每個Handler以select系統呼叫偵測那一個socket有資料可讀,有資料才去讀取並處理。如此每一個Handler thread都可以處理多個通話。因此執行緒數目上限不會再限制了通話總數。Handlerthread的數目可由使用者動態指定。

Gatekeeper階層架構

儘管在H.323規格定義上,gatekeeper並非一個端點。但我發現在實務上若將一個gatekeeper管轄的區域(zone)視為一個端點註冊至別的gatekeeper會非常的便利。於是加上了GkClient模組,可以將GnuGK模擬成gateway或terminal註冊至上層Gatekeeper。子GK和父GK可相互交換路由,並且做號碼的改寫,使得每一個註冊在子GK的端點都像直接註冊在父GK一般。如此很容易建立gatekeeper的階層架構,易於管理與擴充。

彈性的執行緒管理

GnuGK運用多執行緒以簡化程式流程並增進執行效率。然而最大的挑戰在於如何分配不同的工作至不同的執行緒。在GnuGK 2.0 版(含)之前,每一種工作定義在不同的執行緒類別中。這種作法雖然簡單,但問題是每增加一種工作都必須增加對應的執行緒類別。而且或者必須事先fork執行緒以等待事件的發生,導致浪費資源,或者在事件發生時才fork執行緒,而使得效率不彰。為解決此問題我在GnuGK 2.2版中引入了新的執行緒管理模式。我們只定義一種執行緒類別,稱為Worker,並受稱為Agent的singleton物件所管理。所有工作被封裝在稱為Job的類別中。若要增加一種新的工作,只要衍生自Job類別並改寫Run虛擬函式即可。要將該工作交由新的執行緒執行時,只要呼叫Execute函式,Agent會找出一個空閒的Worker的執行該類別定義的虛擬函式Run。若是沒有空閒的Worker,Agent會僱用(fork)新的Worker執行緒執行該工作。反正,當一個Worker失業超過一段時間,它便會被解僱(結束該執行緒)。利用此模型,系統的負載和Worker執行緒數目將會達到平衡。

RAS Type Traits

Gatekeeper與端點溝通的訊息稱上RAS。H.323 規格書上定義了32 種RAS訊息,格式大同小異,理論上程式的處理方式也相近。但由於每種訊息的型態和對應的屬性不同,難以用共同的程式碼處理。我引入了一種稱為type traits的C++技術,將每種RAS訊息的相關屬性封裝在一個叫RasInfo<RAS>的類別中。這樣的類別稱為trait。有了這樣的輔助類別就很容易寫出泛用的程式碼來處理不同的RAS訊息。例如下列程式碼用來建立任一個RAS請求對應的回絕(reject)訊息物件:

template<class RAS>
inline H225_RasMessage & RasPDU<RAS>::BuildReject(unsigned reason)
{
typedef typename RasInfo<RAS>::RejectTag RejectTag;
typedef typename RasInfo<RAS>::RejectType RejectType;
m_msg->m_replyRAS.SetTag(RejectTag());
RejectType & reject = m_msg->m_replyRAS;
reject.m_requestSeqNum = request.m_requestSeqNum;
reject.m_rejectReason.SetTag(reason);
...
}


NAT解決方案

NAT如今已是被廣泛使用的網路技術,用來解決IP不足的問題。然而H.323設計上並非NAT friendly,意即一般簡單的網址轉換無法適用於H.323協定。欲解決此問題一般有兩種作法,一是修改NAT使其具備H.323的轉換能力。另一是修改通訊協定使其能夠穿越NAT。GnuGK中我完整的實作了兩種技術。GnuGK可轉送Q.931、H.245、RTP/RTCP以及T.120的訊息。在此模式下通訊雙方沒有任何直接的流量通過,因此又稱為H.323 Proxy。若將H.323 Proxy安裝在NAT上即可解決H.323無法穿過NAT的問題。不過這種作法需要修改NAT本身,在實務上有時不可行。因此我們研發了新的技術,在不修改NAT的情況下,僅要在gatekeeper和endpoint端略作修改即可。這個技術包含了幾個重點:

1. 偵測是否NAT端點:GnuGK會比較註冊訊息(RRQ)的來源IP和RRQ本身包含的callSignalAddress是否相同。若不同的話即表示端點位於NAT之後。GnuGK 會針對此種端點,將其訊息中包含的IP資訊轉換為NAT IP。

2. RTP/RTCP 的穿越:H.323 使用RTP/RTCP 來傳送聲音或影像資料,而RTP/RTCP使用UDP。由於一般NAT對UDP的穿越有條件限制。假設某一端點的IP A,從port B送出封包,經由NAT轉換為IP C,port D,而目的地是IP X,port Y。一般的條件(port restricted NAT)會要求回送的封包,來源必須是IP X,port Y,送至IP C,port D。如此NAT才會轉送回IP A,port B。GnuGK在轉送RTP/RTCP封包時會設法滿足該條件,使得RTP/RTCP封包可順利穿越NAT。利用以上兩個技術,已可使一般位於NAT之後的端點直接註冊到GnuGK並對外撥打電話。然而欲接電話的話,這樣仍不夠。因為H.323 使用TCP建立通訊連線,但一般NAT無法處理由外至內的TCP請求。因此需要修改
端點,要加上以下兩種技術:

3. Q.931 Penetration:位於NAT之後的端點在註冊成功後,先發起由內至外的TCP連線至GnuGK,稱為NAT Connection。當其它端點要呼叫該端點時,GnuGK會利用已存在的NAT Connection傳送Q.931 Setup至該端點。

4. H.245 Reverting:位於NAT之後的端點若送出h245Address以建立H.245控制通道,GnuGK在收到後,會改發Q.931 Facility startH245的訊息給該端點。該端點收到後要改從內部向外建立H.245的通道。由於TCP連線的方向跟原本正常的程序剛好相反,因此稱為反轉(reverting)。
 

你可能感兴趣的:(gnugk系統設計)