Netty源碼解析 -- ChannelPipeline機制與讀寫過程

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文繼續閱讀Netty源碼,解析ChannelPipeline事件傳播原理,以及Netty讀寫過程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"源碼分析基於Netty 4.1"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"ChannelPipeline"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Netty中的ChannelPipeline可以理解爲攔截器鏈,維護了一個ChannelHandler鏈表,ChannelHandler即具體攔截器,可以在讀寫過程中,對數據進行處理。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ChannelHandler也可以分爲兩類。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"ChannelInboundHandler"},{"type":"text","text":",監控Channel狀態變化,如channelActive,channelRegistered,通常通過重寫ChannelOutboundHandler#channelRead方法處理讀取到的數據,如HttpObjectDecoder將讀取到的數據解析爲(netty)HttpRequest。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"ChannelOutboundHandler"},{"type":"text","text":",攔截IO事件,如bind,connect,read,write,通常通過重寫ChannelInboundHandler#write方法處理將寫入Channel的數據。如HttpResponseEncoder,將待寫入的數據轉換爲Http格式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ChannelPipeline的默認實現類爲DefaultChannelPipeline,它在ChannelHandler鏈表首尾維護了兩個特殊的ChannelHandler -- HeadContext,TailContext。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HeadContext負責將IO事件轉發給對應的UnSafe處理,例如前面文章中說到的register,bind,read等操作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TailContext主要是一些兜底處理,如channelRead方法釋放ByteBuf的引用等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"事件傳播"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"ChannelOutboundInvoker"},{"type":"text","text":"負責觸發ChannelOutboundHandler的方法,他們方法名相同,只是ChannelOutboundInvoker方法中少了ChannelHandlerContext參數。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同樣,"},{"type":"text","marks":[{"type":"strong"}],"text":"ChannelInboundInvoker"},{"type":"text","text":"負責觸發ChannelInboundHandler的方法,但ChannelInboundInvoker的方法名多了fire,如ChannelInboundInvoker#fireChannelRead方法,觸發ChannelInboundHandler#channelRead。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"ChannelPipeline"},{"type":"text","text":"和*"},{"type":"text","marks":[{"type":"italic"}],"text":"ChannelHandlerContext"},{"type":"text","text":"*都繼承了這兩個接口。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但他們作用不同,ChannelPipeline是攔截器鏈,實際請求委託給ChannelHandlerContext處理。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ChannelHandlerContext接口(即ChannelHandler上下文)維護了鏈表的上下節點,它作爲ChannelHandler方法參數, 負責與ChannelPipeline及其他 ChannelHandler互動。通過它可以動態修改Channel的屬性,給EventLoop提交任務,也可以向下一個(上一個)ChannelHandler傳播事件。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"例如,在ChannelInboundHandler#channelRead處理完數據後,可以通過ChannelHandlerContext#write將數據寫到Channel。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ChannelInboundHandler#handler方法返回真正的ChannelHandler,並使用該ChannelHandler執行實際操作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過DefaultChannelPipeline#addFirst等方法添加ChannelHandler時,Netty會爲ChannelHandler構造一個DefaultChannelHandlerContext,handler方法返回對應的ChannelHandler。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HeadContext,TailContext也實現了AbstractChannelHandlerContext,handler方法返回自身this。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們也可以通過ChannelHandlerContext給EventLoop提交異步任務"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"ctx.channel().eventLoop().execute(new Runnable() {\n\tpublic void run() {\n\t\t...\n\t}\n});"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於阻塞時間較長的操作,使用異步任務完成是不錯的選擇。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面以DefaultChannelPipeline#fireChannelRead爲例,看一下他們的事件傳播過程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"DefaultChannelPipeline"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"public final ChannelPipeline fireChannelRead(Object msg) {\n\tAbstractChannelHandlerContext.invokeChannelRead(head, msg);\n\treturn this;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用HeadContext作爲開始節點,調用AbstractChannelHandlerContext#invokeChannelRead方法開始調用攔截器鏈表。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"AbstractChannelHandlerContext"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {\n\tfinal Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, \"msg\"), next);\n\tEventExecutor executor = next.executor();\n\tif (executor.inEventLoop()) {\n\t\tnext.invokeChannelRead(m);\n\t} else {\n\t\t...\n\t}\n}\n\nprivate void invokeChannelRead(Object msg) {\n\tif (invokeHandler()) {\n\t\ttry {\n\t\t\t// #1\n\t\t\t((ChannelInboundHandler) handler()).channelRead(this, msg);\n\t\t} catch (Throwable t) {\n\t\t\tnotifyHandlerException(t);\n\t\t}\n\t} else {\n\t\tfireChannelRead(msg);\n\t}\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"#1"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"handler方法獲取AbstractChannelHandlerContext真正的Handler,再觸發其ChannelPipeline#channelRead方法"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於invokeChannelRead方法在HeadContext中執行,"},{"type":"codeinline","content":[{"type":"text","text":"handler()"}]},{"type":"text","text":"這裏返回HeadContext,這時會觸發HeadContext#channelRead"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HeadContext#channelRead"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n\tctx.fireChannelRead(msg);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HeadContext方法調用"},{"type":"codeinline","content":[{"type":"text","text":"ctx.fireChannelRead(msg)"}]},{"type":"text","text":",就是向下一個ChannelInboundHandler傳播事件。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"AbstractChannelHandlerContext#fireChannelRead"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"public ChannelHandlerContext fireChannelRead(final Object msg) {\n\tinvokeChannelRead(findContextInbound(MASK_CHANNEL_READ), msg);\n\treturn this;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"AbstractChannelHandlerContext#fireChannelRead(final Object msg)"}]},{"type":"text","text":"方法主要負責找到下一個ChannelInboundHandler,並觸發其channelRead方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從DefaultChannelPipeline#fireChannelRead方法可以看到一個完整的調用鏈路:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"#1"}]},{"type":"text","text":" DefaultChannelPipeline通過HeadContext開始調用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"#2"}]},{"type":"text","text":" ChannelInboundHandler處理完當前邏輯後,調用"},{"type":"codeinline","content":[{"type":"text","text":"ctx.fireChannelRead(msg)"}]},{"type":"text","text":"向後傳播事件"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"#3"}]},{"type":"text","text":" AbstractChannelHandlerContext找到下一個ChannelInboundHandler,並觸發其channelRead,從而保證攔截器鏈繼續執行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"注意:對於ChannelOutboundHandler中的方法,DefaultChannelPipeline從TailContext開始調用,並向前傳播事件,與ChannelInboundHandler方向相反。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"大家在閱讀Netty源碼時,對於DefaultChannelPipeline的方法,要注意該方法底層調用是ChannelInboundHandler還是ChannelOutboundHandler的方法,以及他們的傳播方向。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果我們定義一個Http回聲程序,示意代碼如下"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"new ServerBootstrap().group(parentGroup, childGroup)\n .channel(NioServerSocketChannel.class)\n .childHandler(new ChannelInitializer() {\n public void initChannel(SocketChannel ch) throws Exception {\n ChannelPipeline p = ch.pipeline();\n p.addLast(new HttpRequestDecoder());\n\t\t\t\t p.addLast(new HttpResponseEncoder());\n\t\t\t\t p.addLast(new LoggingHandler(LogLevel.INFO));\n\t\t\t\t p.addLast(new HttpEchoHandler());\n }\n });"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中HttpEchoHandler實現了ChannelInboundHandler,並在channelRead方法中調用ChannelHandlerContext#write方法回傳數據。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼,數據流轉如下所示"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"Socket.read() -> head#channelRead -> HttpRequestDecoder#channelRead -> LoggingHandler#channelRead -> HttpEchoHandler#channelRead\n |\n \\|/\nSocket.write() HeadContext#write -> AbstractUnsafe#write"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"public final void write(Object msg, ChannelPromise promise) {\n\tassertEventLoop();\n\t// #1\n\tChannelOutboundBuffer outboundBuffer = this.outboundBuffer;\n\t...\n\n\tint size;\n\ttry {\n\t\t// #2\n\t\tmsg = filterOutboundMessage(msg);\n\t\t// #3\n\t\tsize = pipeline.estimatorHandle().size(msg);\n\t\tif (size < 0) {\n\t\t\tsize = 0;\n\t\t}\n\t} catch (Throwable t) {\n\t\tsafeSetFailure(promise, t);\n\t\tReferenceCountUtil.release(msg);\n\t\treturn;\n\t}\n\t// #4\n\toutboundBuffer.addMessage(msg, size, promise);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"#1"}]},{"type":"text","text":" 獲取AbstractUnsafe中維護的ChannelOutboundBuffer,該類負責緩存write的數據,等到flush再實際寫數據。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"#2"}]},{"type":"text","text":" AbstractChannel提供給子類的擴展方法,可以做一些ByteBuf檢查,轉化等操作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"#3"}]},{"type":"text","text":" 檢查待寫入數據量"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"#4"}]},{"type":"text","text":" 將數據添加到ChannelOutboundBuffer緩存中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到,write並沒有真正的寫數據,而是將數據放到了一個緩衝對象ChannelOutboundBuffer。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ChannelOutboundBuffer中的數據要等到ChannelHandlerContext#flush時再寫出。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ByteBuf是Netty中負責與Channel交互的內存緩衝區,而ByteBufAllocator,RecvByteBufAllocator主要負責分配內存給ByteBuf,後面有文章解析它們。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ChannelOutboundBuffer主要是緩存write數據,等到flush時再一併寫入Channel。後面有文章解析它。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果您覺得本文不錯,歡迎關注我的微信公衆號,您的關注是我堅持的動力!"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/da/da6c5c8363dc2a6fc148bc2eab39d883.jpeg","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章