基于Mina开发网络通信程序,在传感器数据接入领域应用的很广泛,今天我无意中发现一个问题,那就是我在前端session.write(msg)数据出去之后,却没有经过Filter的Encoder方法,同样能够写入远程服务器。因为我所发送的数据不需要很复杂的编码,所以encoder方法也一直没有去看,今天发现无法被自己写的过滤器所编码,针对这个问题,我打开以前的代码以及以前的项目中的相关代码,有些同事也是session.write(IoBuffer)之后,在encoder方法里面还加上了一句out.write(message);通过跟踪Mina源码发现,session写出去的数据类型是IoBuffer格式的,就不经过自定义的过滤器了。所以下面的代码压根是多余的

  1. @Override
  2. public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception {
  3. out.write(message);//IoBuffer格式写出去之后,跳过了encoder.
  4. }

下面我把自己跟踪调试Mina的过程记录下来. 
一、场景 
客户端需要每隔Time时间向服务端发送心跳包,代码如下: 
session.write(IoBuffer.wrap("心跳包XXX".getBytes())); 
二、现象 
MyFilter中的Encoder方法encoder不执行

  1. public class MyFilter implements ProtocolCodecFactory {
  2. private ProtocolEncoder encoder = new MyEncoder();
  3. private ProtocolDecoder decoder = new MyDecoder();
  4. @Override
  5. public ProtocolEncoder getEncoder(IoSession session) throws Exception {
  6. return encoder;
  7. }
  8. @Override
  9. public ProtocolDecoder getDecoder(IoSession session) throws Exception {
  10. return decoder;
  11. }
  12. }

三、分析 
进入session.write方法,实现IoSession.write方法的是AbstractIoSession。直接调用的是

  1. public WriteFuture write(Object message) {
  2. return write(message, null);
  3. }

而AbstractIoSession.write(Object message, SocketAddress address) 
该方法的工作流程是:

  • 创建WriteFeature对象,用于返回值(session.write本身就是返回writeFeature)
  • 将session.write(message)中的Object类型的message封装成writeRequest.
  • 启动write动作,这个主要是IoFilterChain来完成的。

具体的核心代码如下:

  1. // Now, we can write the message. First, create a future
  2. WriteFuture writeFuture = new DefaultWriteFuture(this);
  3. WriteRequest writeRequest = new DefaultWriteRequest(message, writeFuture, remoteAddress);
  4. // Then, get the chain and inject the WriteRequest into it
  5. IoFilterChain filterChain = getFilterChain();
  6. filterChain.fireFilterWrite(writeRequest);

继续跟踪到fireFilterWrite里面去,可知IoFilterChain的默认实现类DefaultIoFilterChain中的关键方法:

  1. public void fireFilterWrite(WriteRequest writeRequest) {
  2. Entry tail = this.tail;
  3. callPreviousFilterWrite(tail, session, writeRequest);
  4. }

在这里先要介绍一下DefaultIoFiterChain的数据格式,主要的属性如下:

  1. private final Map<String, Entry> name2entry = new ConcurrentHashMap<String, Entry>();
  2. /** The chain head */
  3. private final EntryImpl head;
  4. /** The chain tail */
  5. 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);

  1. try {
  2. IoFilter filter = entry.getFilter();
  3. NextFilter nextFilter = entry.getNextFilter();
  4. filter.filterWrite(nextFilter, session, writeRequest);
  5. } catch (Throwable e) {
  6. writeRequest.getFuture().setException(e);
  7. fireExceptionCaught(e);
  8. }

从上面两个代码片段中,可以看出,IoFilterChain首先从列表中找到tail,从tail开始查找filter,顺序调用每个filter的filterWrite()方法。这里的‘顺序调用’,指的是从tail->head调用,也就是逆向调用Filter。但是看到filter.filterWrite(nextFilter, session, writeRequest);这行代码中的参数可以发现,nextFilter,表面的意思是下一个过滤器,有点误解,感觉tail下一个过滤器不就是null吗,其实不然,进入filterWriter可知。

  1. Entry nextEntry = EntryImpl.this.prevEntry;
  2. callPreviousFilterWrite(nextEntry, session, writeRequest);

对于除head和tail过滤器外,其他的过滤器是如何工作的呢?我们看看ProtocolCodecFilter中的fireFilter方法,做了这样的处理:

  1. if ((message instanceof IoBuffer) || (message instanceof FileRegion)) {
  2. nextFilter.filterWrite(session, writeRequest);
  3. return;
  4. }

到这里,就明白了为什么session.write(IoBuffer.wrap())这样写出去,无法经过自己定义的过滤器了,原来在fireFilter中,对message做了判断,如果已经是IoBuffer类型的,就直接return了。 
最后执行的是HeadFilter的fireFilter方法,直接看内容:

  1. if (writeRequest.getMessage() instanceof IoBuffer) {
  2. IoBuffer buffer = (IoBuffer) writeRequest.getMessage();
  3. // I/O processor implementation will call buffer.reset()
  4. // it after the write operation is finished, because
  5. // the buffer will be specified with messageSent event.
  6. buffer.mark();
  7. int remaining = buffer.remaining();
  8. if (remaining == 0) {
  9. // Zero-sized buffer means the internal message
  10. // delimiter.
  11. s.increaseScheduledWriteMessages();
  12. } else {
  13. s.increaseScheduledWriteBytes(remaining);
  14. }
  15. } else {
  16. s.increaseScheduledWriteMessages();
  17. }
  18. s.getWriteRequestQueue().offer(s, writeRequest);
  19. if (!s.isWriteSuspended()) {
  20. s.getProcessor().flush(s);
  21. }

WriteRequestQueue的默认实现就是java.util.concurrent.ConcurrentLinkedQueue,舍去传入的session对象。

session.write类型引发的思考---Mina Session.write流程探索.doc--zhengli的更多相关文章

  1. Mina Session

    Chapter 4 - Session The Session is at the heart of MINA : every time a client connects to the server ...

  2. day58_9_24多对多建表手动,form组件(判断类型),cookies和session

    一.多对多建表关系之手动添加. 1.全自动 像之前讲过的一样,我们可以通过manytomanyField的字段来建立多对多关系: class Book(models.Model): title = m ...

  3. 由SecureCRT引发的思考和学习

    由SecureCRT引发的思考和学习 http://mp.weixin.qq.com/s?__biz=MzAxOTAzMDEwMA==&mid=2652500597&idx=1& ...

  4. SQLAlchemy并发写入引发的思考

    背景 近期公司项目中加了一个积分机制,用户登录签到会获取登录积分,但会出现一种现象就是用户登录时会增加双倍积分,然后生成两个积分记录.此为问题  问题分析 项目采用微服务架构,下图为积分机制流程   ...

  5. class_copyIvarList方法获取实例变量问题引发的思考

    在runtime.h中,你可以通过其中的一个方法来获取实例变量,那就是class_copyIvarList方法,具体的实现如下: - (NSArray *)ivarArray:(Class)cls { ...

  6. 通过session的id号获取对应的session

    说说为什么要用session!!! 每次访问端通过普通http协议访问tomcat时,访问端包括网页或Android app等,tomcat都会自动生成一个不同的session,而且session的i ...

  7. 一个ScheduledExecutorService启动的Java线程无故挂掉引发的思考

    2018年12月12日18:44:53 一个ScheduledExecutorService启动的Java线程无故挂掉引发的思考 案件现场 不久前,在开发改造公司一个端到端监控日志系统的时候,出现了一 ...

  8. int.TryParse非预期执行引发的思考 ASP.NET -- WebForm -- 给图片添加水印标记 Windows -- 使用批处理文件.bat删除旧文件

    int.TryParse非预期执行引发的思考   问题出现 这天在写一个页面,想谨慎些就用了int.TryParse,结果出问题了. 代码如下: Copy int id = 1000; //Reque ...

  9. 由C# dynamic是否装箱引发的思考

    前言 前几天在技术群里看到有同学在讨论关于dynamic是否会存在装箱拆箱的问题,我当时第一想法是"会".至于为啥会有很多人有这种疑问,主要是因为觉得dynamic可能是因为有点特 ...

随机推荐

  1. XMPP协议概述

    XMPP(Extensible Messaging and Presence Protocol,前称Jabber)是一种以 XML 为基础的开放式实时通信协议,关于它的协议细节,网上已经有太多分析文章 ...

  2. 图像处理之log---log算子

    在图像中,边缘可以看做是位于一阶导数较大的像素处,因此,我们可以求图像的一阶导数来确定图像的边缘,像sobel算子等一系列算子都是基于这个思想的. 但是这存在几个问题:1. 噪声的影响,在噪声点处一阶 ...

  3. 32.10 使用模板更改控件的UI

    32.10  使用模板更改控件的UI 样式是改变WPF控件基本外形的非常好(且非常简单)的方式,它通过为窗口部件的特性设置建立一组默认的值,从而改变WPF控件的基本外形.但是,即使样式允许我们改变各种 ...

  4. pycharm连git和gitee

    http://www.cnblogs.com/feixuelove1009/p/5955332.html https://www.58jb.com/html/171.html

  5. Unix环境高级编程—进程关系

    终端登录 网络登录 进程组 getpgrp(void) setpgid(pid_t pid, pid_) 会话: 是一个或多个进程组的集合,通常由shell的管道将几个进程编成一组. setsid(v ...

  6. 兼容性强、简单、成熟、稳定的RTMPClient客户端拉流功能组件EasyRTMPClient

    EasyRTMPClient EasyRTMPClient拉流功能组件是EasyDarwin流媒体团队开发.提供和维护的一套非常稳定.易用.支持重连的RTMPClient工具,SDK形式提供,全平台支 ...

  7. 九度OJ 1042:Coincidence(公共子序列) (DP)

    时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:2303 解决:1241 题目描述: Find a longest common subsequence of two strings. 输入 ...

  8. 支付宝异步通知notify_url接收不了问题解决(转)

    此处return_url可以成功跳转回网站页面但notify_url却接收不到支付宝的异步通知.已保证notify_url是一个外网可以访问的网址 1.网站用的是ssh框架,当支付宝发通知到我这个ac ...

  9. ubuntu安装opencv(自己编译)

    如果只需要python的,只需pip install opencv-python,然后apt-get一下下面第一点的东东. 1/ sudo apt-get install build-essentia ...

  10. 将socket通信实现多进程

    我们知道,使用TCP协议需要提前建立连接,这样就只能一对一的传输,但是这样感觉十分单一,如果实现一个服务器能同时和多个客户端同信了? 这里就需要用到多线程. 处理的不同之处就在于:每一个接进来的客户都 ...