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