1. 程式人生 > >《FreeSWITCH: VoIP實戰》:FreeSWITCH 架構

《FreeSWITCH: VoIP實戰》:FreeSWITCH 架構

  總體結構
  FreeSWITCH 由一個穩定的核心及外圍模組組成。

  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 的值為 1234567890,那麼:

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

  小結

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

轉自:http://ec.ctiforum.com/jishu/qiye/qiyetongxinjishu/kaiyuantongxin/jishuwenzhai/330719.html