深入理解SimpleChannelInboundHandler
?
因項目需要,需要了解 Netty 這款號稱 "高性能Java網絡編程" 框架。拿起一本《Netty In Action》開始研究,在第2章的例子中,發現 Echo 服務端使用的ChannelHandler是 ChannelInboundHandlerAdapter ,而 Echo 客戶端使用的卻是 SimpleChannelInboundHandler 。一臉茫然,不知所措,只能點進去看各自的實現原理。
一.SimpleChannelInboundHandler
? ? 首先看到的是?SimpleChannelInboundHandler 繼承自?ChannelInboundHandlerAdapter。
public abstract class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter{ ... }? ? 有圖有真相:
? ? 既然是繼承關系,也就是說,"你有的我也有,你沒有的我還有。"?那么 SimpleChannelInboundHandler 里面肯定重寫或者新增了 ChannelInboundHandlerAdapter 里面的方法功能 -?channelRead0 和 channelRead()。
protected abstract void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception;? ? 這里只提供了一個模板,作用是把處理邏輯不變的內容寫好在?channelRead(ctx,msg)?中,并且在里面調用?channelRead0?,這樣變化的內容通過抽象方法實現傳遞到子類中去了(在Netty5中channelRead0已被重命名為messageReceived)。
@Override// 繼承了 ChannelInboundHandlerAdapter#channelReadpublic 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 {//將未決消息沖刷到遠程節點,并且關閉該 Channelctx.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)// cancelledreturn 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;}四.參考
? Netty進階之路第三章:Netty內存池泄漏疑云案例
?
總結
以上是生活随笔為你收集整理的深入理解SimpleChannelInboundHandler的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 虚拟机 CentOS7 Secure
- 下一篇: 我的世界显示服务器领地指令,我的世界领地