撥號計劃是 FreeSWITCH 中至關(guān)重要的一部分。它的主要作用就是對電話進(jìn)行路由(從這一點上來說,相當(dāng)于一個路由表)。說的簡明一點,就是當(dāng)一個用戶撥號時,對用戶所撥的號碼進(jìn)行分析,進(jìn)而決定下一步該做什么。當(dāng)然,實際上,它所能做的比你想象的要強大的多。
我們在第二章中已經(jīng)提到過修改過撥號計劃,單從配置文件看,還算比較簡單直觀。實際上,它的概念也不是很復(fù)雜。如果你理解正則表達(dá)式,那你應(yīng)該能看懂系統(tǒng)系統(tǒng)自帶的大部分的配置。但是,在實際應(yīng)用中,有許多問題還是常常令初學(xué)者感到疑惑。主要的問題是,要理解 Dialplan,還需要了解 FS 是怎樣工作的(第五章),API 與 APP 的區(qū)別等。
通過本章,我們除了要了解 Dialplan 的基本概念和運作方式,還要以理論與實踐相結(jié)合的方式來進(jìn)行學(xué)習(xí),使用初學(xué)者能快速上手,有經(jīng)驗的人也能學(xué)到新的維護(hù)和調(diào)試技巧。
XML Dialplan
Dialplan 是 FreeSWITCH 中一個抽象的部分,它可以支持多種不同的格式,如類似 Asterisk 的格式(由 mod_dialplan_asterisk提供)。但在實際使用中,用的最多的還是 XML 格式。下面,我們就先討論這種格式。
配置文件的結(jié)構(gòu)
撥號計劃的配置文件在 conf/dialplan 中,在前面的章節(jié)中我們講過,它們是在 freeswitch.xml 中,由 <X-PRE-PROCESS cmd="include" data="dialplan/*.xml"/> 裝入的。
撥號計劃由多個 Context (上下文/環(huán)境)組成。每個 Context 中有多個 Extension (分支,在簡單的 PBX 中也可以認(rèn)為是分機號,但很顯然,Extension 涵蓋的內(nèi)容遠(yuǎn)比分機號多)。所以,Context 就是多個 Extension 的邏輯集合,它相當(dāng)于一個分組,一個 Context 中的 Extension 與其它 Context 中的 Extension 在邏輯上是隔離的。
下面是 Dialplan 的完整結(jié)構(gòu):
<?xml version="1.0"?>
<document type="freeswitch/xml">
<section name="dialplan" description="Regex/XML Dialplan">
<context name="default">
<extension name="Test Extension">
</extension>
</context>
</section>
</document>
Extension 相當(dāng)于路由表中的表項,其中,每一個 Extension 都有一個 name 屬性。它可以是任何合法的字符串,本身對呼叫流程沒有任何影響,但取一個好聽的名字,有助于你在查看 Log 時發(fā)現(xiàn)它。
在 Extension 中可以對一些 condition (條件)進(jìn)行判斷,如果滿足測試條件所指定的表達(dá)式,則執(zhí)行相對應(yīng)的 action (動作)。
例如,我們將下列 Extension 配置加入到 conf/dialplan/default.xml 中。并作為第一個 Extension。
<extension name="My Echo Test">
<condition field="destination_number" expression="^echo|1234$">
<action application="echo" data=""/>
</condition>
</extension>
FreeSWITCH 安裝時,提供了很多例子,為了避免與提供的例子沖突,強列建議在學(xué)習(xí)時把自己寫的 Extension 寫在最前面。當(dāng)然我說的最前面并不是 default.xml 的第一行,而是放到第一個 Extension 的位置,就是以下語句的后面(你通常能在第13-14行找到它們):
<include>
<context name="default">
用你喜歡的編譯器編輯好并存盤后,在 FreeSWITCH 命令行上(Console 或 fs_cli)執(zhí)行 reloadxml 或按 F6鍵,使 FreeSWITCH 重新讀入你修改過的配置文件。并按 F8 鍵將 log 級別設(shè)置為 DEBUG,以看到詳細(xì)日志.然后,將軟電話注冊上,并撥叫 1234 或 echo (大部分軟電話都能呼叫字母,如Zoiper,Xlite可以使用空格鍵切換數(shù)字和字母)。
你將會看到很多 Log, 注意如下的行:
Processing Seven <1000>->1234 in context default
parsing [default->My Echo Test] continue=false
Regex (PASS) [Echo Test] destination_number(1234) =~ /^echo|1234$/ break=on-false
Action echo()
在我的終端上,上面的第一行是以綠色顯示的。當(dāng)然,為了排版方便,我省去了 Log 中的日期以及其它不關(guān)鍵的一些信息。
第一行,Processing 說明是在處理 Dialplan,Seven 是我的的 SIP 名字,1000 是我的分機號, 1234 是我所撥叫的號碼,這里,我直接撥叫了 1234。它完整意思是說,呼叫已經(jīng)達(dá)到路由階段,要從 XML Dialplan 中查找路由,該呼叫來自 Seven,分機號是1000,它所呼叫的被叫號碼是 1234 (或 echo,如果你撥叫 echo 的話)。
第二行,呼叫進(jìn)入 parsing (解析XML) 階段,它首先找到 XML 中的一個 Context,這里是 default(它是在 user directory 中定義的,看第五章。user directory 中有一項 , 說明,如果 1000 這個用戶發(fā)起呼叫,則它的 context 就是 default,所以要從 XML Dialplan 中的 default 這個 Context 查起)。它首先找到的第一個 Extension 的 name 是 My Echo Test(還記得吧?我們我們把它放到了 Dialplan 的最前面)。continue=false 的意思我們后面再講。
第三行,由于該 Extension 中有一個 Condition,它的測試條件是 destination_number,也就是被叫號碼,所以, FreeSWITCH 測試被叫號碼(這里是 1234)是否與配置文件中的正則表達(dá)式相匹配。 ^echo|1234$ 是正則表達(dá)式,它匹配 echo 或 1234。所以這里匹配成功,Log 中顯示 Regex (PASS)。 當(dāng)然既然匹配成功了,它就開始執(zhí)行動作 echo(它是一個 APP),所以你就聽到了自己的聲音。
這是最簡單的路由查找。前面我已經(jīng)說了,系統(tǒng)自帶了一些 Dialplan 的例子,也許在第二章你已經(jīng)測試過了。下面,我們試一下系統(tǒng)自帶的 echo 的例子。這次,我呼叫的是 9196。在 Log 中,還是從綠色的行開始看:
Processing Seven <1000>->9196 in context default
parsing [default->My Echo Test] continue=false
Regex (FAIL) [Echo Test] destination_number(9196) =~ /^echo|1234$/ break=on-false
parsing [default->unloop] continue=false
Regex (PASS) [unloop] ${unroll_loops}(true) =~ /^true$/ break=on-false
Regex (FAIL) [unloop] ${sip_looped_call}() =~ /^true$/ break=on-false
parsing [default->tod_example] continue=true
Date/Time Match (FAIL) [tod_example] break=on-false
parsing [default->holiday_example] continue=true
Date/Time Match (FAIL) [holiday_example] break=on-false
parsing [default->global-intercept] continue=false
Regex (FAIL) [global-intercept] destination_number(9196) =~ /^886$/ break=on-false
parsing [default->group-intercept] continue=false
Regex (FAIL) [group-intercept] destination_number(9196) =~ /^\*8$/ break=on-false
parsing [default->intercept-ext] continue=false
Regex (FAIL) [intercept-ext] destination_number(9196) =~ /^\*\*(\d+)$/ break=on-false
parsing [default->redial] continue=false
Regex (FAIL) [redial] destination_number(9196) =~ /^(redial|870)$/ break=on-false
parsing [default->global] continue=true
Regex (FAIL) [global] ${call_debug}(false) =~ /^true$/ break=never
parsing [default->fax_receive] continue=false
Regex (FAIL) [fax_receive] destination_number(9196) =~ /^9178$/ break=on-false
parsing [default->fax_transmit] continue=false
Regex (FAIL) [fax_transmit] destination_number(9196) =~ /^9179$/ break=on-false
parsing [default->ringback_180] continue=false
Regex (FAIL) [ringback_180] destination_number(9196) =~ /^9180$/ break=on-false
parsing [default->ringback_183_uk_ring] continue=false
Regex (FAIL) [ringback_183_uk_ring] destination_number(9196) =~ /^9181$/ break=on-false
parsing [default->ringback_183_music_ring] continue=false
Regex (FAIL) [ringback_183_music_ring] destination_number(9196) =~ /^9182$/ break=on-false
parsing [default->ringback_post_answer_uk_ring] continue=false
Regex (FAIL) [ringback_post_answer_uk_ring] destination_number(9196) =~ /^9183$/ break=on-false
parsing [default->ringback_post_answer_music] continue=false
Regex (FAIL) [ringback_post_answer_music] destination_number(9196) =~ /^9184$/ break=on-false
parsing [default->ClueCon] continue=false
Regex (FAIL) [ClueCon] destination_number(9196) =~ /^9191$/ break=on-false
parsing [default->show_info] continue=false
Regex (FAIL) [show_info] destination_number(9196) =~ /^9192$/ break=on-false
parsing [default->video_record] continue=false
Regex (FAIL) [video_record] destination_number(9196) =~ /^9193$/ break=on-false
parsing [default->video_playback] continue=false
Regex (FAIL) [video_playback] destination_number(9196) =~ /^9194$/ break=on-false
parsing [default->delay_echo] continue=false
Regex (FAIL) [delay_echo] destination_number(9196) =~ /^9195$/ break=on-false
parsing [default->echo] continue=false
Regex (PASS) [echo] destination_number(9196) =~ /^9196$/ break=on-false
Action answer()
Action echo()