Mina过滤器链原理

Mina中的过滤器,位于ioService和ioHandler之间,用于在正式的业务处理之前,做一些额外的预处理或者过滤。比如,编解码过滤器,日志过滤器,心跳过滤器等。下图是mina官网上的一个mina应用的结构图:

                        


多个过滤器共同组成过滤器链,这篇文章对mina中的过滤器的自定义、如何进入和退出过滤器链、过滤器之间的传递、过滤器链中过滤器的顺序问题等做一个研究。

mina中默认使用的过滤器链是DefaultIoFilterChain,对于过滤器和过滤器链,基本上所有的需要了解的关键都在这个类里面。


1. 自定义过滤器。

    这个问题不是本篇的重点,简单说一下就行。比如,我们需要在mina应用中插入日志功能,mina中有一个现在的LoggingFilter,插入即可:

    DefaultIoFilterChain.addLast(new LoggingFilter())


2. 如何进入过滤器链

   比如说,服务端要写数据到客户端,流程是什么时候进入过滤器链的?

   要发送数据,首先在handler里面调用iosession.write(msg), 稍微一跟踪,就会发现,write方法中,调用了过滤器链的fireFilterWrite()方法:

   

  filterChain.fireFilterWrite(writeRequest);

   这就进入了过滤器链,然后接着跟踪,最终会按顺序调用一个个过滤器的filterWrite()方法:

   

  filter.filterWrite(nextFilter, session, writeRequest);

   在查看DefaultIoFilterChain类中的源码,fire开头的方法很多,如fireMessageReceived、fireSessionClosed,。。。这些方法的意思也很明显,触发相关的事件。


3. 如何退出过滤器链

   现在考虑一个问题,一般的过滤器,他们的事件处理方法中,处理完之后,一般都会调用下一个过滤器的处理方法,如:LoggingFilter中messageReceived方法:

 

    @Override
    public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception {
        log(messageReceivedLevel, "RECEIVED: {}", message);
        nextFilter.messageReceived(session, message);
    }

我们知道,所有过滤器都处理完了之后,messageReceived会转交给IoHandler处理,这件事应该是最后一个过滤器来做,但是自己的业务代码中从来没有写过把消息转交给handler的工作。猜测:DefaultIoFilterChain中应该有一个最后把关的过滤器,来做这个事情,所以翻开代码继续查看:


DefaultIoFilterChain有一个head和tail,构造DefaultIoFilterChain时,对这两个成员进行了初始化:

public DefaultIoFilterChain(AbstractIoSession session) {
    if (session == null) {
        throw new IllegalArgumentException("session");
    }

    this.session = session;
    head = new EntryImpl(null, null, "head", new HeadFilter());
    tail = new EntryImpl(head, null, "tail", new TailFilter());
    head.nextEntry = tail;
}

head和tail是一个EntryImpl,里面包含filter。从这里看出head包含一个HeadFilter,tail包含一个TailFilter。

根据猜测,TailFilter是最后一个filter,那么我们看看他的messageReceived是怎么写的:

session.getHandler().messageReceived(s, message);

大部分省略,上面这句是重点,调用了handler的方法,猜测得到验证。

配合DefaultIoFilterChain的addLast、addFirst等方法,我们发现,不管我们往过滤器链中插入了多少过滤器,head始终作为第一个节点,而tail始终作为最后一个。

至于head有什么作为,下面会讲。


4. 过滤器的传递方向问题

   继续说write,写数据进入过滤器链中,调用fireFilterWrite方法,在这个方法中调用了另一个方法:callPreviousFilterWrite,这个方法有点奇怪,调用前一个filterWrite方法?

   在其他的fire方法中,调用的是next,比如fireMessageReceived中,调用了callNextMessageReceived,调用下一个的messageReceived方法。那为什么在fireFilterWrite 中,要调用前一个的filterWrite方法呢?方法的逻辑是否真和方法名所表明的一样呢?

public void fireFilterWrite(WriteRequest writeRequest) {
        callPreviousFilterWrite(tail, session, writeRequest);
    }

    private void callPreviousFilterWrite(Entry entry, IoSession session, WriteRequest writeRequest) {
        try {
            IoFilter filter = entry.getFilter();
            NextFilter nextFilter = entry.getNextFilter();
            filter.filterWrite(nextFilter, session, writeRequest);
        } catch (Exception e) {
            writeRequest.getFuture().setException(e);
            fireExceptionCaught(e);
        } catch (Error e) {
            writeRequest.getFuture().setException(e);
            fireExceptionCaught(e);
            throw e;
        }
    }

查看源码发现,fireFilterWrite方法确实是从tail开始的,首先调用了tail中的filter的filterWrite方法,跟踪发现Tail的filterWrite方法中又调用了他的下一个filter的filterWrite:

@Override
public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception {
     nextFilter.filterWrite(session, writeRequest);
}

不仅如此,其他继承了IoFilterAdapter的普通filter,也都是调用了nextFilter.filterWrite(session, writeRequest); 既然调用的是nextFilter,为什么叫callPreviousFilterWrite?

在创建每个过滤器entry的时候,构造了他的nextFilter,我们发现,每个过滤器的构造,都是一个EntryImpl对象,我们看看这个对象的源码,构造方法中有这么一段:

this.nextFilter = new NextFilter() {
                public void sessionCreated(IoSession session) {
                    Entry nextEntry = EntryImpl.this.nextEntry;
                    callNextSessionCreated(nextEntry, session);
                }

                public void sessionOpened(IoSession session) {
                    Entry nextEntry = EntryImpl.this.nextEntry;
                    callNextSessionOpened(nextEntry, session);
                }

                public void sessionClosed(IoSession session) {
                    Entry nextEntry = EntryImpl.this.nextEntry;
                    callNextSessionClosed(nextEntry, session);
                }

                public void sessionIdle(IoSession session, IdleStatus status) {
                    Entry nextEntry = EntryImpl.this.nextEntry;
                    callNextSessionIdle(nextEntry, session, status);
                }

                public void exceptionCaught(IoSession session, Throwable cause) {
                    Entry nextEntry = EntryImpl.this.nextEntry;
                    callNextExceptionCaught(nextEntry, session, cause);
                }

                public void inputClosed(IoSession session) {
                    Entry nextEntry = EntryImpl.this.nextEntry;
                    callNextInputClosed(nextEntry, session);
                }

                public void messageReceived(IoSession session, Object message) {
                    Entry nextEntry = EntryImpl.this.nextEntry;
                    callNextMessageReceived(nextEntry, session, message);
                }

                public void messageSent(IoSession session, WriteRequest writeRequest) {
                    Entry nextEntry = EntryImpl.this.nextEntry;
                    callNextMessageSent(nextEntry, session, writeRequest);
                }

                public void filterWrite(IoSession session, WriteRequest writeRequest) {
                    Entry nextEntry = EntryImpl.this.prevEntry;
                    callPreviousFilterWrite(nextEntry, session, writeRequest);
                }

                public void filterClose(IoSession session) {
                    Entry nextEntry = EntryImpl.this.prevEntry;
                    callPreviousFilterClose(nextEntry, session);
                }

                public String toString() {
                    return EntryImpl.this.nextEntry.name;
                }
            };

这里定义了nextFilter, 注意这里面filterWrite方法和 filterClose方法的不同:

Entry nextEntry = EntryImpl.this.prevEntry;
callPreviousFilterWrite(nextEntry, session, writeRequest);
把prevEntry赋给了nextENtry, 与messageReceived刚好相反,这样就一清二楚了,所谓的nextEntry其实是prevEntry。那么我们继续猜测,在写的时候,head将会是最后一个过滤器,负责把消息交给下层的ioProcessor处理,看看HeadFilter的源码:

private class HeadFilter extends IoFilterAdapter {
        @SuppressWarnings("unchecked")
        @Override
        public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception {

            AbstractIoSession s = (AbstractIoSession) session;

            // Maintain counters.
            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) {
                    s.increaseScheduledWriteBytes(remaining);
                }
            } else {
                s.increaseScheduledWriteMessages();
            }

            WriteRequestQueue writeRequestQueue = s.getWriteRequestQueue();

            if (!s.isWriteSuspended()) {
                if (writeRequestQueue.isEmpty(session)) {
                    // We can write directly the message
                    s.getProcessor().write(s, writeRequest);
                } else {
                    s.getWriteRequestQueue().offer(s, writeRequest);
                    s.getProcessor().flush(s);
                }
            } else {
                s.getWriteRequestQueue().offer(s, writeRequest);
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        public void filterClose(NextFilter nextFilter, IoSession session) throws Exception {
            ((AbstractIoSession) session).getProcessor().remove(session);
        }
    }

重点是下面这句:

if (writeRequestQueue.isEmpty(session)) {
      // We can write directly the message
      s.getProcessor().write(s, writeRequest);
} else {
      s.getWriteRequestQueue().offer(s, writeRequest);
      s.getProcessor().flush(s);
}

再次得到证实。


总结:

我们发现,NextFilter中,只有filterClose方法和filterWrite相同,把prevEntry作为nextEntry。

这种现象的本质是,写和连接关闭的事件,在过滤器链中的传递方向,与其他事件的传递方向是刚好相反的,这是为什么?


上图F1、F2、F3是过滤器链中的三个过滤器,事件流的方向有A和B两种方向,服务端向客户端写数据,是一种handler主动的操作,在handler中调用session的write方法,所以事件流是A方向,而当有消息收到时,事件流的方向是B,其他类似。

再假设,F1和F2是两个不同的编解码器,当有消息来时,首先经过F2解码,再经过F1解码,最后交给handler处理。那么,当要发送消息的时候,要对消息进行编码,是不是应该先经过F1编码,然后才让F2编码,如果反了,客户端解码就得不到正确结果。当然,这样做是基于服务端和客户端具有完全相同的过滤器链(包括过滤器顺序也要相同), 这是一个基本原则。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值