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. [数据库/SQL] 浅谈DDL、DSL、DCL、DML、DQL

    概念辨析:SQL.DQL.DML.DDL.DCL SQL(Structure Query Language, 结构化查询语言)语言是数据库的核心语言. SQL的发展是从1974年开始的,其发展过程如下 ...

  2. 代码随想录第十五天 | Leecode 110. 平衡二叉树、257. 二叉树的所有路径、404. 左叶子之和、222. 完全二叉树的节点个数

    Leecode 110. 平衡二叉树 题目描述 给定一个二叉树,判断它是否是 平衡二叉树(是指该树所有节点的左右子树的高度相差不超过 1.) 示例 1: 输入:root = [3,9,20,null, ...

  3. Axure RP医疗在线挂号问诊原型图医院APP原形模板

    Axure RP医疗在线挂号问诊原型图医院APP原形模板 医疗在线挂号问诊Axure RP原型图医院APP原形模板,是一款原创的医疗类APP,设计尺寸采用iPhone13(375*812px),原型图 ...

  4. Visual Studio 快捷键(收藏)

    代码编辑器的展开和折叠代码确实很方便和实用.以下是展开代码和折叠代码所用到的快捷键,很常用: Ctrl + M + O: 折叠所有方法 Ctrl + M + M: 折叠或者展开当前方法 Ctrl + ...

  5. Web前端入门第 58 问:JavaScript 运算符 == 和 === 有什么区别?

    运算符 JavaScript 运算符是真的多,尤其是 ES6 之后还在不停的加运算符,其他编程语言看 JS 就像怪物一样,各种骚操作不断~~ 运算符分类 1.算术运算符 算术运算符的作用就是用来基础计 ...

  6. Ribbon过滤器原理解析

    Ribbon过滤器整体看是一个矩阵构建与矩阵乘法,RocksDB中对它的实现是进行了合理的空间.时间上的优化的. 符号 整个过滤器都和矩阵计算CS=R相关,C是\(n*n\)矩阵,S是\(n*m\)矩 ...

  7. 在云服务器上开MC-Forge服

    在云服务器上开MC-Forge服 记录一下在云服务器上开mc-1.16.5-Forge服. OS: Ubuntu 22.04.2 LTS x86_64 CPU: Intel Xeon Platinum ...

  8. 巧用指标平台DataIndex,五步法轻松实现指标管理

    开发部门在做指标加工的全流程中,是否经常出现如下问题: · 业务部门看指标数据的时候,看到两个名称相似的指标,不清楚两个指标的差异性,来咨询开发部门指标计算口径,开发部门配合业务部门翻找代码,找出指标 ...

  9. 4.Java SDK源码分析系列笔记-LinkedList

    目录 1. 是什么 2. 如何使用 3. 原理分析 3.1. uml 3.2. 构造方法 3.3. add方法 3.3.1. 确保容量足够容纳新的元素 3.3.2. 把元素放入数组最后一个位置 3.4 ...

  10. 【机器人】—— 1. ROS 概述与环境搭建

    1. ROS 简介 1.1 ROS 诞生背景 机器人是一种高度复杂的系统性实现,机器人设计包含了机械加工.机械结构设计.硬件设计.嵌入式软件设计.上层软件设计....是各种硬件与软件集成,甚至可以说机 ...