Netty源碼:深刻理解SimpleChannelInboundHandler

    因項目須要,須要瞭解 Netty 這款號稱 "高性能Java網絡編程" 框架。拿起一本《Netty In Action》開始研究,在第2章的例子中,發現 Echo 服務端使用的ChannelHandler是 ChannelInboundHandlerAdapter ,而 Echo 客戶端使用的倒是 SimpleChannelInboundHandler 。一臉茫然,不知所措,只能點進去看各自的實現原理。java

一.SimpleChannelInboundHandler

    首先看到的是 SimpleChannelInboundHandler 繼承自 ChannelInboundHandlerAdapter。算法

public abstract class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter{ ... }

    有圖有真相:編程

    既然是繼承關係,也就是說,"你有的我也有,你沒有的我還有。" 那麼 SimpleChannelInboundHandler 裏面確定重寫或者新增了 ChannelInboundHandlerAdapter 裏面的方法功能 - channelRead0 和 channelRead()。promise

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的內存引用。異步

    那爲何服務端不須要這樣處理呢?ide

在EchoServerHandler中,你仍然須要將傳入消息回送給發送者,而 write() 操做是異步的,直到 channelRead() 方法返回後可能仍然沒有完成。爲此,EchoServerHandler擴展了 ChannelInboundHandlerAdapter ,其在這個時間點上不會釋放消息。性能

    啥意思,個人理解是 ChannelInboundHandlerAdapter 不會像 SimpleChannelInboundHandler 同樣在 channelRead() 裏面釋放資源,而是在收到消息處理完成的事件時,纔會釋放資源,看下面的代碼就能理解了。spa

二.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;
    }
相關文章
相關標籤/搜索