session.write类型引发的思考---Mina Session.write流程探索.doc--zhengli
基于Mina开发网络通信程序,在传感器数据接入领域应用的很广泛,今天我无意中发现一个问题,那就是我在前端session.write(msg)数据出去之后,却没有经过Filter的Encoder方法,同样能够写入远程服务器。因为我所发送的数据不需要很复杂的编码,所以encoder方法也一直没有去看,今天发现无法被自己写的过滤器所编码,针对这个问题,我打开以前的代码以及以前的项目中的相关代码,有些同事也是session.write(IoBuffer)之后,在encoder方法里面还加上了一句out.write(message);通过跟踪Mina源码发现,session写出去的数据类型是IoBuffer格式的,就不经过自定义的过滤器了。所以下面的代码压根是多余的
- @Override
- public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception {
- out.write(message);//IoBuffer格式写出去之后,跳过了encoder.
- }
下面我把自己跟踪调试Mina的过程记录下来.
一、场景
客户端需要每隔Time时间向服务端发送心跳包,代码如下:
session.write(IoBuffer.wrap("心跳包XXX".getBytes()));
二、现象
MyFilter中的Encoder方法encoder不执行
- public class MyFilter implements ProtocolCodecFactory {
- private ProtocolEncoder encoder = new MyEncoder();
- private ProtocolDecoder decoder = new MyDecoder();
- @Override
- public ProtocolEncoder getEncoder(IoSession session) throws Exception {
- return encoder;
- }
- @Override
- public ProtocolDecoder getDecoder(IoSession session) throws Exception {
- return decoder;
- }
- }
三、分析
进入session.write方法,实现IoSession.write方法的是AbstractIoSession。直接调用的是
- public WriteFuture write(Object message) {
- return write(message, null);
- }
而AbstractIoSession.write(Object message, SocketAddress address)
该方法的工作流程是:
- 创建WriteFeature对象,用于返回值(session.write本身就是返回writeFeature)
- 将session.write(message)中的Object类型的message封装成writeRequest.
- 启动write动作,这个主要是IoFilterChain来完成的。
具体的核心代码如下:
- // Now, we can write the message. First, create a future
- WriteFuture writeFuture = new DefaultWriteFuture(this);
- WriteRequest writeRequest = new DefaultWriteRequest(message, writeFuture, remoteAddress);
- // Then, get the chain and inject the WriteRequest into it
- IoFilterChain filterChain = getFilterChain();
- filterChain.fireFilterWrite(writeRequest);
继续跟踪到fireFilterWrite里面去,可知IoFilterChain的默认实现类DefaultIoFilterChain中的关键方法:
- public void fireFilterWrite(WriteRequest writeRequest) {
- Entry tail = this.tail;
- callPreviousFilterWrite(tail, session, writeRequest);
- }
在这里先要介绍一下DefaultIoFiterChain的数据格式,主要的属性如下:
- private final Map<String, Entry> name2entry = new ConcurrentHashMap<String, Entry>();
- /** The chain head */
- private final EntryImpl head;
- /** The chain tail */
- private final EntryImpl tail;
其中 head与tail都是DefaultIoFilterChain固有的属性,name2entity是我们为FilterChain添加的过滤器。因而IoFilterChain是用一个链表来保存过滤器的(('tail', prev: 'myFilter:ProtocolCodecFilter', next: 'null')),其中表头和表位都是固定的head和tail,他们对应的Filter也是专有的,HeadFilter和TailFilter.
关键方法是callPreviousFilterWrite(tail, session, writeRequest);
- try {
- IoFilter filter = entry.getFilter();
- NextFilter nextFilter = entry.getNextFilter();
- filter.filterWrite(nextFilter, session, writeRequest);
- } catch (Throwable e) {
- writeRequest.getFuture().setException(e);
- fireExceptionCaught(e);
- }
从上面两个代码片段中,可以看出,IoFilterChain首先从列表中找到tail,从tail开始查找filter,顺序调用每个filter的filterWrite()方法。这里的‘顺序调用’,指的是从tail->head调用,也就是逆向调用Filter。但是看到filter.filterWrite(nextFilter, session, writeRequest);这行代码中的参数可以发现,nextFilter,表面的意思是下一个过滤器,有点误解,感觉tail下一个过滤器不就是null吗,其实不然,进入filterWriter可知。
- Entry nextEntry = EntryImpl.this.prevEntry;
- callPreviousFilterWrite(nextEntry, session, writeRequest);
对于除head和tail过滤器外,其他的过滤器是如何工作的呢?我们看看ProtocolCodecFilter中的fireFilter方法,做了这样的处理:
- if ((message instanceof IoBuffer) || (message instanceof FileRegion)) {
- nextFilter.filterWrite(session, writeRequest);
- return;
- }
到这里,就明白了为什么session.write(IoBuffer.wrap())这样写出去,无法经过自己定义的过滤器了,原来在fireFilter中,对message做了判断,如果已经是IoBuffer类型的,就直接return了。
最后执行的是HeadFilter的fireFilter方法,直接看内容:
- if (writeRequest.getMessage() instanceof IoBuffer) {
- IoBuffer buffer = (IoBuffer) writeRequest.getMessage();
- // I/O processor implementation will call buffer.reset()
- // it after the write operation is finished, because
- // the buffer will be specified with messageSent event.
- buffer.mark();
- int remaining = buffer.remaining();
- if (remaining == 0) {
- // Zero-sized buffer means the internal message
- // delimiter.
- s.increaseScheduledWriteMessages();
- } else {
- s.increaseScheduledWriteBytes(remaining);
- }
- } else {
- s.increaseScheduledWriteMessages();
- }
- s.getWriteRequestQueue().offer(s, writeRequest);
- if (!s.isWriteSuspended()) {
- s.getProcessor().flush(s);
- }
WriteRequestQueue的默认实现就是java.util.concurrent.ConcurrentLinkedQueue,舍去传入的session对象。
session.write类型引发的思考---Mina Session.write流程探索.doc--zhengli的更多相关文章
- Mina Session
Chapter 4 - Session The Session is at the heart of MINA : every time a client connects to the server ...
- day58_9_24多对多建表手动,form组件(判断类型),cookies和session
一.多对多建表关系之手动添加. 1.全自动 像之前讲过的一样,我们可以通过manytomanyField的字段来建立多对多关系: class Book(models.Model): title = m ...
- 由SecureCRT引发的思考和学习
由SecureCRT引发的思考和学习 http://mp.weixin.qq.com/s?__biz=MzAxOTAzMDEwMA==&mid=2652500597&idx=1& ...
- SQLAlchemy并发写入引发的思考
背景 近期公司项目中加了一个积分机制,用户登录签到会获取登录积分,但会出现一种现象就是用户登录时会增加双倍积分,然后生成两个积分记录.此为问题 问题分析 项目采用微服务架构,下图为积分机制流程 ...
- class_copyIvarList方法获取实例变量问题引发的思考
在runtime.h中,你可以通过其中的一个方法来获取实例变量,那就是class_copyIvarList方法,具体的实现如下: - (NSArray *)ivarArray:(Class)cls { ...
- 通过session的id号获取对应的session
说说为什么要用session!!! 每次访问端通过普通http协议访问tomcat时,访问端包括网页或Android app等,tomcat都会自动生成一个不同的session,而且session的i ...
- 一个ScheduledExecutorService启动的Java线程无故挂掉引发的思考
2018年12月12日18:44:53 一个ScheduledExecutorService启动的Java线程无故挂掉引发的思考 案件现场 不久前,在开发改造公司一个端到端监控日志系统的时候,出现了一 ...
- int.TryParse非预期执行引发的思考 ASP.NET -- WebForm -- 给图片添加水印标记 Windows -- 使用批处理文件.bat删除旧文件
int.TryParse非预期执行引发的思考 问题出现 这天在写一个页面,想谨慎些就用了int.TryParse,结果出问题了. 代码如下: Copy int id = 1000; //Reque ...
- 由C# dynamic是否装箱引发的思考
前言 前几天在技术群里看到有同学在讨论关于dynamic是否会存在装箱拆箱的问题,我当时第一想法是"会".至于为啥会有很多人有这种疑问,主要是因为觉得dynamic可能是因为有点特 ...
随机推荐
- Spark源码分析之一:Job提交运行总流程概述
Spark是一个基于内存的分布式计算框架,运行在其上的应用程序,按照Action被划分为一个个Job,而Job提交运行的总流程,大致分为两个阶段: 1.Stage划分与提交 (1)Job按照RDD之间 ...
- 【Python基础】之异常
一.常见异常 try: open('abc.txt','r') except FileNotFoundError: print('异常啦!') 输出结果: ======= 异常啦! 我们通过 open ...
- erlang中通过ip和子网掩码,计算地址范围 【二进制和十进制的转换】
在程序中,难免用的二进制和十进制之间的转换.遇到一个场景,通过ip和子网掩码,计算地址范围. 而地址范围为:网络地址+1—— 广播地址 -1 . 网络地址即ip和子网掩码的与的位运算.广播地址为:网 ...
- 华为基于策略划分VLAN的配置方法及示例
学过思科交换机的朋友,可能对基于策略划分VLAN的配置方法印象非常深,感觉确实比较复杂,先要配置VMPS以及VMPS数据库,但在华为交换机中,这种现象得到了彻底改变,因为它有了一种特殊的端口类型—— ...
- linux 安装mongo
在Linux中安装Mongodb操作说明 MongoDB配置 版本说明:因本机所装Red Hat 为 64位操作系统故本例以64位的MongDB为例.所用版本如下: (1) Red Hat En ...
- 网页直播、微信直播技术解决方案:EasyNVR与EasyDSS流媒体服务器组合之区分不同场景下的easynvr
近期遇到好多客户咨询关于实现微信直播.或者是将直播页面集成进入自己项目中. 该方案的主要目的:完成在公网一直进行内网摄像头的RTMP/HLS直播! 实现方案的具体实现: EasyNVR+EasyDSS ...
- EasyNVR摄像机H5流媒体服务器在windows上批处理脚本自动以管理员权限运行
很多时候, 我们需要以管理员权限来运行批处理脚本, 比如操作 windows 服务. EasyNVR 中提供安装服务的批处理脚本, 运行这个bat文件, 自动将 EasyNVR 以 windows 服 ...
- Upgrading Elasticsearch
Upgrading Elasticsearch | Elasticsearch Reference [5.6] | Elastic https://www.elastic.co/guide/en/el ...
- mysql系列之1.mysql基础
非关系型(NOSQL)数据库 键值存储数据库: memcached / redis / memcachedb / Berkeley db 列存储数据库: Cassandra / Hba ...
- 我的Java开发学习之旅------>Java经典排序算法之二分插入排序
一.折半插入排序(二分插入排序) 将直接插入排序中寻找A[i]的插入位置的方法改为采用折半比较,即可得到折半插入排序算法.在处理A[i]时,A[0]--A[i-1]已经按关键码值排好序.所谓折半比较, ...