esl inbound client,内部有一个canSend()方法:

    public boolean canSend() {
return channel != null && channel.isConnected() && authenticated;
}

大多数情况下(之所以说大多数情况是因为最末尾还有一个authenticated),都可以用它来检测网络是否断开,如果断开了,可以自己写代码重连(注:0.9.2版本依赖的netty较老,esl client本身也并没有重连逻辑)。

而且在org.freeswitch.esl.client.inbound.Client#connect()方法里,有一个判断:

如果之前有连着,先close断开,接下来看close方法:

这里又做了1次网络检测,checkConnected实现如下:

看上去很严谨,双重检测,感觉重连时只要再调用1次connect就可以了,但是这里有一个陷阱:如果channel连接正常,但是authenticated=false,canSend()就返回false,这时候再去connect,先前的连接并不会释放,造成连接泄露!

为了重现这个问题,我们先准备一段代码:

import org.freeswitch.esl.client.IEslEventListener;
import org.freeswitch.esl.client.inbound.Client;
import org.freeswitch.esl.client.transport.event.EslEvent; public class InboundTest { private static class DemoEventListener implements IEslEventListener { @Override
public void eventReceived(EslEvent event) {
System.out.println("eventReceived:" + event.getEventName());
} @Override
public void backgroundJobResultReceived(EslEvent event) {
System.out.println("backgroundJobResultReceived:" + event.getEventName());
}
} public static void main(String[] args) throws InterruptedException {
String host = "localhost";
int port = 8021;
String password = "ClueCon";
int timeoutSeconds = 10;
Client inboundClient = new Client();
try {
inboundClient.connect(host, port, password, timeoutSeconds);
inboundClient.addEventListener(new DemoEventListener());
inboundClient.cancelEventSubscriptions();
inboundClient.setEventSubscriptions("plain", "all");
} catch (Exception e) {
System.out.println("connect fail");
} while (true) {
System.out.println(System.currentTimeMillis() + " " + inboundClient.canSend());
if (!inboundClient.canSend()) {
try {
//重连
inboundClient = new Client();
inboundClient.addEventListener(new DemoEventListener());
inboundClient.connect(host, port, password, timeoutSeconds);
inboundClient.cancelEventSubscriptions();
inboundClient.setEventSubscriptions("plain", "all");
} catch (Exception e) {
System.out.println("connect fail");
}
}
Thread.sleep(200);
}
}
}

代码很简单,先连上,然后用一个循环不停检测canSend(),发现"断开"了,就重连。

参考上图,在if条件这行打一个断点,然后利用调试工具,在断点处,强制把inboundClient.authenticated改成false(不清楚该调试技巧的同学,可参考之前的旧文idea 高级调试技巧),同时打开一个终端窗口,在程序运行前、断点修改前、断点修改并完成connect后,分别用lsof -i:8021观察下本机的连接情况

如上图:
1) 程序运行前,只有一个freeswitch在监听本机的8021端口
2) 启用成功后,在断点修改前,java进程13516,建立了1个连接(对应的随机端口号为58825)
3) 断点修改后,继续运行到connect后,还是13516进程,又建立了1个连接(对应的随机端口号为58857),而之前的旧连接(58825)并没有释放,哪怕这里我用new Client()生成了一个全新的实例,旧实例关联的连接资源仍然在!
4) 继续这样操作,会发现每次都会创建1个新链接,而原来的链接依然存在。

解决方法:重连先调用channel.close()方法,关闭channel,可以在源码中,加一个方法closeChannel

    /**
* close netty channel
*
* @return
*/
public ChannelFuture closeChannel() {
if (channel != null && channel.isOpen()) {
return channel.close();
}
return null;
}

然后connect开头那段检测改成:

        // If already connected, disconnect first
if (canSend()) {
close();
} else {
//canSend()=false but channel is still opened or connected
closeChannel();
}

这里说点题外话,channel类有isOpen、isConnected 二个方法,另外还有close()及disconnect()方法,有啥区别?

isOpen=true时,该channel可write,但是不能read (即:打开,但是没连网)
isConnected=true,该channel可read/write(即:真正连上了网),换句话说:isOpen=true,未必isConnected=true,但是isConnected=true,isOpen必须为true.

这里我们旨在重连前释放channel的所有资源,所以用close更彻底点。

 

再来看看内存泄露的问题,这个问题其实已经有网友记录过了,大致原因是netty底层大量使用了DirectByteBuffer,这是直接在堆外分配的(即:堆外内存),不会被GC自动回收,如果代码处理不当,多次调用connect()时,就有可能内存泄露。按该网友的建议,改成static静态实例后,保证只有1个实例就可以了。细节不多说,代码最后会给出,这里谈另一个问题:

这里使用的是newCachedThreadPool方法,查看该方法源码可知:

线程池的最大线程数是MAX_VALUE,相当于没有上限,如果异常情况下,线程会一直上涨,直到资源用完, 最好换成明确有上限的写法。

另外,还有1个细节问题,Client只提供了添加事件监控的方法:

    public void addEventListener(IEslEventListener listener) {
if (listener != null) {
eventListeners.add(listener);
}
}

但却没有提供移除的方法,如果重连时,无意重复调用了该方法,同样的事件(即:同一个listener重复注册),就会处理多次,可以新增一个清空方法,每次重连前,最好调用一下:

    /**
* remove all eslEventlistener
*/
public void removeAllEventListener() {
if (eventListeners != null) {
eventListeners.clear();
}
}

以上修改已经提交到github,需要的朋友可参考https://github.com/yjmyzz/esl-client/tree/0.9.x

freeswitch笔记(4)-esl inbound模式的重连及内存泄露问题的更多相关文章

  1. 【笔记】web 的回流与重绘及优化

    最近看了幕课网 web 前端性能优化的课程,其中说到了浏览器的回流(reflow) 及 重绘(repaint).觉得以后面试或许会被问到所以做一下笔记: 课程从回流及重绘这两个点延伸出了一个知识点就是 ...

  2. jsp学习笔记:mvc开发模式

    jsp学习笔记:mvc开发模式2017-10-12 22:17:33 model(javabe)与view层交互 view(视图层,html.jsp) controller(控制层,处理用户提交的信息 ...

  3. PDO之MySql持久化自动重连导致内存溢出

    前言 最近项目需要一个常驻内存的脚本来执行队列程序,脚本完成后发现Mysql自动重连部分存在内存溢出,导致运行一段时间后,会超出PHP内存限制退出 排查 发现脚本存在内存溢出后排查了一遍代码,基本确认 ...

  4. ARC模式下的内存泄露问题

    ARC模式下的内存泄露问题 iOS提供的ARC 功能很大程度上简化了编程,让内存管理变得越来越简单,但是ARC并不是说不会发生内存泄露,使用不当照样会发生. 以下列举两种内存泄露情况: 死循环造成的内 ...

  5. Java虚拟机6:内存溢出和内存泄露、并行和并发、Minor GC和Full GC、Client模式和Server模式的区别

    前言 之前的文章尤其是讲解GC的时候提到了很多的概念,比如内存溢出和内存泄露.并行与并发.Client模式和Server模式.Minor GC和Full GC,本文详细讲解下这些概念的区别. 内存溢出 ...

  6. JMM中的重排序及内存屏障

    目录 1. 概述 2. 重排序 2-1. as-if-serial语义 2-2. 重排序的种类 2-3. 从Java源代码到最终实际执行的指令序列, 会分别经历下面3中重排序. 3. 内存屏障类型 3 ...

  7. 使用WinIO库实现保护模式下的IO和内存读写

    问题已解决: 原因是函数的调用方式与WinIO中不一致,使用的时候漏掉了__stdcall. 函数原定义为: 在实际的GPIO读写中遇到以下问题: SetPortVal可正常写入,但是GetPortV ...

  8. LevelDB学习笔记 (3): 长文解析memtable、跳表和内存池Arena

    LevelDB学习笔记 (3): 长文解析memtable.跳表和内存池Arena 1. MemTable的基本信息 我们前面说过leveldb的所有数据都会先写入memtable中,在leveldb ...

  9. 【设计模式】学习笔记15:代理模式(Proxy Pattern)

    本文出自   http://blog.csdn.net/shuangde800 本笔记内容: 1. JAVA远程代理调用(RMI) 2. 代理模式 走进代理模式 在上一篇的状态模式中,我们实现了一个糖 ...

  10. Java设计模式学习笔记(三) 工厂方法模式

    前言 本篇是设计模式学习笔记的其中一篇文章,如对其他模式有兴趣,可从该地址查找设计模式学习笔记汇总地址 1. 简介 上一篇博客介绍了简单工厂模式,简单工厂模式存在一个很严重的问题: 就是当系统需要引入 ...

随机推荐

  1. 45分钟从零搭建私有MaaS平台和生产级的Qwen3模型服务

    今天凌晨,阿里通义团队正式发布了 Qwen3,涵盖六款 Dense 模型(0.6B.1.7B.4B.8B.14B.32B)和两款 MoE 模型(30B-A3B 和 235B-A22B).其中的旗舰模型 ...

  2. 解析异步消息加上(→_→)@SuppressLint("HandlerLeak")

    在主线程又Handler处理消息出现时这样写会有警告 private Handler handler = new Handler(){ @Override public void handleMess ...

  3. HttpServletResponse相关

    目录 返回给客户端数据:字节流和字符流 解决返回字节流和字符流乱码问题 万能解决乱码 文件下载 tomcat提供的默认下载方式 手动编写的DownloadServlet 返回给客户端数据:字节流和字符 ...

  4. 关于HarmonyOS NEXT中的模块化开发

    今天不写页面和动画,斗胆给大家讲一讲软件工程. 软件工程讲究高内聚低耦合,意思就是把整个工程按照分工不同分成不同的模块,每一个模块紧密联系又互不影响.就像一座摩天大楼,它里面的电路网非常庞大和复杂,它 ...

  5. 如何在 AI 小助手对话中显示原文预览

    写在前面 本文使用的开源工具平台包含以下内容: AI 助手平台:MaxKB 运维管理面板:1Panel Linux运维管理面板 一.整理原文链接 如果现有的文档已经有能够下载的链接或者预览链接,可以跳 ...

  6. 梯度下降(Gradient Descent)法

    梯度下降法(Gradient Descent)是求解无约束最优化问题最常用的方法之一,它是一种迭代方法,每一步的主要操作就是求解目标函数的梯度向量,将当前位置的负梯度方向作为搜索方向. 直观的表示可用 ...

  7. QuickSort之C#实现

    /// <summary> /// 快速排序中的切分 /// lIndex已经是基准值,i记录基准值的大小值的边界,j记录目前遍历的边界: /// i值必须从lIndex+1开始,因为基准 ...

  8. 安装Scikit-learn 0.24.2版本

    安装Scikit-learn 0.24.2版本 Anaconda 安装scikit-learn是0.19版本,目前最新版本是0.24.2,之前的版本与现在的版本之间差距还是比较大,因此有必要对scik ...

  9. My Calendar III——LeetCode⑪

    //原题链接https://leetcode.com/problems/my-calendar-iii/submissions/ 题目描述 Implement a MyCalendarThree cl ...

  10. Linux服务器(CentOS/Ubuntu)接口Bond模式详解、配置指南及交换机对应接口的配置示例

    以下是关于Linux服务器(CentOS/Ubuntu)与交换机对接的接口Bond模式详解.配置指南及交换机配置示例(思科/华为/华三) 的全面说明: 一.Linux Bonding 模式对比 模式 ...