如何使用Asterisk-Java进行IP电话拨打
环境: Linux
参考API: http://www.asterisk-java.org/latest/apidocs/index.html
网上关于这个的文章已经不少,这里主要是把一些注意事项整理一下,然后是保留部分代码以备以后使用.
这里我们要做的是:用户点击页面的'打电话'按键,系统将通过Java完成目标IP电话和当前用户电话的自动连接功能.
首先是常规的代码:
package org.jerval.test.pbx; import java.io.IOException; import java.util.Map; import org.asteriskjava.manager.AuthenticationFailedException; import org.asteriskjava.manager.ManagerConnection; import org.asteriskjava.manager.ManagerConnectionFactory; import org.asteriskjava.manager.TimeoutException; import org.asteriskjava.manager.action.OriginateAction; import org.asteriskjava.manager.response.ManagerResponse; public class HelloManager { private ManagerConnection managerConnection; public HelloManager() throws IOException { ManagerConnectionFactory factory = new ManagerConnectionFactory("10.17.64.10", 5038, "admin", "xxxx"); this.managerConnection = factory.createManagerConnection(); } public void run() throws IOException, AuthenticationFailedException, TimeoutException { System.out.println("Login start..."); managerConnection.login(); System.out.println("Login sucessfully..."); OriginateAction originateAction; ManagerResponse originateResponse; managerConnection.addEventListener(new HelloEvents()); originateAction = new OriginateAction(); originateAction.setChannel("SIP/791582"); originateAction.setContext("default"); originateAction.setExten("791650"); originateAction.setPriority(new Integer(1)); originateAction.setTimeout(30000L); originateAction.setCallerId("US Reception <XXX Company>"); // send the originate action and wait for a maximum of 30 seconds for Asterisk to send a reply originateResponse = managerConnection.sendAction(originateAction); Map<String,Object> map = originateResponse.getAttributes(); System.out.println(map); // print out whether the originate succeeded or not System.out.println(originateResponse.getMessage()); System.out.println("Enter Response=" + originateResponse.getResponse()); // Originate successfully queued // and finally log off and disconnect System.out.println("Log off start..."); managerConnection.logoff(); System.out.println("Log off sucessfully..."); } public static void main(String[] args) throws Exception { HelloManager helloManager = new HelloManager(); helloManager.run(); } }
>>如果电话成功连接将出现如下消息:
{response=Success, message=Originate successfully queued, actionid=26399554_6#} Originate successfully queued Enter Response=Success
>>如果电话不可达:
{response=Error, message=Originate failed, actionid=25358555_6#} Originate failed Enter Response=Error
>>如果在规定时间内无应答:
Exception in thread "main" org.asteriskjava.manager.TimeoutException: Timeout waiting for response to Originate at org.asteriskjava.manager.internal.ManagerConnectionImpl.sendAction(ManagerConnectionImpl.java:825) at org.asteriskjava.manager.internal.ManagerConnectionImpl.sendAction(ManagerConnectionImpl.java:781) at org.asteriskjava.manager.DefaultManagerConnection.sendAction(DefaultManagerConnection.java:311) at org.jerval.test.pbx.HelloManager.run(HelloManager.java:38) at org.jerval.test.pbx.HelloManager.main(HelloManager.java:55)
这里需要注意的两点是:
1,如何获取正确的管理帐号:
在Linux下/etc/asterisk/manager.conf文件中会有如下信息:
[admin] secret = admin deny=0.0.0.0/0.0.0.0 permit=127.0.0.1/255.255.255.0 read = system,call,log,verbose,command,agent,user write = system,call,log,verbose,command,agent,user
2,如果出现下面错误如何解决:
>>问题1:
0 [main] INFO org.asteriskjava.manager.internal.ManagerConnectionImpl - Connecting to 10.17.64.10:5038 Exception in thread "main" java.net.ConnectException: Connection refused: connect at java.net.PlainSocketImpl.socketConnect(Native Method) at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:351) at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:213) at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:200) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366) at java.net.Socket.connect(Socket.java:529) at org.asteriskjava.util.internal.SocketConnectionFacadeImpl.<init>(SocketConnectionFacadeImpl.java:77) at org.asteriskjava.manager.internal.ManagerConnectionImpl.createSocket(ManagerConnectionImpl.java:729) at org.asteriskjava.manager.internal.ManagerConnectionImpl.connect(ManagerConnectionImpl.java:708) at org.asteriskjava.manager.internal.ManagerConnectionImpl.doLogin(ManagerConnectionImpl.java:490) at org.asteriskjava.manager.internal.ManagerConnectionImpl.login(ManagerConnectionImpl.java:438) at org.asteriskjava.manager.internal.ManagerConnectionImpl.login(ManagerConnectionImpl.java:423) at org.asteriskjava.manager.DefaultManagerConnection.login(DefaultManagerConnection.java:294) at org.jerval.test.pbx.HelloManager.run(HelloManager.java:23) at org.jerval.test.pbx.HelloManager.main(HelloManager.java:55)
这种错误通常是端口不正确,此时在Linux下/etc/asterisk/manager.conf文件中会有如下信息:
[general] enabled = yes port = 5538 bindaddr = 0.0.0.0
由此可见端口应该是5538 . 但通常都会使用默认端口5038的.
>>问题2:
Login start... 0 [main] INFO org.asteriskjava.manager.internal.ManagerConnectionImpl - Connecting to 10.17.64.10:5038 115 [Asterisk-Java ManagerConnection-0-Reader-0] INFO org.asteriskjava.manager.internal.ManagerConnectionImpl - Connected via Asterisk Call Manager/1.3 115 [Asterisk-Java ManagerConnection-0-Reader-0] WARN org.asteriskjava.manager.internal.ManagerConnectionImpl - Unsupported protocol version 'Asterisk Call Manager/1.3'. Use at your own risk. Exception in thread "main" org.asteriskjava.manager.AuthenticationFailedException: Authentication failed at org.asteriskjava.manager.internal.ManagerConnectionImpl.doLogin(ManagerConnectionImpl.java:578) at org.asteriskjava.manager.internal.ManagerConnectionImpl.login(ManagerConnectionImpl.java:438) at org.asteriskjava.manager.internal.ManagerConnectionImpl.login(ManagerConnectionImpl.java:423) at org.asteriskjava.manager.DefaultManagerConnection.login(DefaultManagerConnection.java:294) at org.jerval.test.pbx.HelloManager.run(HelloManager.java:23) at org.jerval.test.pbx.HelloManager.main(HelloManager.java:55) 1142 [main] INFO org.asteriskjava.manager.internal.ManagerConnectionImpl - Closing socket. 1142 [Asterisk-Java ManagerConnection-0-Reader-0] INFO org.asteriskjava.manager.internal.ManagerReaderImpl - Terminating reader thread: No more lines available: null
这种情况下,首先得确保帐号没有问题(参考:1,如何获取正确的管理帐号). 如果确定了帐号没有问题时,问题可能是没有将你机器的IP放到管理所允许的IP列表里.
打开/etc/asterisk/manager.conf:
[admin] secret = admin deny=0.0.0.0/0.0.0.0 permit=192.168.87.222/255.255.255.0 read = system,call,log,verbose,command,agent,user write = system,call,log,verbose,command,agent,user
加入自己机器的IP:192.168.87.222.
Live API
相比于manager包下面的API,live API更加容易使用或者说更加人性化.比如上面的HelloManager可以使用如下代码代替:
package org.jerval.test.pbx.live; import org.asteriskjava.live.AsteriskServer; import org.asteriskjava.live.DefaultAsteriskServer; import org.asteriskjava.live.ManagerCommunicationException; import org.asteriskjava.manager.action.OriginateAction; public class HelloLive { private AsteriskServer asteriskServer; public HelloLive() { asteriskServer = new DefaultAsteriskServer("10.17.64.10", 5038, "admin", "xxxx"); } public void run() throws ManagerCommunicationException { OriginateAction originateAction = new OriginateAction(); originateAction.setChannel("SIP/791582"); originateAction.setContext("default"); originateAction.setExten("791650"); originateAction.setPriority(new Integer(1)); originateAction.setTimeout(30000L); originateAction.setCallerId("US Reception <XXXX>"); asteriskServer.originate(originateAction); } public static void main(String[] args) throws Exception { HelloLive helloLive = new HelloLive(); helloLive.run(); } }
是不是看起来更加简单?
当然你也可以进行事件的监听,举个简单的例子:
package org.jerval.test.pbx.live; import org.asteriskjava.live.AsteriskServer; import org.asteriskjava.live.DefaultAsteriskServer; import org.asteriskjava.live.ManagerCommunicationException; import org.asteriskjava.manager.action.OriginateAction; public class HelloLive { private AsteriskServer asteriskServer; public HelloLive() { asteriskServer = new DefaultAsteriskServer("10.17.64.10", 5038, "admin", "xxxx"); } public void run() throws ManagerCommunicationException { OriginateAction originateAction = new OriginateAction(); originateAction.setChannel("SIP/791582"); originateAction.setContext("default"); originateAction.setExten("791650"); originateAction.setPriority(new Integer(1)); originateAction.setTimeout(30000L); originateAction.setCallerId("US Reception <XXXX>"); asteriskServer.addAsteriskServerListener(new HelloListener()); asteriskServer.originate(originateAction); } public static void main(String[] args) throws Exception { HelloLive helloLive = new HelloLive(); helloLive.run(); } }
这里通过代码:asteriskServer.addAsteriskServerListener(new HelloListener());添加事件监听器.
事件监听器代码:
package org.jerval.test.pbx.live; import org.asteriskjava.live.AsteriskChannel; import org.asteriskjava.live.AsteriskQueueEntry; import org.asteriskjava.live.AsteriskServerListener; import org.asteriskjava.live.MeetMeUser; import org.asteriskjava.live.internal.AsteriskAgentImpl; public class HelloListener implements AsteriskServerListener { @Override public void onNewAsteriskChannel(AsteriskChannel channel) { System.out.println("------onNewAsteriskChannel---------------------" + channel); } @Override public void onNewMeetMeUser(MeetMeUser user) { System.out.println("------onNewMeetMeUser---------------------" + user); } @Override public void onNewAgent(AsteriskAgentImpl agent) { System.out.println("------onNewAgent---------------------" + agent); } @Override public void onNewQueueEntry(AsteriskQueueEntry entry) { System.out.println("------onNewQueueEntry---------------------" + entry); } }
通常在正式的应用中,当我们拨打用户电话时,通常需要在页面中提示用户目标电话的状态,此时可以使用如下代码完成:
package org.jerval.test.pbx.live; import org.asteriskjava.live.AsteriskChannel; import org.asteriskjava.live.AsteriskServer; import org.asteriskjava.live.DefaultAsteriskServer; import org.asteriskjava.live.LiveException; import org.asteriskjava.live.ManagerCommunicationException; import org.asteriskjava.live.OriginateCallback; import org.asteriskjava.manager.action.OriginateAction; public class HelloLive2 { private AsteriskServer asteriskServer; public HelloLive2() { asteriskServer = new DefaultAsteriskServer("10.17.64.10", 5038, "admin", "xxxx"); } public void run() throws ManagerCommunicationException { OriginateAction originateAction = new OriginateAction(); originateAction.setChannel("SIP/791582");// 791582 791703 originateAction.setContext("default"); originateAction.setExten("791650"); originateAction.setPriority(new Integer(1)); originateAction.setTimeout(10000L); originateAction.setCallerId("US Reception <XXX>"); OriginateCallback callback = new OriginateCallback() { @Override public void onDialing(AsteriskChannel channel) { System.out.println("------onDialing---------------------"); } @Override public void onSuccess(AsteriskChannel channel) { System.out.println("------onSuccess---------------------"); } @Override public void onNoAnswer(AsteriskChannel channel) { System.out.println("------onNoAnswer---------------------"); } @Override public void onBusy(AsteriskChannel channel) { System.out.println("------onBusy---------------------"); } @Override public void onFailure(LiveException cause) { System.out.println("------onFailure---------------------"); } }; asteriskServer.originateAsync(originateAction, callback); try { Thread.sleep(20000); } catch (InterruptedException e) { } } public static void main(String[] args) throws Exception { HelloLive2 helloLive = new HelloLive2(); helloLive.run(); } }
这里的OriginateCallback里的实现方法可以由需求来改写,比如将消息实时反馈到页面上.
对于代码Thread.sleep(20000);,主要是为了让主线程保持,以便于最后主线程能输出负责监听的子线程所打印的状态,这里的时间通常要大于上面的timeOut时间. 因此,如果你不写线程睡眠或者等待的这句代码的话,回调方法打印的结果将不会出现在控制台中. 我开始也这此弄了很久.呵呵.
例子:
当然实际开发中,我们可能会有这样的场景. 当用户拨打电话后,如果是收到onFailure的状态,则立即将结果返回结用户,否则在等待一些时间后,收到onNoAnswer,onBusy,onSuccess时才将结果状态返回给用户. 因此这里将会用到线程的wait和notify方法.代码如下:
package org.jerval.test.pbx.live; import org.asteriskjava.live.AsteriskServer; import org.asteriskjava.live.DefaultAsteriskServer; import org.asteriskjava.live.ManagerCommunicationException; import org.asteriskjava.manager.action.OriginateAction; public class HelloLive3 { private AsteriskServer asteriskServer; public HelloLive3() { asteriskServer = new DefaultAsteriskServer("10.17.64.10", 5038, "admin", "amp111"); } public String run() throws ManagerCommunicationException { OriginateAction originateAction = new OriginateAction(); originateAction.setChannel("SIP/791703");// 791582 791703 originateAction.setContext("default"); originateAction.setExten("791650"); originateAction.setPriority(new Integer(1)); originateAction.setTimeout(10000L); originateAction.setCallerId("US Reception <XXX>"); StringBuilder msg = new StringBuilder(); Thread thread = Thread.currentThread(); MyOriginateCallback callback = new MyOriginateCallback(msg, thread); asteriskServer.originateAsync(originateAction, callback); // Wait one minute to get monitor msg. synchronized (thread) { try { thread.wait(60 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } return msg.toString(); } public static void main(String[] args) throws Exception { HelloLive3 helloLive = new HelloLive3(); System.out.println(helloLive.run()); } }
package org.jerval.test.pbx.live; import org.asteriskjava.live.AsteriskChannel; import org.asteriskjava.live.LiveException; import org.asteriskjava.live.OriginateCallback; public class MyOriginateCallback implements OriginateCallback { private StringBuilder msg; private Thread thread; public MyOriginateCallback(StringBuilder msg, Thread thread) { this.msg = msg; this.thread = thread; } private void notifyMainThread(){ synchronized (thread) { thread.notifyAll(); } } @Override public void onDialing(AsteriskChannel channel) { System.out.println("------onDialing---------------------"); } @Override public void onSuccess(AsteriskChannel channel) { System.out.println("------onSuccess---------------------"); msg.append("[onSuccess]"); notifyMainThread(); } @Override public void onNoAnswer(AsteriskChannel channel) { System.out.println("------onNoAnswer---------------------"); msg.append("[onNoAnswer]"); notifyMainThread(); } @Override public void onBusy(AsteriskChannel channel) { System.out.println("------onBusy---------------------"); msg.append("[onBusy]"); notifyMainThread(); } @Override public void onFailure(LiveException cause) { System.out.println("------onFailure---------------------"); msg.append("[onFailure]"); notifyMainThread(); } }