天天看點

基于Asterisk的VoIP開發指南——(2)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

根據項目需求,如果需要這些資料,就先儲存起來,否則不用處理它。儲存步驟按如下過程。

$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/(也有可能在使用者自定義的路徑中)

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的輸入輸出資訊