Netty源码:深入理解SimpleChannelInboundHandler

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

    因项目需要,需要了解 Netty 这款号称 "高性能Java网络编程" 框架。拿起一本《Netty In Action》开始研究,在第2章的例子中,发现 Echo 服务端使用的ChannelHandler是 ChannelInboundHandlerAdapter ,而 Echo 客户端使用的却是 SimpleChannelInboundHandler 。一脸茫然,不知所措,只能点进去看各自的实现原理。

一.SimpleChannelInboundHandler

    首先看到的是 SimpleChannelInboundHandler 继承自 ChannelInboundHandlerAdapter。

public abstract class SimpleChannelInboundHandler extends ChannelInboundHandlerAdapter{ ... }

    有图有真相:

Netty源码:深入理解SimpleChannelInboundHandler_第1张图片

    既然是继承关系,也就是说,"你有的我也有,你没有的我还有。" 那么 SimpleChannelInboundHandler 里面肯定重写或者新增了 ChannelInboundHandlerAdapter 里面的方法功能 - channelRead0 和 channelRead()。

protected abstract void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception;

    这里只提供了一个模板,作用是把处理逻辑不变的内容写好在 channelRead(ctx,msg) 中,并且在里面调用 channelRead0 ,这样变化的内容通过抽象方法实现传递到子类中去了(在Netty5中channelRead0已被重命名为messageReceived)。

    @Override// 继承了 ChannelInboundHandlerAdapter#channelRead
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        boolean release = true;
        try {
            if (acceptInboundMessage(msg)) {
                @SuppressWarnings("unchecked")
                I imsg = (I) msg;
                channelRead0(ctx, imsg);// 模板方法,抽出可变的部分,具体实现在子类中
            } else {
                release = false;
                ctx.fireChannelRead(msg);
            }
        } finally {
            if (autoRelease && release) {
                ReferenceCountUtil.release(msg);// 释放资源(保存消息的ByteBuf)
            }
        }
    }

    为什么这样做?看看《Netty In Action》的原话:

在客户端,当 channelRead0() 方法完成时,你已经有了传入消息,并且已经处理完它了。当该方法返回时,SimpleChannelInboundHandler负责释放指向保存该消息的ByteBuf的内存引用。

    那为什么服务端不需要这样处理呢?

在EchoServerHandler中,你仍然需要将传入消息回送给发送者,而 write() 操作是异步的,直到 channelRead() 方法返回后可能仍然没有完成。为此,EchoServerHandler扩展了 ChannelInboundHandlerAdapter ,其在这个时间点上不会释放消息。

    啥意思,我的理解是 ChannelInboundHandlerAdapter 不会像 SimpleChannelInboundHandler 一样在 channelRead() 里面释放资源,而是在收到消息处理完成的事件时,才会释放资源,看下面的代码就能理解了。

二.ChannelInboundHandlerAdapter

    EchoServerHandler#channelReadComplete,这是一个EchoServer小例子:

    public void channelReadComplete(ChannelHandlerContext ctx)
            throws Exception {
        //将未决消息冲刷到远程节点,并且关闭该 Channel
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
                .addListener(ChannelFutureListener.CLOSE);
    }

    消息在 channelReadComplete() 方法中,当 writeAndFlush() 方法被调用时才被释放,我们点进去源码验证一下:

    AbstractChannelHandlerContext#writeAndFlush,如下所示,最后的资源是在 writeAndFlush() 中被释放的。

    public ChannelFuture writeAndFlush(Object msg) {
        return writeAndFlush(msg, newPromise());// 跳转到另外一个重载的方法中
    }
    public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
        if (msg == null) {// msg不能为空
            throw new NullPointerException("msg");
        }
        if (isNotValidPromise(promise, true)) {
            ReferenceCountUtil.release(msg);// 释放资源(保存消息的ByteBuf)
            // cancelled
            return promise;
        }
        write(msg, true, promise);// 异步写操作
        return promise;
    }

三.扩展 - ReferenceCountUtil

    上面的源码中,最后资源是通过 ReferenceCountUtil 来释放的。也就是说,当我们需要释放ByteBuf相关内存的时候,也可以使用 ReferenceCountUtil#release()。

    ReferenceCountUtil 底层实现是 ReferenceCounted ,当新的对象初始化的时候计数为1,retain() 方法被调用时引用计数加1,release()方法被调用时引用计数减1,当计数减少到0的时候会被显示清除,再次访问被清除的对象会出现访问冲突(这里想起了JVM判断对象是否存活的引用计数算法)。

    ReferenceCountUtil#release:

    public static boolean release(Object msg) {
        if (msg instanceof ReferenceCounted) {
            return ((ReferenceCounted) msg).release();// Decreases the reference count by 1
        }
        return false;
    }

 

转载于:https://my.oschina.net/javamaster/blog/2994961

你可能感兴趣的:(Netty源码:深入理解SimpleChannelInboundHandler)