看了前面四小节的内容后,如果你现在正在做asterisk客户端的开发,我想你为了很好的实现第四节中的multiline也花了不少的时间吧。至少我是这样,不然也不会现在才写第五节内容。
首先讲一讲我在实现multiline中花费很多时间做一项工作 – 同步。由于iaxc工作的话会启用一条线程(iaxc_start_processing_thread();),为了不让你的程序出现异常,我们必须要小心翼翼的处理callNo 和 callState 在main thread 和iaxc thread的同步问题。实现同步当然就要利用锁机制啦,java中只需要假如synchronized ,C/C++就要利用互斥变量,信号量等来加锁。同时,如果有些异常还是无法避免的话,你可要小心的处理这些异常,不让你的程序轻则陷入混乱状态,重则崩溃(当然,你应该不希望看到这种情况的发生)。
接下来我要说一说这一节的主题—AMI(Asterisk Manager Interface)
What is AMI?
The Asterisk Manager Interface (AMI) allows a client program to connect to an Asterisk instance and issue commands or read events over a TCP/IP stream. Integrators will find this particularly useful when trying to track the state of a telephony client inside Asterisk, and directing that client based on custom (and possibly dynamic) rules.
A simple "key: value" line-based protocol is utilized for communication between the connecting client and the Asterisk PBX. Lines are terminated using CR/LF. For the sake of discussion below, we will use the term "packet" to describe a set of "key: value" lines that are terminated by an extra CR/LF.
简单来说就是你可以与asterisk server进行telnet通信,并且按照特定的格式向asterisk服务器发送指令来完成特定的任务。你是否想为你的super account实现一个监视的功能,好让这个supervi- sor可以实时的监视各个agent的状态,例如idle,dialing,talking,hold,talk-time。没错,这个时候AMI就派上用场啦!
Protocol Behavior
AMI的通信协议:
1. 你首先得建立一个与asterisk服务器的可靠授权连接。Eg: telnet server_ip 5038
2. 你向服务器发送的包应该是plain text,并且以特定的格式发送:第一行必须是 Action : ActionName ,
同时,服务器向client发送回复的第一行必然是 Event : EventName 或 Response : ResponseName
3. 每行的结束标志位CR/CL,在code层面也就是”/r/n”啦。从第二行开始,你可以描述这个Action的行为特性,具体有哪些行为特性可以参考: http://www.voip-info.org/wiki/view/Asterisk+manager+API 和 http://www.voip-info.org/wiki/view/asterisk+manager+events
4. 要发送command 时只需要输入”/r/n/r/n”,在telnet中就是连续两个回车换行符。
Opening a Manager Session and Authenticating as a User
通常,asterisk server都会开放5038端口来给manager通过TCP/IP建立连接和登陆。你可以通过配置/etc/asterisk/manager.conf 来改变这个端口和设定允许连接的IP范围,以及设置各个用户的asterisk权限。这些权限有(节选自/etc/asterisk/manager.conf)
; Read authorization permits you to receive asynchronous events, in general.
; Write authorization permits you to send commands and get back responses. The
; following classes exist:
;
; system - General information about the system and ability to run system
; management commands, such as Shutdown, Restart, and Reload.
; call - Information about channels and ability to set information in a
; running channel.
; log - Logging information. Read-only.
; verbose - Verbose information. Read-only.
; agent - Information about queues and agents and ability to add queue
; members to a queue.
; user - Permission to send and receive UserEvent.
; config - Ability to read and write configuration files.
; command - Permission to run CLI commands. Write-only.
; dtmf - Receive DTMF events. Read-only.
; reporting - Ability to get information about the system.
; cdr - Output of cdr_manager, if loaded. Read-only.
; dialplan - Receive NewExten and VarSet events. Read-only.
; originate - Permission to originate new calls. Write-only.
为了增加一个manager用户,你只需要在manager.conf中增加一个section, eg:
[super]
secret= super
read = system,call,log,verbose,agent,user,config,dtmf,reporting,cdr
write = system,call,agent,user,config,command,reporting,originate
打开windows的cmd,然后输入 telnet asterisk_ip 5038 , 然后登陆super用户,你将见到:
登陆成功后,你将收到很多asterisk发来的Event(如果有的话),为了只发送action command而不去接受event, 你可以在Secret: super 后面再加入一行 events: off。登陆后若想登出,只需要键入Action: logoff就可以啦!
Action Packets
To send Asterisk an action, follow this simple format:
Action: <action type><CRLF>
<Key 1>: <Value 1><CRLF>
<Key 2>: <Value 2><CRLF>
...
Variable: <Variable 1>=<Value 1><CRLF>
Variable: <Variable 2>=<Value 2><CRLF>
...
<CRLF>
Programing On AMI
暂时我编写的AMI程序是用Java写的,如果你一定要问我为什么不是C或C++呢?那我的回答就是,用Java的话已经有现成的AMI开发包啦,这个开发包已经把Action和Event都封装好了。为了节省开发时间所以就选择了Java。(详情请参考: http://asterisk-java.org/development/ 和 http://asterisk-java.org/development/apidocs/index.html )若想要这些开发包或源代码的话,可以联系我。
为了熟悉AMI的Action和Event,我这里给了一个Java AMI的例子,程序中主要查看了BridgeEvent/ UnlinkEvent/StatusEvent, 同时可以发送BridgeAction/StatusAction/RedirectAction。这些都是为之后要实现transfer call和3 way conference的功能打下基础。
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package testast;
import org.asteriskjava.manager.AuthenticationFailedException;
import org.asteriskjava.manager.ManagerConnection;
import org.asteriskjava.manager.ManagerConnectionFactory;
import java.io.IOException;
import java.util.Scanner;
import org.asteriskjava.manager.ManagerEventListener;
import org.asteriskjava.manager.TimeoutException;
import org.asteriskjava.manager.action.BridgeAction;
import org.asteriskjava.manager.action.RedirectAction;
import org.asteriskjava.manager.action.StatusAction;
import org.asteriskjava.manager.event.BridgeEvent;
import org.asteriskjava.manager.event.JitterBufStatsEvent;
import org.asteriskjava.manager.event.ManagerEvent;
import org.asteriskjava.manager.event.RtcpReceivedEvent;
import org.asteriskjava.manager.event.RtcpSentEvent;
import org.asteriskjava.manager.event.RtpReceiverStatEvent;
import org.asteriskjava.manager.event.RtpSenderStatEvent;
import org.asteriskjava.manager.event.StatusEvent;
import org.asteriskjava.manager.event.UnlinkEvent;
/**
*
* @author Dolphin Cheung (E-Mail: dolphin98629@163.com)
*/
public class Main {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
boolean quit = false;
Scanner sc = new Scanner(System.in);
System.out.println("Press Quit to quit ");
MyAMI ami = new MyAMI();
do{
String input = sc.next();
if(input.equals("Quit"))
quit = true;
if(input.equals("Transfer"))
{
if(ami.transfer())
{
ami.clearBridgeChan(0);
ami.clearBridgeChan(1);
}
}
if(input.equals("ChanState"))
{
System.out.println("input chan :");
input = sc.next();
ami.sentStatusAction(input);
}
if(input.equals("Redirect"))
{
System.out.println("input chan1 :");
String ch1 = sc.next();
System.out.println("input chan2 :");
String ch2 = sc.next();
if(ch2.equals("null"))
ami.redirectCall(ch1,"");
else
ami.redirectCall(ch1,ch2);
}
}while(!quit);
ami.removeEventListener();
ami.logOff();
}
}
class MyAMI {
public ManagerConnection managerConn;
public MyAstEvents astEvents;
String bridgeChan[][] = new String[2][2];
public MyAMI()
{
managerLogin();
astEvents = new MyAstEvents();
managerConn.addEventListener(astEvents);
}
public void removeEventListener()
{
managerConn.removeEventListener(astEvents);
}
public void logOff()
{
managerConn.logoff();
}
public void managerLogin()
{
ManagerConnectionFactory factory = new ManagerConnectionFactory ("xxx.xxx.xxx.xxx", "super", "super");
managerConn = factory.createManagerConnection();
try {
managerConn.login();
} catch (IllegalStateException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
} catch (AuthenticationFailedException ex) {
ex.printStackTrace();
} catch (org.asteriskjava.manager.TimeoutException ex) {
ex.printStackTrace();
}
}
public void dispalyBridgeChan()
{
for(int i=0;i<2;i++)
System.out.println(i+" : "+bridgeChan[i][0]+" <--> "+bridgeChan[i][1]);
}
public void setBridgeChan(int idx,String iax2Chan, String sipChan)
{
bridgeChan[idx][0] = iax2Chan;
bridgeChan[idx][1] = sipChan;
}
public void clearBridgeChan(int idx)
{
bridgeChan[idx][0] = null;
bridgeChan[idx][1] = null;
}
public String getIax2Chan(int idx)
{
return bridgeChan[idx][0];
}
public String getSipChan(int idx)
{
return bridgeChan[idx][1];
}
public int getFreeBridgeIdx()
{
for(int i=0;i<2;i++)
{
if(bridgeChan[i][0] == null && bridgeChan[i][1] == null)
return i;
}
return -1;
}
public boolean transfer()
{
String chan1 = null, chan2=null;
if(bridgeChan[0][0] != null && bridgeChan[0][1] != null)
chan1 = bridgeChan[0][1]; //first sip chan
if(bridgeChan[1][0] != null && bridgeChan[1][1] != null)
chan2 = bridgeChan[1][1]; //second sip chan
if(chan1 == null || chan2 == null )
{
System.out.println("Can not bridge : only one call!");
return false;
}
BridgeAction bAct = new BridgeAction (chan1, chan2);
System.out.println("Bridge " + chan1 + " + " + chan2 );
try {
managerConn.sendAction(bAct);
return true;
} catch (IOException ex) {
ex.printStackTrace();
} catch (TimeoutException ex) {
ex.printStackTrace();
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
} catch (IllegalStateException ex) {
ex.printStackTrace();
}
return false;
}
public void sentStatusAction(String chan)
{
if(chan == null)
return;
StatusAction stAct = new StatusAction();
System.out.println("sent ChanStatus action - " + chan );
stAct.setChannel(chan);
try {
managerConn.sendAction(stAct);
} catch (IOException ex) {
ex.printStackTrace();
} catch (TimeoutException ex) {
ex.printStackTrace();
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
} catch (IllegalStateException ex) {
ex.printStackTrace();
}
}
public boolean redirectCall(String chan1, String chan2){
RedirectAction reAct;
//this.managerLogin();
System.out.println("redirectCall - [" + chan1 + "], [" + chan2 +"]");
if (chan2.length() > 0)
reAct = new RedirectAction(chan1, chan2, "from_iax", "*88111001000000", 1);
else
reAct = new RedirectAction(chan1, "from_iax", "*88111001000000", 1);
try {
managerConn.sendAction(reAct);
//this.managerConn.logoff();
return true;
} catch (IOException ex) {
ex.printStackTrace();
} catch (TimeoutException ex) {
ex.printStackTrace();
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
} catch (IllegalStateException ex) {
ex.printStackTrace();
}
//this.managerConn.logoff();
return false;
}
class MyAstEvents implements ManagerEventListener {
public void onManagerEvent(ManagerEvent event) {
Object classObj = event.getClass();
if(classObj.equals(RtpSenderStatEvent.class) || classObj.equals(RtpReceiverStatEvent.class))
return;
if(classObj.equals(RtcpReceivedEvent.class) || classObj.equals(RtcpSentEvent.class))
return;
if(classObj.equals(JitterBufStatsEvent.class))
return;
if(classObj.equals(BridgeEvent.class)){
System.out.println("/n BridgeEvent detail - " + event.toString());
BridgeEvent evt = (BridgeEvent)event;
if(evt.getBridgeState().equals(BridgeEvent.BRIDGE_STATE_LINK)
/*&& evt.getBridgeType().equals(BridgeEvent.BRIDGE_TYPE_CORE )*/)
{
int idx = getFreeBridgeIdx();
if(idx != -1)
{
String ch1 = evt.getChannel1();
String ch2 = evt.getChannel2();
if(ch1.startsWith("IAX2/")){
setBridgeChan(idx,ch1,ch2);
}else if(ch1.startsWith("SIP/")){
setBridgeChan(idx,ch2,ch1);
}
}
}
dispalyBridgeChan();
}else if(classObj.equals(UnlinkEvent.class)){
System.out.println("/n UnlinkEvent detail - " + event.toString());
UnlinkEvent evt = (UnlinkEvent)event;
String iaxCh = null;
String sipCh = null;
System.out.println("Unlinke event: chan1: "+evt.getChannel1()+" chan2: "+evt.getChannel2());
if(evt.getChannel1().startsWith("IAX2/") && evt.getChannel2().startsWith("SIP/"))
{
iaxCh = evt.getChannel1();
sipCh = evt.getChannel2();
}else if(evt.getChannel1().startsWith("SIP/") && evt.getChannel2().startsWith("IAX/"))
{
iaxCh = evt.getChannel2();
sipCh = evt.getChannel1();
}
if(iaxCh == null || sipCh == null )
return;
for(int i=0;i<2;i++)
{
if(iaxCh.equals(bridgeChan[i][0])
&& sipCh.equals(bridgeChan[i][1]))
{
clearBridgeChan(i);
}
}
dispalyBridgeChan();
}else if(classObj.equals(StatusEvent.class)){
System.out.println("/n StatusEvent detail - " + event.toString());
}
else
System.out.println("/n Event detail - " + event.toString());
}
}
}