5. Asterisk AGI程序編寫指南
5.1概述
很多時候,我們需要在撥號方案中做某些業務邏輯的判斷或者外部數據庫的查詢,根據具體地需要,有幾種做法:
1.使用Asterisk的通道變量、Goto函數、Gotoif函數等實現某些簡單跳轉,通過幾個這樣的函數的組合,實現簡單的業務。
2.對終端接入用戶的呼叫請求中的某些屬性,進行簡單的數據庫增刪改查,在Asterisk官方發布的asterisk-addons開發包中安裝MYSQL模塊,具體地方法在這不細述。使用類似下面的方式:
exten => _0[0-9].,1, MYSQL(Connect connid dhhost dbuser dbpass dbname)
exten => _0[0-9].,2., MYSQL(Query resultid ${connid} query-string)
exten => _0[0-9].,3,MYSQL(Fetch fetchid ${resultid} var1/ var2/ .../ varN)
exten => _0[0-9].,4,MYSQL(Disconnect ${connid})
3.如果碰到了結合了1、2的業務需求,這時候撥號方案配置文件中就會出現大量地與業務邏輯有關的復雜代碼,造成技術人員閱讀上不方便,並且代碼也不好維護,如下面這段配置文件:
……
exten => 888,1,MYSQL(Connect connid localhost ipcontact passwd ipcontact)
exten => 888,n,GotoIf($["${connid}" = ""]?error,1)
exten => 888,n,MYSQL(Query resultid ${connid} SELECT/ `number`/ FROM/ `phones`/ WHERE/ `channel`=/'${chan}/')
……
exten => 888,n,GotoIf($["${foundRow}" = "1"]?done) ; leave loop if no row found
exten => 888,n,NoOp(${number})
exten => 888,n,Goto(fetchrow) ; continue loop if row found
exten => 888,n(done),MYSQL(Clear ${resultid})
exten => 888,n,MYSQL(Disconnect ${connid})
……
上面綜合了通道變量、Goto、Gotoif、MYSQL數據查詢等操作,其實如果配置文件中只是幾行這樣的操作閱讀起來沒問題,但業務需求具有可變性,代碼維護起來就比較麻煩。所以,我們就尋找另外一種方案,即在配置文件中不進行數據庫查詢等重量級的操作,而是將它交到一個外部應用程序或者腳本中,即封裝到Asterisk的AGI后台中,可以實現各種復雜的業務需求。使用類似PHP、JAVA、C等編程語言內置的判斷語句,而不是Asterisk封裝的類似Goto、Gotoif等函數,會更加快地實現業務需求,代碼也更好維護。下面先看看如何在撥號方案中使用AGI程序。
5.2 使用AGI腳本
執行AGI腳本時,Application應用就是'agi',參數是腳本的文件名,同時腳本需要滿足以下條件:
l 必須可執行,chomd 755
l 必須放置在指定目錄,如標准目錄:/var/lib/asterisk/agi-bin
l 必須指定完整的extension信息
比如說執行一個用PHP語言實現的AGI程序,有可能就是這樣:
exten => 1,2,AGI(test.php, ${CALLERID(name)})
腳本執行時,可以從控制台上得到不同基本的詳細信息,例如,文件不能被執行,或者找不到文件等等。
通過向Asterisk控制台信息輸出,可以得到AGI腳本的執行信息,至少在開發初期中,控制台信息輸出是個好辦法。
通過agi VERBOSE命令,可以將信息發送到asterisk控制台上,並且而通過verbosity設置可以關閉開啟這個功能。
如果直接,可以采用各種語言很方便的通過AGI接口編寫實施腳本。腳本和Asterisk之間通過標准的輸入輸出進行交互。
5.3 AGI(Asterisk Gateway Interface)技術實現原理
l 傳遞參數到AGI腳本
在腳本名稱后緊跟以英文半角狀態下的逗號,分隔的字符串,就可以把需要的參數傳入腳本:AGI(dial_agi.php,${EXTEN:11},${CALLERID(name)}),AGI腳本總是接收2種參數,1是腳本的完整路徑,2是撥號方案中'Exten'傳遞的參數,其中第1種參數,如果AGI程序放在了Asterisk默認路徑,可以省略,只寫AGI程序名。第2種參數是AGI程序需要撥號方案中'Exten'傳遞進來的參數,比如說本文中${EXTEN:11},${CALLERID(name)},一個是被叫的電話號碼,一個是主叫的賬號ID。
l 通過標准的輸出,發送命令到Asterisk
我們可以在AGI腳本程序中向Asterisk發送各種命令從而調用Asterisk的某些應用程序,如Dial、Goto、Monitor等,也可以直接發送命令獲得或者設置某些Asterisk通道變量的值。對於基於PHP的AGI腳本程序,可以按照如下步驟:
1.打開PHP輸出文件描述符:
$stdout = fopen('php://stdout', 'w');
2.向Asterisk發送命令:
fputs($stdout," SET CONTEXT media_gw1/n"); ");
fflush($stdout);
上述步驟是對於SET 或者 GET Asterisk的某些通道變量時的使用方法,如果需要調用Asterisk內置的應用,如執行跳轉到某個context下的某個priority的Goto應用函數,可以調用EXEC命令后面緊跟Asterisk,如:fputs($stdout," EXEC Goto media_gw1|s|2/n"); ");命令必須以換行符結束,AGI命令返回文本字符串,如下格式:200 Result=<number>,有時會在number數字后附加一些信息。如果向Asterisk發送了無效的命令,信息如下:510 Invalid or unknown command。對應上面的命令,如下所示:
AGI Rx << SET CONTEXT media_gw1
AGI Tx >> 200 result=0
l 通過標准的輸入,從Asterisk接收信息
當AGI腳本執行時,Asterisk會向腳本發送各種的信息,可以在做其他事情之前通過標准輸入獲取這些信息,每項數據都是一行,發送完畢Asterisk會發送一個空行,表示結束,如:
AGI Tx >> agi_request: dial_agi.php
AGI Tx >> agi_channel: SIP/25946-0821ea88
AGI Tx >> agi_language: en
AGI Tx >> agi_type: SIP
AGI Tx >> agi_uniqueid: 1209093478.477
AGI Tx >> agi_callerid: 0000123456
AGI Tx >> agi_calleridname: beigaolin
AGI Tx >> agi_dnid: 998866015810370728
AGI Tx >> agi_context: default
AGI Tx >> agi_extension: 998866015810370728
AGI Tx >> agi_priority: 1
根據項目需求,如果需要這些數據,就先保存起來,否則不用處理它。保存步驟按如下過程。
1.打開PHP輸出文件描述符:
$in = fopen("php://stdin","r");
2.分析從Asterisk傳到AGI的頭信息,如需要在AGI程序中獲取終端用戶的ID,那么從“agi_calleridname: beigaolin”這個頭信息可以獲取,我們通過分析每一行這樣以:分隔的字符串,取到需要后續處理的字符串
while (!feof($stdin)) {
$temp = fgets($stdin);
$temp = str_replace("/n","",$temp);
$s = explode(":",$temp);
$agivar[$s[0]] = trim($s[1]);
if (($temp == "") || ($temp == "/n")) {
break;
}
}
5.4 使用開源PHP AGI類函數PHPAGI
像上一小節那樣先是獲取輸入流,分析從輸入頭字符串中獲取對應某個輸入變量的值,或者獲取輸出流然后發送各種標准命令執行某些Asterisk內置應用,如果在AGI程序中實現很復雜的業務邏輯,這樣的流程會顯得有點累贅,所以需要提取某些常用的操作,我們使用的時候不用關心這些操作,直接以調用類似Asterisk內置應用那樣的方式。PHPAGI就是這樣的一個開源PHP類函數。它封裝了對應Asterisk內置應用的常用函數調用接口,比如說從PHP向Asterisk發送Dial命令的操作,可以直接調用PHP AGI類函數中的exec_dial。使用PHP AGI能夠很容易的操作Asterisk AGI常用接口。使用這個類函數也很簡單:
l 下載准備phpagi 函數文件:
cd /var/lib/asterisk/agi-bin/(也有可能在用戶自定義的路徑中)
wget http://nchc.dl.sourceforge.net/sourceforge/phpagi/phpagi-2.14.tgz
tar zxvf phpagi-2.14.tgz
l 在代碼中使用:
include ("phpagi.php");//包含文件
include ("phpagi-asmanager.php");
$agi = new AGI;//引用PHPAGI類函數
5.5 使用AGI實現主叫號碼透傳功能
在這里以一個例子來說明AGI程序在VoIP開發中的作用以及開發思路。
假設說有個普通電話為02412345678,手機號為15810370728,而網絡電話虛擬號碼是0000123456,如果想讓撥打出去的電話號碼在被叫方(手機或者帶有來電顯示功能的座機)的來電顯示為02412345678或者15810370728,那么他們回復電話的時候就可以直接打到這個普通電話上,方便與主叫的業務聯系。這個需求就叫主叫號碼透傳,能不能進行主叫號碼的透傳,取決於VoIP落地網關運營商,語音網關可以設置IP側送過來的主叫號碼是否透傳。在保證號碼規范的前提下,透傳什么樣的主叫號碼,則取決於IP-PBX系統,即Asterisk的設計了。
l 設計思路
1.增加一個針對終端用戶賬戶ID的綁定管理系統,如圖用戶在第二項中輸入自己的賬戶ID,然后再輸入想要作為來顯示的主叫號碼完成綁定操作,后台php程序向數據庫中插入一條新記錄(X-Lite ID對應電話號碼或者手機號碼)。
圖5-2 AGI后台管理系統頁面
2.使用綁定了主叫號碼的X-Lite呼叫某個被叫(手機或者座機)
Asterisk的后台PHP AGI程序的詳細設計主叫號碼透傳流程設計如圖4-4所示。
圖5-3 Asterisk 主叫號碼透傳的后台PHP AGI流程圖
l 代碼實現
以下代碼片斷展示的是PHP AGI中部分代碼,並且作了簡化。
#!/usr/local/php.5.2.5/bin/php –q
<?php
include_once("phpagi.php");//開源PHP類函數
......
//判斷當前這個id是否做了主叫號碼來電顯示的綁定操作
$query_string = "select * from xliteid where xliteid = '{$caller_name}'";
$query_result = mysql_query($query_string, $db_connection);
//如果當前這個id做了綁定操作,調用PHPAGI類函數,設置Asterisk主叫號碼
if($query_result && mysql_num_rows($query_result) > 0)
{
caller_phone_display_agi ();
}
//沒有做綁定,設置一個隨機的號碼
else
{
caller_name = $argv[2];
$rand_num1 = rand(0,9);
$rand_num2 = rand(0,9);
$rand_num3 = rand(0,9);
$caller_phone= "024{$rand_num1}{$rand_num2}650{$rand_num3}{$rand_num4}";
land_media_gw1($caller_phone);
exit();
}
/**
*@caller_phone_display_agi 主叫號碼特殊顯示
*/
function caller_phone_display_agi()
{
global $db_connection, $callee_phone, $caller_name;
$query_string = "select caller_phone from caller_phone_display _xliteid where skype_id = '{$caller_name}'";
$query_result = mysql_query($query_string, $db_connection);
if($query_result && mysql_num_rows($query_result) > 0)
{
$row = mysql_fetch_array($query_result);
$caller_phone = $row[0];
$callerid_cli = "/"{$caller_name}/"<{$caller_phone}>";
land_media_gw1($callerid_cli);
exit();
}
}
/**
*@ land_media_gw1 VoIP語音網關media_gw1
*/
function land_media_gw1($callerid_num)
{
global $agi, $callee_phone_withpre;
$agi->set_context("media_gw1");
$agi->set_extension($callee_phone_withpre);
$agi->set_priority(1);
//調用phpagi封裝的set_callerid方法,向Asterisk傳遞設置主叫號碼的指令
$agi->set_callerid($callerid_num);
}
對X-Lite賬戶gaolinb作了主叫號碼綁定,使用X-Lite軟終端呼叫普通的手機,在Asterisk中設置了agi debug,從Asterisk后台我們可以清晰地看到:
1.AGI Tx >> *CLI>上面部分,全是從Asterisk輸入到當前AGI的環境變量信息,它包含了當前這個呼叫的詳細信息,如Channel的類型,是SIP還是H.323,calleridname,即終端用戶是gaolinb等重要信息。
2.AGI Tx >> *CLI>下面部分,全是在上面調用PHPAGI類函數后將命令傳給了AGI程序執行,對於主叫號碼來電顯示的命令是:
SET CALLERID ‘gaolinb’<15810370728>,Asterisk將15810370728傳到能夠支持主叫號碼透傳的VoIP運營商,從而被叫用戶在接聽電話前能夠顯示一個有意義的電話號碼。
圖5-4 Asterisk服務器上AGI的輸入輸出信息