1简介
在开启Java ESL Client编程之前,请先阅读《FreeSWITCH权威指南》学习什么是FreeSWITCH Event Socket。
Java连接FreeSWITCH的ESL可以采用两种模式:inbound和outbound。
-
Inbound模式:Java应用作为客户端主动连接到FreeSWITCH的内置TCP服务器上,默认监听8021端口。连接成功后,Java应用可以订阅FreeSWITCH的各种事件通知,并对特定事件进行处理,也可以向FreeSWITCH发送命令。例如,通过Java应用可以获取来电事件,并且可以控制呼叫流程,如发送异步API命令发起呼叫 。
-
Outbound模式:Java应用作为服务器端,FreeSWITCH作为客户端连接到Java应用。这种模式下,需要在FreeSWITCH配置中指定Java应用的IP和端口。当有来电时,FreeSWITCH会连接到Java应用,Java应用可以根据业务需求对通话进行处理,如播放音乐或转接呼叫 。
选择哪种模式取决于具体的业务需求。如果需要监控所有来电情况或实现自助语音服务,inbound模式可能更合适。而如果需要对来电进行人工客服分配,outbound模式可能更简单 。另外,Java ESL客户端库支持这两种模式,并且提供了丰富的API来处理ESL连接和事件 。
在实际开发中,开发者可以根据需要选择适合的连接模式,并利用Java ESL客户端提供的接口来实现与FreeSWITCH的交互。
Java ESL客户端通过与FreeSWITCH的Event Socket Library(ESL)交互,能够实现以下操作:
-
连接管理:建立与FreeSWITCH的连接,并处理连接的生命周期,包括断开和重连。
-
事件订阅:订阅FreeSWITCH产生的各种事件,如呼叫开始、结束、通道变更等。
-
事件处理:接收并处理来自FreeSWITCH的事件通知,允许应用根据事件执行业务逻辑。
-
发送API命令:向FreeSWITCH发送同步或异步API命令来控制呼叫流程,例如发起呼叫、挂断、转接等。
-
执行异步命令:发送异步命令并接收结果,如查询呼叫状态、执行数据库操作等。
-
同步命令执行:发送同步命令并等待其执行结果,适用于需要即时反馈的操作。
-
设置呼叫变量:在呼叫过程中设置或修改呼叫变量,以控制FreeSWITCH的行为。
-
用户和设备管理:通过API命令管理SIP用户、分机、IVR流程等。
-
呼叫录音:控制呼叫录音的开始和停止。
-
呼叫监听和监视:监听特定呼叫的音频流或监视呼叫状态。
-
发送DTMF:向正在通话的通道发送DTMF信号。
-
应用执行:在呼叫中执行特定的FreeSWITCH应用,如播放音乐、发送短信等。
-
错误处理:处理与FreeSWITCH连接或命令执行过程中可能出现的错误。
-
日志记录:记录与FreeSWITCH交互的日志信息,方便问题排查。
-
自定义协议支持:实现自定义的ESL协议命令,以支持FreeSWITCH的高级特性。
-
多线程和并发处理:支持多线程环境下的并发调用和事件处理。
-
资源管理:管理与FreeSWITCH的连接资源,优化连接使用效率。
Java ESL客户端作为一个强大的工具,允许开发者通过编程方式与FreeSWITCH进行交互,实现复杂的呼叫处理和通信应用(fs_cli)。
2实现
2.1maven依赖
<dependency>
<groupId>org.freeswitch.esl.client</groupId>
<artifactId>org.freeswitch.esl.client</artifactId>
<version>0.9.2</version>
</dependency>
2.2代码
package org.example;
import org.freeswitch.esl.client.IEslEventListener;
import org.freeswitch.esl.client.inbound.Client;
import org.freeswitch.esl.client.transport.event.EslEvent;
/**
* @author yrz
* @create 2024/08/20
*/
public class ESLClient {
private static final String HOST = "10.192.33.34";
private static final int PORT = 8021;
private static final String PASSWORD = "ClueCon";
private static String job_UUID = null;
public static void main(String[] args) {
InBound();
}
private static void InBound() {
final Client client = new Client();
try {
client.connect(HOST, PORT, PASSWORD, 20);
System.out.println("连接成功");
} catch (Exception e) {
System.out.println("连接失败");
}
client.addEventListener(new IEslEventListener() {
@Override
public void eventReceived(EslEvent event) {
if (event.getEventName().equals("CHANNEL_ANSWER")) {
System.out.println("通道应答");
} else if (event.getEventName().equals("HEARTBEAT")) {
System.out.println("收到心跳 --> " + event.getEventBodyLines());
job_UUID = client.sendAsyncApiCommand("status", null);
System.out.println("Job_UUID --> " + job_UUID);
} else if (event.getEventName().equals("CHANNEL_DESTROY")) {
System.out.println("通道销毁");
} else if (event.getEventName().equals("CHANNEL_HANGUP_COMPLETE")) {
//挂断
System.out.println("通道挂断完成");
} else if (event.getEventName().equals("CHANNEL_CREATE")) {
System.out.println("通道创建");
}
}
@Override
public void backgroundJobResultReceived(EslEvent event) {
String uuid = event.getEventHeaders().get("Job-UUID");
if (job_UUID.equals(uuid)) {
for (String s : event.getEventBodyLines()) {
System.out.println(s);
}
}
}
});
client.setEventSubscriptions("plain", "all");
}
}
/*
播打电话
String originate = client.sendAsyncApiCommand("originate", CallEnum.CALL_BRIDGE);
类型
//相当于1001给1002打电话 {origination_caller_id_number=1001}可以设置1001方显示名字(默认是0000000)
public static final String CALL_BRIDGE = "{origination_caller_id_number=1002}user/1001 &bridge(user/1002)";
// 给1001打电话,channel启动echo app(类似汤姆猫) 并修改名字和号码
public static final String CALL_CHANNEL_CHANGE_NAME = "{origination_caller_id_name='Zhang San',origination_caller_id_number=1234}user/1001 &echo";
//等价于 user/1001 &echo
public static final String CALL_DIALPLAN_XML = "user/1001 echo inline";
// 给1001通话,channel启动playback app(播放录音)
public static final String CALL_PLAYBACK = "{origination_caller_id_name='Zhang San',origination_caller_id_number=1234}user/1001 &playback(/播放文件路径)";
// 顺振 第一个呼叫失败,呼叫第二个,以此类推
public static final String CALL_PARAMAGNETIC_RESONANCE = "user/1001|user/1002 &echo";
// 同振 同时呼叫 谁先接听谁通话,另一个挂断
public static final String CALL_WITH_VIBRATION = "user/1001,user/1002 &echo";
// 服务器先给1001打,1001接听后进入dialplan,找到1002这个exten,最后执行&echo
public static final String CALL_EXTEN = "user/1001 1002";
// 在1001接听后进入public dialplan查找路由
public static final String CALL_DIALPLAN_CONTEXT = "user/1001 1002 XML public";
// 在呼叫时,在SDP里面向对方提供G729 PCMU编码,但在执行是会缺少PCMU编码 需要转义字符\或者^^: {absolute_codec_string=G729\,PCMU}user/1001 &echo 或者下面的
public static final String CALL_ESCAPE_CODEC = "{absolute_codec_string=G729^^:PCMU}user/1001 &echo";
*/
3问题处理
3.1mod_event_socket.c:2674 IP ::ffff Rejected by acl “loopback.auto”
event socket默认监听地址为127.0.0.1。
在event_socket.conf.xml配置文件中,取消该行注释:
<param name="apply-inbound-acl" value="loopback.auto"/>
在acl.conf.xml中增加:
<list name="loopback.auto" default="allow">
<node type="allow" cidr="0.0.0.0"/>
</list>
/***********************************************/
<list name="loopback.auto" default="allow">
<node type="allow" cidr="0.0.0.0/0"/>
</list>
reloadxml
reloadacl
4参考文档
https://developer.signalwire.com/freeswitch/FreeSWITCH-Explained/Client-and-Developer-Interfaces/Java-ESL/Java-ESL-Client_7144076/
https://github.com/esl-client/esl-client
https://sunxiaodou.com/2017/08/02/java-esl-freeswitch/
https://blog.csdn.net/qq_40170041/article/details/127015890