在netty中有咱們通常有兩種發送數據的方式,即便用ChannelHandlerContext或者Channel的write方法,這兩種方法都能發送數據,那麼其有什麼區別呢。這兒引用netty文檔中的解釋以下。promise
這個通俗一點的解釋呢能夠說ChannelHandlerContext執行寫入方法時只會執行當前handler以前的OutboundHandler。而Channel則會執行全部的OutboundHandler。下面咱們能夠經過例子來理解網絡
1.創建一個netty服務端socket
public class Server { public static void main(String[] args) throws InterruptedException { ServerBootstrap serverBootstrap = new ServerBootstrap(); ChannelFuture channelFuture = serverBootstrap.group(new NioEventLoopGroup(1) , new NioEventLoopGroup(10)) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler()) .childHandler(new InitialierHandler()) //即步驟2中的類 .bind(8080) .sync(); channelFuture.channel().closeFuture().sync(); } }
2.建立ChannelInitializeride
在這個類中咱們添加了四個處理器 這兒注意順序 (具體類在步驟3) 工具
public class InitialierHandler extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(new RequestChannelHandler1()); socketChannel.pipeline().addLast(new ResponseChannelHandler1()); socketChannel.pipeline().addLast(new RequestChannelHandler2()); socketChannel.pipeline().addLast(new ResponseChannelHandler2()); } }
順序分別爲 in1 →out1→ in2 →out2 這兒用圖來增長理解 (netty會自動區分in或是out類型)oop
3. 分別建立2個 int Handler 2個out handlerthis
RequestChannelHandler1(注意後面業務會修改方法具體內容)idea
public class RequestChannelHandler1 extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx , Object msg) throws Exception { System.out.println("請求處理器1"); super.channelRead(ctx,msg); } }
RequestChannelHandler2(注意後面業務會修改方法具體內容)spa
public class RequestChannelHandler2 extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx , Object msg) throws Exception { System.out.println("請求處理器2");super.channelRead(ctx,msg); } }
ResponseChannelHandler13d
public class ResponseChannelHandler1 extends ChannelOutboundHandlerAdapter { @Override public void write(ChannelHandlerContext ctx , Object msg , ChannelPromise promise) throws Exception { System.out.println("響應處理器1"); ByteBuf byteMsg = (ByteBuf) msg; byteMsg.writeBytes("增長請求1的內容".getBytes(Charset.forName("gb2312"))); super.write(ctx,msg,promise); } }
ResponseChannelHandler2
public class ResponseChannelHandler2 extends ChannelOutboundHandlerAdapter { @Override public void write(ChannelHandlerContext ctx , Object msg , ChannelPromise promise) throws Exception { System.out.println("響應處理器2"); ByteBuf byteMsg = (ByteBuf) msg; byteMsg.writeBytes("增長請求2的內容".getBytes(Charset.forName("gb2312"))); super.write(ctx,msg,promise); } }
4.檢驗
可使用調試器來調試請求,例如網絡調試助手
咱們一共建立了四個Handler 且類型以及順序爲 in1 → out1 →in2 →out2 ,按照netty的定義。可實驗以下
4.1 in1中調用ChannelHandlerContext(只會調用其以前的handler)的方法則out1,out2都不會調用,in1中調用Channel(全部都會調用)的方法則會out1,out2都調用
咱們將in1 read方法內容改成以下
@Override public void channelRead(ChannelHandlerContext ctx , Object msg) throws Exception { System.out.println("請求處理器1"); ctx.writeAndFlush(Unpooled.copiedBuffer("hello word1" , Charset.forName("gb2312"))); super.channelRead(ctx,msg); }
in2 read方法改成以下
public void channelRead(ChannelHandlerContext ctx , Object msg) throws Exception { System.out.println("請求處理器2"); super.channelRead(ctx,msg); }
使用網絡調試後發現控制檯打印以下 並無通過out1和out2
而網絡調試控制檯打印以下 ,咱們只接收到了hello word原始內容
而後將in1中的ChannelHandlerContext改成Channel後則控制檯和網絡調試控制檯打印分別以下
public void channelRead(ChannelHandlerContext ctx , Object msg) throws Exception { System.out.println("請求處理器1"); ctx.channel().writeAndFlush(Unpooled.copiedBuffer("hello word1" , Charset.forName("gb2312"))); super.channelRead(ctx,msg); }
控制檯中兩次響應的處理已經打印,而且返回內容已經被分別加上out處理器中的信息
4.2 in2中調用ChannelHandlerContext(只會調用其以前的handler)的方法則out1會調用,out2不會調用,in2中調用Channel(全部都會調用)的方法則會out1,out2都調用
這兒咱們將in1 與in2稍做修改
in1 read方法改成以下
public void channelRead(ChannelHandlerContext ctx , Object msg) throws Exception { System.out.println("請求處理器1"); super.channelRead(ctx,msg); }
in2 read方法改成以下
public void channelRead(ChannelHandlerContext ctx , Object msg) throws Exception { System.out.println("請求處理器2"); ctx.writeAndFlush(Unpooled.copiedBuffer("hello word2" , Charset.forName("gb2312"))); super.channelRead(ctx,msg); }
使用網絡調試工具訪問後控制檯和網絡調試控制檯分別打印以下
能夠發現idea控制檯只打印了out1 網絡調試控制檯也只增長了請求1的內容。
至於將ChannelHandlerContext則和4.1中效果一致,out1和out2都會執行,這兒就不在寫了
5.源碼簡略分析
經過上面的案例應該就很明確這二者的差異了,咱們這兒能夠簡要看下源碼步驟。
5.1 pipeline.addLast()
上面咱們經過socketChannel.pipeline().addLast 添加了咱們的Handler,顧名思義就是將咱們的處理器添加到末尾(netty內部使用一個鏈表存儲)。咱們能夠看下其源碼
public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) { ObjectUtil.checkNotNull(handlers, "handlers"); for (ChannelHandler h: handlers) { if (h == null) { break; } addLast(executor, null, h); } return this; }
上面循環是可能傳入多個,根據這個能夠得知,咱們傳入多個的時候也是根據參數順序來的。咱們能夠接着看addLast(executor,null,h)方法。
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) { final AbstractChannelHandlerContext newCtx; synchronized (this) { checkMultiplicity(handler); newCtx = newContext(group, filterName(name, handler), handler); addLast0(newCtx); .... } callHandlerAdded0(newCtx); return this; }
這兒就是將咱們傳入的handler包裝了成了一個AbstractChannelHandlerContext (數據類型是一個雙向鏈表),而後執行了addLast0方法。
private void addLast0(AbstractChannelHandlerContext newCtx) { AbstractChannelHandlerContext prev = tail.prev; newCtx.prev = prev; newCtx.next = tail; prev.next = newCtx; tail.prev = newCtx; }
這兒的代碼就比較簡單,就是將當前的handler插入到tail節點與倒數第二個節點之間。這樣當前的handler就成爲了倒數第二個節點,之後每加一個handler都會成爲新的倒數第2個節點。這兒注意tail節點由一個專門的TailContext維護。
既然處理器已經添加,咱們就能夠看下其如何工做的吧
5.2 ChannelHandlerContext.writeAndFlush方法
private void write(Object msg, boolean flush, ChannelPromise promise) { //注意flush爲true ........ final AbstractChannelHandlerContext next = findContextOutbound(flush ? (MASK_WRITE | MASK_FLUSH) : MASK_WRITE); final Object m = pipeline.touch(msg, next); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { if (flush) { next.invokeWriteAndFlush(m, promise); } else { next.invokeWrite(m, promise); } } else { ....... } }
這裏面的邏輯能夠發現主要分爲兩步,第一步找到下一個執行的handler,第二部執行這個handler的write方法。咱們主要看下查找next的方法,即這個findContextOutbound()方法,點進去看下
private AbstractChannelHandlerContext findContextOutbound(int mask) { AbstractChannelHandlerContext ctx = this; do { ctx = ctx.prev; } while ((ctx.executionMask & mask) == 0); return ctx; }
能夠看到 這裏面會不斷的查找當前handlerContext的前一個知足寫操做的handler。找到知足的後就會返回。
好比咱們如今有個handler鏈是這樣的head→in1→out1→in2→out2→in3→out3→tail 。咱們在in3中寫入,那in3的pre就是out2,若是知足條件就會將out2返回,不知足就會→in2→out1→in1這樣不斷往前查找
5.3Channel.writeAndFlush方法
這個方法根據上面的結論會從handler鏈的tail開始調用,其實這個也很好理解,上面的ChannelHanderContext自己就是鏈表結構,因此支持查找當前的節點的先後節點。而這個Channel並非鏈表結構,因此只能從tail開始一個一個找了。
@Override public final ChannelFuture writeAndFlush(Object msg) { return tail.writeAndFlush(msg); }
這裏面調用的tail的write方法,咱們看下tail
final AbstractChannelHandlerContext tail;
這就是5.2中咱們說的tail節點,那最終會調用的也就是5.2中的write方法,只是這個this從當前的channelContextHandler變爲了tail