Freeswitch架構

源地址連接:http://blog.csdn.net/perfectpdl/article/details/6039967

從來章開始,我們正式開始我們的 FreeSWITCH 之旅。今後我們不再用單獨的章節來講述VoIP中的其它要素和概念,而是在用到時穿插於各個章節之中。

總體結構

FreeSWITCH 由一個穩定的核心及外圍模塊組成,下圖來自 FreeSWITCH Wiki:

FreeSWITCH 使用線程模型來處理併發請求,每個連接都在單獨的線程中進行處理。這不僅能提供最大強度的併發,更重要的是,即使某路電話發生問題,也隻影響到它所在的線程,而不會影響到其它電話。FreeSWITCH 的核心非常短小精悍,這也是保持穩定的關鍵。所有其它功能都在外圍的模塊中。模塊是可以動態加載(以及卸載)的,在實際應用中可以只加載用到的模塊。外圍模塊通過核心提供的 Public API 與核心進行通信,而核心則通過回調機制執行外圍模塊中的代碼。

核心

FS Core 是 FreeSWITCH 的核心,它包含了關鍵的數據結構和複雜的代碼,但這些代碼只出現在覈心中,並保持了最大限度的重用。外圍模塊只能通過 API 調用核心的功能,因而核心運行在一個受保護的環境中,核心代碼都經過精心的編碼和嚴格的測試,最大限度地保持了系統整體的穩定。

核心代碼保持了最高度的抽象,因而它可以調用不同功能,不同協議的模塊。同時,良好的 API 也使得編寫不同的外圍模塊非常容易。

數據庫

FreeSWITCH 的核心除了使用內部的隊列、哈希表存儲數據外,也使用外部的 SQL 數據庫存儲數據。當前,系統的核心數據庫使用 SQLite,默認的存儲位置是 db/core.db 。 使用外部數據庫的好處是--查詢數據不用鎖定內存數據結構,這不僅能提供性能,而且降低了死鎖的風險,保證了系統穩定。命令 show calls、show channels 等都是直接從數據庫中讀取內容並顯示的。由於 SQLite 會進行讀鎖定,因此不建議直接讀取核心數據庫。

系統對數據庫操作做了優化,在高併發狀態時,核心會盡量將幾百條 SQL 一齊執行,這大大提高了性能。但在低併發的狀態下執行顯得稍微有點慢,如一個 channel 已經建立了,但還不能在 show channels 中顯示;或者,一個 channel 已經 destroy 了,還顯示在 show channels 中。但由於這些數據只用於查詢,而不用於決策,所以一般沒什麼問題。

除核心數據庫外,系統也支持使用 ODBC 方式連接其它數據庫,如 PostgreSQL、MySQL等。某些模塊,如 mod_sofia、mod_fifo等都有自己的數據庫(表)。如果在 *nix 類系統上使用 ODBC,需要安裝 UnixODBC,並進行正確的配置,如果編譯安裝的話還需要開發包 unixodbc-devel(CentOS) 或 unixodbc-dev(Debian/Ubuntu)。由於 PostgreSQL、MySQL 等都是 Client-Server 的結構,因此,外部程序可以直接查詢數據(但需要清楚數據的準確性,可能會比 FreeSWITCH 核心中的數據有所滯後)。

模塊

FreeSWITCH 主要分爲以下幾個部分:

終點

End Points 是終結 FreeSWITCH 的地方,也就是說再往外走就超出 FreeSWITCH 的控制了。它主要包含了不同呼叫控制協議的接口,如 SIP, TDM 硬件,H323 以及 Google Talk 等。這使得 FreeSWITCH 可以與衆多不同的電話系統進行通信。如,可以使用 mod_skypopen 與 Skype 網絡進行通信。另外,前面也講過,它還可以通過 portaudio 驅動本地聲卡,用作一個軟電話。

撥號計劃

Dialplan 主要是爲了查找電話路由,主要的是 XML 描述的,但它也支持 Asterisk 格式的配置文件。另外它也持 ENUM 查詢。

XML 接口

XML Interface 支持多種獲取 XML 配置的方式,它可以是本地的配置文件,或從數據庫中讀取,甚至是一個能動態返回 XML 的遠程 HTTP 服務器。

編解碼器

FreeSWITCH 支持最廣泛的 Codec,除了大多數 VoIP 系統支持的 G711、G722、G729、GSM 外,它還支持 iLBC,BV16/32、SILK、CELT等。它可以同時橋接不同採樣頻率的電話,以及電話會議等。

語音識別

支持語音自動識別(ASR)及文本-語音轉換(TTS)。

文件格式

支持不同的聲音文件格式,如 wav,mp3等。

日誌

日誌可以寫到控制檯、日誌文件、系統日誌(syslog)以及遠程的日誌服務器。

嵌入式語言

通過 swig 包裝支持多種腳本語本語言控制呼叫流程,如 Lua、Javascript、Perl等。

事件套接字

使用 Event Socket 可以使用任何其它語言通過 Socket 方式控制呼叫流程、擴展 FreeSWITCH 功能。

目錄結構

在 *nix 類系統上,FreeSWITCH 默認的安裝位置是 /usr/local/freeswitch,在 Windows 上可能是 C:/freeswitch,目錄結構大致相同。

bin         可執行程序
db          系統數據庫(sqlite),FreeSWITCH 把呼叫信息存放到數據庫裏以便在查詢時無需對核心數據結構加鎖
htdocs      HTTP Web srver 根目錄
lib         庫文件
mod         可加載模塊
run         運行目錄,存放 PID
sounds      聲音文件,使用 playback() 時默認的尋找路徑
grammar     語法
include     頭文件
log         日誌,CDR 等
recordings  錄音,使用 record() 時默認的存放路徑
scripts     嵌入式語言寫的腳本,如使用 lua()、luarun()、jsrun 等默認尋找的路徑
storage     語言留言(Voicemail)的錄音
conf        配置文件,詳見下節

配置文件

配置文件由許多 XML 文件組成。在系統裝載時,XML解析器會將所有XML文件組織在一起,並讀入內存,稱爲XML註冊表。這種設計的好處在於其非常高的可擴展性。由於XML文檔本身非常適合描述複雜的數據結構,在 FreeSWITCH 中 就可以非常靈活的使用這些數據。並且,外部應用程序也可以很簡單地生成XML,FreeSWITCH 在需要時可以動態的裝載這些 XML。另外,系統還允許在某些 XML 節點上安裝回調程序(函數),當這些節點的數據變化時,系統便自動調用這些回調程序。

使用 XML 唯一的不足就是手工編輯這些 XML 比較困難,但正如其作者所言,他絕對不是 XML 的粉絲,但這一缺點與它所帶來的好處相比是微不足道的。而且,將來也許會有圖形化的配置工具,到時候只所高級用戶會去看這些XML了。

目錄結構

配置文件的的目錄結構如下(其中結尾有 “/” 的爲目錄):

autoload_configs/
dialplan/
directory/
extensions.conf
freeswitch.xml
fur_elise.ttml
jingle_profiles/
lang/
mime.types
notify-voicemail.tpl
sip_profiles/
tetris.ttml
vars.xml
voicemail.tpl
web-vm.tpl

其中最重要的是 freeswitch.xml,就是它將所有配置文件“粘”到一起。只要有一點 XML 知識,這些配置是很容易看懂的。其中,X-PRE-PROCESS標籤稱爲預處理命令,它用來設置一些變量和裝入其它配置文件。在 XML 加載階段,FreeSWITCH 的 XML 解析器會先將預處理命令進行展開,生成一個大的 XML 文件 log/freeswitch.xml.fsxml。該文件是一個內存鏡像,用戶不應該手工編輯它。但它對調試非常有用,假設你不慎弄錯了某個標籤,又不知道它在哪個地方,FreeSWITCH 在加載時就報 XML 的某一行出錯,在該文件中就行容易找到。

整個XML文件分爲幾個重要的部分:configuration (配置)、dialplan (撥號計劃)、directory(用戶目錄)及phrase(分詞)。每一部分又分別裝入不同的 XML。

小知識:XML
XML由標籤(Tag)和屬性構成。<tag> 和 </tag>組成一對標籤,如果該標籤有相關屬性,剛以
<tag attr="value"></tag> 形式指定。有些標籤無須配對,則必須以 “/>”關閉該標籤定義,
如<other/_tag attr="value"/>。

freeswitch.xml

<?xml version="1.0"?>
<document type="freeswitch/xml">
    <!-- #comment 這是一個配置文件,本行是註釋 -->

    <X-PRE-PROCESS cmd="include" data="vars.xml"/>

    <section name="configuration" description="Various Configuration">
        <X-PRE-PROCESS cmd="include" data="autoload_configs/*.xml"/>
    </section>
</document>

上面是一個精減了的 freeswitch.xml。它的根是 document,在 document 中,有許多 section,每個 section 都對應一部分功能。其中有兩個 X-PRE-PROCESS 預處理指令,它們的作用是將 data 參數指定的文件包含(include)到本文件中來。由於它是一個預處理指令,FreeSWITCH 在加載階段只對其進行簡單替換,並不進行語法分析,因此,對它進行註釋是沒有效果的,這是一個新手常犯的錯誤。假設 vars.xml 的內容如下,它是一個合法的 XML:

<!-- this is vars.xml -->
<var>xxxxx</var>

若你在調試階段想把一條 X-PRE-PROCESS 指令註釋掉:

<!-- <X-PRE-PROCESS cmd="include" data="vars.xml"/> -->

當 FreeSWITCH 預處理時,還沒有到達 XML 解析階段,也就是說它還不認識 XML 註釋語法,而僅會機械地將預處理指令替換爲 vars.xml 裏的內容:

<!-- <!-- this is vars.xml -->
<var>xxxxx</var> -->                                                  

由於 XML 的註釋不能嵌套,因此便產生錯誤的XML。解決辦法是破壞掉 X-PRE-PROCESS 的定義,如我常用下面兩種方法:

<xX-PRE-PROCESS cmd="include" data="vars.xml"/>
<XPRE-PROCESS cmd="include" data="vars.xml"/>

由於 FreeSWITCH 不認識 xX-PRE-PROCESS 及 XPRE-PROCESS,因此它會忽略掉該行,相當於註釋掉了。

vars.xml

vars.xml 主要通過 X-PRE-PROCESS 指令定義了一些全局變量。全局變量以 $${var} 表示,臨時變量以 ${var} 表示。有些變量是系統在運行時自動獲取的,如默認情況下 $${base_dir}=/usr/local/freeswitch, $${local_ip_v4}=你機器的IP地址等。

autoload_configs 目錄

autoload_configs目錄下面的各種配置文件會在系統啓動時裝入。一般來說都是模塊級的配置文件,每個模塊對應一個。文件名一般以 模塊名.conf.xxml 方式命名。其中 modules.conf.xml 決定了 FreeSWITCH 啓動時自動加載哪些模塊。

dialplan 目錄

定義 XML 撥號計劃,我們會有專門的章節講解撥號計劃。

directory 目錄

它裏面的配置文本決定了 FreeSWITCH 作爲註冊服務器時哪些用戶可以註冊上來。FreeSWITCH 支持多個域(Domain),每個域可以寫到一個 XML 文件裏。默認的配置包括一個 default.xml,裏面定義了 1000 ~ 1019 一共 20 個用戶。

sip_profiles

它定義了 SIP 配置文件,實際上它是由 mod_sofia 模塊在 autoload_configs/sofia.conf.xml 中加載的。但由於它本身比較複雜又是核心的功能,因此單列了一個目錄。我們將會在後面加以詳細解釋。

XML 用戶目錄

XML 用戶目錄決定了哪些用戶可以註冊到 FreeSWITCH 上。當然,SIP 並不要求一定要註冊纔可以打電話,但是用戶認證仍需要在用戶目錄中配置。

用戶目錄的默認配置文件在 conf/directory/,系統自帶的配置文件爲 default.xml(其中 dial-string 一行由於排版要求人工換行,實際上不應該有換行):

<domain name="$${domain}">
  <params>
    <param name="dial-string" value="{presence_id=${dialed_user}@${dialed_domain}}
        ${sofia_contact(${dialed_user}@${dialed_domain})}"/>
  </params>

  <variables>
    <variable name="record_stereo" value="true"/>
    <variable name="default_gateway" value="$${default_provider}"/>
    <variable name="default_areacode" value="$${default_areacode}"/>
    <variable name="transfer_fallback_extension" value="operator"/>
  </variables>

</domain>

該配置文件決定了哪些用戶能註冊到 FreeSWITCH 中。一般來說,所有用戶都應該屬於同一個 domain(除非你想使用多 domain,後面我們會有例子)。這裏的 $${domain} 全局變量是在 vars.xml 中設置的,它默認是主機的 IP 地址,但也可以修改,使用一個域名。params 中定義了該 domain 中所有用戶的公共參數。在這裏只定義了一個 dial-string,這是一個至關重要的參數。當你在使用 user/user_name 或 sofia/internal/user_name 這樣的呼叫字符串時,它會擴展成實際的 SIP 地址。其中 sofia_contact 是一個 API,它會根據用戶的註冊地址擴展成相應的呼叫字符串。

variables 則定義了一些公共變量,在用戶做主叫或被叫時,這些變量會綁定到相應的 Channel 上形成 Channel Variable。

在 domain 中還定義了許多組(group),組裏麪包含很多用戶(user)。

<groups>
  <group name="default">
    <users>
      <X-PRE-PROCESS cmd="include" data="default/*.xml"/>
    </users>
  </group>
</groups>               

在這裏,組名 default 並沒有什麼特殊的意義,它只是隨便起的,你可以修改成任何值。在用戶標籤裏,又使用預處理指令裝入了 default/ 目錄中的所有 XML 文件。你可以看到,在 default/ 目錄中,每個用戶都對應一個文件。

你也可以定義其它的用戶組,組中的用戶並不需要是完整的 XML 節點,也可以是一個指向一個已存在用戶的“指針”,如下圖,使用 type="pointer" 可以定義指針。

  <group name="sales">
    <users>
      <user id="1000" type="pointer"/>
      <user id="1001" type="pointer"/>
      <user id="1002" type="pointer"/>
    </users>
  </group>

雖然我們這裏設置了組,但使用組並不是必需的。如果你不打算使用組,可以將用戶節點(users)直接放到 domain 的下一級。但使用組可以支持像羣呼、代接等業務。使用 group_call 可以同時或順序的呼叫某個組的用戶。

實際用戶相關的設置也很直觀,下面顯示了 alice 這個用戶的設置:

<user id="alice">
  <params>
    <param name="password" value="$${default_password}"/>
    <param name="vm-password" value="alice"/>
  </params>
  <variables>
    <variable name="toll_allow" value="domestic,international,local"/>
    <variable name="accountcode" value="alice"/>
    <variable name="user_context" value="default"/>
    <variable name="effective_caller_id_name" value="Extension 1000"/>
    <variable name="effective_caller_id_number" value="1000"/>
    <variable name="outbound_caller_id_name" value="$${outbound_caller_name}"/>
    <variable name="outbound_caller_id_number" value="$${outbound_caller_id}"/>
    <variable name="callgroup" value="techsupport"/>
  </variables>
</user>

由上面可以看到,實際上 params 和 variables 可以出現在 user 節點中,也可以出現在 group 或 domain 中。 當它們有重複時,優先級順序爲 user,group,domain。

當然,用戶目錄還有一些更復雜的設置,我們留待以後再做研究。

呼叫流程及相關概念

再複習一下,FreeSWITCH是一個B2BUA,我們還是以第四章中的圖爲例:

主要呼叫流程有以下兩種:

  • bob 向 FreeSWITCH 發起呼叫,FreeSWTICH 接着啓動另一個 UA 呼叫 alice,兩者通話;
  • FreeSWITCH 同時呼叫 bob 和 alice,兩者接電話後 FreeSWITCH 將 a-leg 和 b-leg 橋接(bridge)到一起,兩者通話。

其中第二種又有一種變種。如市場上有人利用上、下行通話的不對稱性賣電話回撥卡獲取不正當利潤:bob 呼叫 FreeSWITCH,FreeSWITCH 不應答,而是在獲取 bob 的主叫號碼後直接掛機;然後 FreeSWITCH 回撥 bob;bob 接聽後 FreeSWITCH 啓動一個 IVR 程序指示 bob 輸入 alice 的號碼;然後 FreeSWITCH 呼叫 Alice……

在實際應用中,由於涉及回鈴音、呼叫失敗等,實際情況要複雜的多。

Session 與 Channel

對每一次呼叫,FreeSWITCH 都會啓動一個 Session(會話,它包含SIP會話,SIP會在每對UAC-UAS之間生成一個 SIP Session),用於控制整個呼叫,它會一直持續到通話結束。其中,每個 Session 都控制着一個 Channel(信道),Channel 是一對 UA 間通信的實體,相當於 FreeSWITCH 的一條腿(leg),每個 Channel 都有一個唯一的 UUID。另外,Channel 上可以綁定一些呼叫參數,稱爲 Channel Variable(信道變量)。Channel 中可能包含媒體(音頻或視頻流),也可能不包含。通話時,FreeSWITCH 的作用是將兩個 Channel(a-leg 和 b-leg,通常先創建的或佔主動的叫 a-leg)橋接(bridge)到一起,使雙方可以通話。

通話中,媒體(音頻或視頻)數據流在 RTP 包中傳送(不同於 SIP, RTP是另外的協議)。一般來說,Channel是雙向的,因此,媒體流會有發送(Send/Write)和接收(Receive/Read)兩個方向。

回鈴音與 Early Media

A  ------ |a 交換機 | ---X--- | 交換機 b| -------- B

爲了便於說明,我們假定A與B不在同一臺服務器上(如在PSTN通話中可能不在同一座城市),中間需要經過多級服務器的中轉。

假設上圖是在 PSTN 網絡中,A 呼叫 B,B 話機開始振鈴,A 端聽回鈴音(Ring Back Tone)。在早期,B 端所在的交換機只給 A 端交換機送地址全(ACM)信號,證明呼叫是可以到達 B 的,A 端聽到的回鈴音鈴流是由 A 端所在的交換機生成併發送的。但後來,爲了在 A 端能聽到 B 端特殊的回鈴音(如“您撥打的電話正在通話中…” 或 “對方暫時不方便接聽您的電話” 尤其是現代交換機支持各種個性化的彩鈴 - Ring Back Color Tone 等),回鈴音就只能由 B 端交換機發送。在 B 接聽電話前,回鈴音和彩鈴是不收費的(不收取本次通話費。彩鈴費用一般是在 B 端以月租或套餐形式收取的)。這些回鈴音就稱爲 Early Media(早期媒體)。它是由 SIP 的183(帶有SDP)消息描述的。

理論上講,B 接聽電話後交換機 b 可以一直不向 a 交換機發送應答消息,而將真正的話音數據僞裝成 Early Media,以實現“免費通話”。

Channel Variable

在每一個 Channel 上都可以設置好多 Variable,稱爲信道變量。FreeSWITCH 呼叫過程中,會根據這些變量控制 Channel 的行爲。

$${var} 與 ${var}

${var} 是在 dialplan、application 或 directory 中設置的變量,它會影響呼叫流程並且可以動態的改變。而 $${var} 則是全局的變量,它僅在預處理階段(系統啓動時,或重新裝載 - reloadxml時)被求值。後者一般用於設置一些系統一旦啓動就不會輕易改變的量,如 $${domain} 或 $${local_ip_v4}等。所以,兩者最大的區別是,$${var} 只求值一次,而 ${var} 則在每次執行時求值(如一個新電話進來時)。

$variable_xxxx

你會發現,有些變量在顯示時(可以使用dp_tools 中的 info() 顯示,後面會講到)是以“variable_”開頭的,但在實際引用時要去掉這開頭的“variable_”。如“variable_user_name”,引用時要使用“${user_name}”。http://wiki.freeswitch.org/wiki/Channel_Variables#variable_xxxx 列舉了一些常見的變量顯示與引用時的對應關係。

給 Variable 賦值

在 dialplan 中,有兩個程序可以給 Variable 賦值:

<action application="set" data="my_var=my_value"/>
<action application="export" data="my_var=my_value"/>

以上兩條命令都可以設置 my_var 變量的值爲 my_value。不同的是 -- set 程序僅會作用於“當前”的 Channel (a-leg),而 export 程序則會將變量設置到兩個 Channel (a-leg 和 b-leg)上,如果當時 b-leg 還沒有創建,則會在創建時設置。另外,export 還可以只將變量設置到 b-leg 上:

<action appliction="export" data="nolocal:my_var=my_value"/>

在實際應用中,如果 a-leg 上已經有一些變量的值(如 var1、var2、var3),而想同時把這些變量都複製到 b-leg 上,可以使用以下幾種辦法:

<action application="export" data="var1=$var1"/>
<action application="export" data="var2=$var2"/>
<action application="export" data="var3=$var3"/>

或者使用如下等價的方式:

<action application="set" data="export_vars=var1,var2,var3">

所以,其實 set 也具有能往 b-leg 上賦值的能力,其實,它和 export 一樣,都是操作 export_vars 這個特殊的變量。

取消 Variable 定義

取消 Variable 定義只需對它賦一個特殊的值_undef_”:

<action application="set" data="var1=_undef_">

截取 Variable 的一部分

可以使用特殊的語法取一個 Variable 的子串,格式是“${var:位置:長度}”。其中 “位置” 從 0 開始計烽,若爲負數則從字符串尾部開始計數;如果“長度”爲 0 或小於 0,則會從當前“位置”一直取到字符串結尾(或開頭,若“位置”爲負的話)。例如 var 的值爲 123456789,那麼:

${var}      = 1234567890
${var:0:1}  = 1
${var:1}    = 234567890
${var:-4}   = 7890
${var:-4:2} = 78
${var:4:2}  = 56

小結

本章描述了 FreeSWITCH 的架構。到這裏,讀者應該對 FreeSWITCH 有了一個總體的瞭解。我們也提到了一些基本元素和概念,簡單介紹了配置文件的基本結構,由於脫離了實際單講配置會比較抽象,因此,我們把具體的配置也寫到後面的章節裏,即,用到時再說。

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