netty筆記-:Channel與ChannelHandlerContext執行write方法的區別

 

  在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

相關文章
相關標籤/搜索