Asterisk Summary - 5
来源:互联网 发布:甘肃省网络研修社区 编辑:程序博客网 时间:2024/05/18 13:12
看了前面四小节的内容后,如果你现在正在做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()); } }}
- Asterisk Summary - 5
- Asterisk Summary - 1
- Asterisk Summary - 2
- Asterisk Summary - 3
- Asterisk Summary - 4
- Asterisk电子传真-5
- Asterisk电子传真-5
- Asterisk
- asterisk
- asterisk
- Asterisk
- asterisk
- Asterisk
- Asterisk
- summary 8/5
- Summary - 2015.2.5 贪心
- Summary
- summary
- [幼学壮行]工具推荐:
- MySQL优化
- SourceInsight快捷键超好用
- keil c union 存储的问题
- oracle控制文件管理
- Asterisk Summary - 5
- 栈和栈帧(一)
- 通过脚本实现AD用户自动连接打印机与共享文件夹
- 后台发送消息
- 他两次都没感动CCTV,却真正感动了中国
- 将图片等文件保存到sqlite中(c#)
- 获取本地计算机的主机名
- C#COM组件发布流程
- 【转】LDD命令的原理与使用方法