Netty4之處理鏈

本文是基於Netty4.1.x,Handler在Netty佔據着很重要的位置,跟Servlet中的filter很像,通過Handler可以完成消息的編解碼、攔截指定的消息、統一對日誌錯誤進行處理、統一對請求進行計數。所有的Handler都實現ChannelHandler接口,分爲兩大類,ChannelInboundHandler與ChannelOutboundHandler,ChannelInboundHandler對從客戶端發往服務器的報文進行處理,一般用來執行解碼、讀取客戶端數據、進行業務處理等;ChannelOutboundHandler對從服務器發往客戶端的報文進行處理,一般用來進行編碼、發送報文到客戶端。Netty中,可以註冊多個handler。ChannelInboundHandler按照註冊的先後順序執行;ChannelOutboundHandler按照註冊的先後順序逆序執行,如下圖所示,按照註冊的先後順序對Handler進行排序,request進入Netty後的執行順序爲:

從上圖可以看出,在入站時,request會依次經過相關的Inbound處理器,然後出站時response也經過相應的Outbound處理器。從上圖中可以看出Handler組成了一個鏈表放在ChannelPipeplie中,下面看看ChannelPipe中的Handler如何組成鏈表的。下面是一段NettyServer初始化的代碼:

public class NettyServer {
    public static void main(String[] args) throws Exception {
        new NettyServer().start("127.0.0.1", 8081);
    }

    public void start(String host, int port) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        EventLoopGroup bossGroup = new NioEventLoopGroup(0, executorService);//Boss I/O線程池,用於處理客戶端連接,連接建立之後交給work I/O處理
        EventLoopGroup workerGroup = new NioEventLoopGroup(0, executorService);//Work I/O線程池
        EventExecutorGroup businessGroup = new DefaultEventExecutorGroup(2);//業務線程池
        ServerBootstrap server = new ServerBootstrap();//啓動類
        server.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024).childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                pipeline.addLast("decoder", new StringDecoder());
                pipeline.addLast("encoder", new StringEncoder());
                pipeline.addLast(businessGroup, new ServerHandler());
            }
        });
        server.childOption(ChannelOption.TCP_NODELAY, true);
        server.childOption(ChannelOption.SO_RCVBUF, 32 * 1024);
        server.childOption(ChannelOption.SO_SNDBUF, 32 * 1024);
        InetSocketAddress addr = new InetSocketAddress(host, port);
        server.bind(addr).sync().channel();//啓動服務
    }
}

從上面看出在initChannel時會調用 pipeline.addLast("decoder", new StringDecoder());逐個將Handler加入到Pipeline,下在看看 pipeline.addLast()這個方法:

@Override
public final ChannelPipeline addLast(String name, ChannelHandler handler) {
    return addLast(null, name, handler);
}

@Override
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);

        // If the registered is false it means that the channel was not registered on an eventLoop yet.
        // In this case we add the context to the pipeline and add a task that will call
        // ChannelHandler.handlerAdded(...) once the channel is registered.
        if (!registered) {
            newCtx.setAddPending();
            callHandlerCallbackLater(newCtx, true);
            return this;
        }

        EventExecutor executor = newCtx.executor();
        if (!executor.inEventLoop()) {
            callHandlerAddedInEventLoop(newCtx, executor);
            return this;
        }
    }
    callHandlerAdded0(newCtx);
    return this;
}

private void addLast0(AbstractChannelHandlerContext newCtx) {
    AbstractChannelHandlerContext prev = tail.prev;
    newCtx.prev = prev;
    newCtx.next = tail;
    prev.next = newCtx;
    tail.prev = newCtx;
}

上面代碼片中 addLast()方法中先調用 checkMultiplicity(handler)檢查一下是否有重複,然後將handler包裝成一個AbstractChannelHandlerContext然後再調用 addLast0(newCtx);將newCtx放入到鏈表中,從addLast0方法中可以看出,處理鏈是一個雙向鏈表。到這裏處理鏈的執行邏輯及組成方式就說完了總結一下:

  • 處理鏈是一個有順的雙向鏈表放在ChannelPipeline(DefaultChannelPipeline)中,消息上行或下行時依次會被相關的處理器處理;
  • 可以通過 channel.pipeline().addLast()、 channel.pipeline().addFirst()、channel.pipeline().addBefore()等方法向鏈表的指定位置添加處理器,也可以在處理過程中動態添加或刪除處理器;
  • 可以爲Handler指定處理的業務線程池;
  • 可以使用相關的api將消息在處理鏈路中流轉,如ctx.fireChannelRead()表示將消息傳遞到下一個處理器,ctx.channel().pipeline()表示將消息放到pipeline讓處理器從第一個處理器開始處理;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章