2021SC@SDUSC
什么是Asterisk
Asterisk官网:https://www.asterisk.org/
以上解释来自Asterisk官方网站。翻译一下,就是说:
Asterisk是一个用于构建通信应用程序的开源框架。Asterisk将一台普通的计算机变成了一台通信服务器。Asterisk为IP PBX系统、VoIP网关、会议服务器和其他定制解决方案赋能。Asterisk通常由系统集成商和开发人员部署,它可以成为一个完整的业务电话系统的基础,或者用来增强或扩展现有系统,或者在系统之间架起一座桥梁。
什么是Asterisk-Java
Asterisk-Java官网:http://asterisk-java.org/
就是说,Asterisk-Java包由一组Java类组成,允许您轻松构建与Asterisk PBX Server交互的Java应用程序。Asterisk-Java支持以下Asterisk接口的大部分特性:
- 快速Asterisk网关接口(FastAGI)
- Asterisk管理接口(AMI)。
在我们的代码中使用的是AMI接口,所以以下分析如何在Java应用程序中使用AMI接口与Asterisk PBX Server交互。
The Manager API
官方教程:http://asterisk-java.org/tutorial/
Manager API是与Asterisk服务器进行远程交互的另一种方式。与FastAGI协议不同,Asterisk在使用Manager API时不会显式地将控制传递给应用程序,而是允许您随时查询和更改其状态。
Manager API由三个概念组成:动作(Actions)、响应(Responses)和事件(Events)。动作可以发送到Asterisk并指示它做某事。例如,您的应用程序可以向Asterisk发送一个Action,请求它拨一个号码,并将被拨方引导到您的一个电话上。作为对Action的应答,Asterisk发送一个包含执行操作结果的应答。
事件由Asterisk发送,与应用程序发送的Actions没有直接关系。事件通知您Asterisk状态的相关更改。例如,事件用于通知应用程序有来电或用户加入或离开MeetMe会议室。
通过Manager API与Asterisk服务器的连接通过TCP/IP进行,通常在默认端口5038上进行。
要在Asterisk上启用Manager API,必须编辑Manager.conf配置文件并重新启动Asterisk。conf还包含对允许连接的IP地址范围、身份验证的用户名和密码的约束。样本可能是这样的:
[general]
enabled = yes
port = 5038
bindaddr = 0.0.0.0
[manager]
secret=pa55w0rd
permit=0.0.0.0/0.0.0.0
read=all
write=all
这将启用Manager AP,允许从任何IP地址使用用户名“manager”和密码“pa55w0rd”访问。
使用示例
假设我们有一个通过SIP连接的电话,该电话在SIP/john上可用,我们想在默认上下文中从该电话发起呼叫到分机1300。我们必须获得ManagerConnection,提供运行Asterisk的主机名以及在manager.conf中配置的用户名和密码。接下来,我们登录并发送一个OriginateAction,最后断开连接。下面展示了一个这样做的示例。
import java.io.IOException;
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(
"localhost", "manager", "pa55w0rd");
this.managerConnection = factory.createManagerConnection();
}
public void run() throws IOException, AuthenticationFailedException,
TimeoutException
{
OriginateAction originateAction;
ManagerResponse originateResponse;
originateAction = new OriginateAction();
originateAction.setChannel("SIP/John");
originateAction.setContext("default");
originateAction.setExten("1300");
originateAction.setPriority(new Integer(1));
originateAction.setTimeout(new Integer(30000));
// 连接到Asterisk主机并登录
managerConnection.login();
// 发送originateAction并且等待Asterisk回复,应答最长时间为30秒
originateResponse = managerConnection.sendAction(originateAction, 30000);
// 打印是否成功接收到回复
System.out.println(originateResponse.getResponse());
// 最后登出并且断开连接
managerConnection.logoff();
}
public static void main(String[] args) throws Exception
{
HelloManager helloManager;
helloManager = new HelloManager();
helloManager.run();
}
}
要从Asterisk接收事件,您必须实现ManagerEventListener接口并将其添加到ManagerConnection。下面的代码展示了如何做到这一点的一个简单示例:
import java.io.IOException;
import org.asteriskjava.manager.AuthenticationFailedException;
import org.asteriskjava.manager.ManagerConnection;
import org.asteriskjava.manager.ManagerConnectionFactory;
import org.asteriskjava.manager.ManagerEventListener;
import org.asteriskjava.manager.TimeoutException;
import org.asteriskjava.manager.action.StatusAction;
import org.asteriskjava.manager.event.ManagerEvent;
public class HelloEvents implements ManagerEventListener
{
private ManagerConnection managerConnection;
public HelloEvents() throws IOException
{
ManagerConnectionFactory factory = new ManagerConnectionFactory(
"localhost", "manager", "pa55w0rd");
this.managerConnection = factory.createManagerConnection();
}
public void run() throws IOException, AuthenticationFailedException,
TimeoutException, InterruptedException
{
// 注册事件监听
managerConnection.addEventListener(this);
// 连接到Asterisk并登录
managerConnection.login();
// 发送一个动作,请求通道状态
managerConnection.sendAction(new StatusAction());
// 等待10秒等事件进入
Thread.sleep(10000);
// 最后登出并断开连接
managerConnection.logoff();
}
public void onManagerEvent(ManagerEvent event)
{
// 打印接收到的事件
System.out.println(event);
}
public static void main(String[] args) throws Exception
{
HelloEvents helloEvents;
helloEvents = new HelloEvents();
helloEvents.run();
}
}
SipDao代码解析
学习完上面的知识后,对基于Asterisk的网络通话技术有了一定的了解,下面开始分析我们openmeetings项目中的SipDao类代码
// SipDao.java
// 构造方法
public SipDao(String sipHostname, int sipPort, String sipUsername, String sipPassword, long timeout) {
this.sipHostname = sipHostname;
this.sipPort = sipPort;
this.sipUsername = sipUsername;
this.sipPassword = sipPassword;
this.timeout = timeout;
/*
在这里,和上面示例中的HelloManager类连接方法大同小异
sipHostname就是运行Asterisk的主机名,
sipPort是该api与Asterisk服务器的连接端口,
sipUsername是连接的用户名,
sipPassword是连接的密码
*/
factory = new ManagerConnectionFactory(this.sipHostname, this.sipPort, this.sipUsername, this.sipPassword);
}
// 从工厂创建连接并进行相应超时时间的配置
private ManagerConnection getConnection() {
DefaultManagerConnection con = (DefaultManagerConnection)factory.createManagerConnection();
con.setDefaultEventTimeout(timeout);
con.setDefaultResponseTimeout(timeout);
con.setSocketReadTimeout((int)timeout);
con.setSocketTimeout((int)timeout);
return con;
}
// 执行,传入一个ManagerAction对象,即动作;返回ManagerResponse对象,即响应
private ManagerResponse exec(ManagerAction action) {
if (factory == null) {
log.warn("There is no Asterisk configured");
return null;
}
// 获取ManagerConnection
ManagerConnection con = getConnection();
try {
// 登入并建立连接
con.login();
// 发送动作并接收响应
ManagerResponse r = con.sendAction(action);
if (r != null) {
log.debug(r.toString());
}
return (r instanceof ManagerError) ? null : r;
} catch (Exception e) {
log.error("Error while executing ManagerAction: " + action, e);
} finally {
try {
// 登出并断开连接
con.logoff();
} catch (Exception e) {
// no-op
}
}
return null;
}
有了前面的基础知识,分析SipDao类中如何实现网络通信就变得非常容易了
再看一下我们在ImportInitvalues类中使用到sipDao的代码:
// ImportInitvalues.java
public void loadSystem(InstallationConfig cfg, boolean force) {
checkInstalled(force);
sipDao.delete();
progress = 20;
loadConfiguration(cfg);
progress = 40;
if (cfg.isCreateDefaultObjects()) {
loadInitialOAuthServers();
}
progress = 60;
}
调用了sipDao的delete()方法
// SipDao.java
public void delete() {
DbDelTreeAction da = new DbDelTreeAction(ASTERISK_OM_FAMILY, ASTERISK_OM_KEY);
exec(da);
}
DbDelTreeAction就是AMI的一个动作,负责让Asterisk服务器从数据库中删除指定的一批数据
exec方法负责将动作发送,在前面的代码中也分析过了
所以在ImportInitvalues类的loadSystem方法中,它执行sipDao.delete()的作用是删除了Asterisk服务器的数据库中的一批数据,大概是为了防止数据库中存在的旧数据影响到安装进程或者之后的运行体验
总结
在该篇文章中,我学习并介绍了Asterisk、利用asterisk-java实现网络通话的方法以及完成了上篇文章中留下的对sipDao.delete()方法的分析。因为SipDao类是在openmeetings-db包下的,其实不属于我的分工范畴,所以在这篇文章中我也没有把该类的所有方法都分析完,只是挑了重点的实现网络通话部分进行分析以有助于我理解ImportInitvalues类下的sipDao.delete()方法。在下篇文章中,我将继续回到openmeetings-install包下的ImportInitvalues类进行详细的分析。如有错误,欢迎指正~