Netty源碼分析:ChannelPipeline

Netty源碼分析:ChannelPipeline

在博文Netty源碼分析:服務端啓動全過程

我們在知道NioServerSocketChannel這個類的構造函數的調用鏈如下:

    public NioServerSocketChannel() {
        this(newSocket(DEFAULT_SELECTOR_PROVIDER));//newSocket的功能爲:利用SelectorProvider產生一個SocketChannelImpl對象。
    }

    public NioServerSocketChannel(ServerSocketChannel channel) {
        super(null, channel, SelectionKey.OP_ACCEPT);
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    } 
    //父類AbstractNioMessageChannel的構造函數
    protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent, ch, readInterestOp);
    }   
    //父類 AbstractNioChannel的構造函數
    protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch;
        this.readInterestOp = readInterestOp;//SelectionKey.OP_ACCEPT
        try {
            ch.configureBlocking(false);//設置當前的ServerSocketChannel爲非阻塞的
        } catch (IOException e) {
            try {
                ch.close();
            } catch (IOException e2) {
                if (logger.isWarnEnabled()) {
                    logger.warn(
                            "Failed to close a partially initialized socket.", e2);
                }
            }

            throw new ChannelException("Failed to enter non-blocking mode.", e);
        }
    } 
    //父類AbstractChannel的構造函數
    protected AbstractChannel(Channel parent) {
        this.parent = parent;
        unsafe = newUnsafe();
        pipeline = new DefaultChannelPipeline(this);
    }

在如上的AbstractChannel構造函數中, 我們看到,使用 DefaultChannelPipeline類的實例初始化了一個 pipeline 屬性。

下面首先看下DefaultChannelPipeline的構造函數。

    public DefaultChannelPipeline(AbstractChannel channel) {
        if (channel == null) {
            throw new NullPointerException("channel");
        }
        this.channel = channel;

        tail = new TailContext(this);
        head = new HeadContext(this);

        head.next = tail;
        tail.prev = head;
    }

在如上的構造函數中,首先將與之關聯的Channel保存在屬性channel中,然後實例化了兩個對象:一個是TailContext 實例tail,一個是HeadContext 實例 head。然後將head和tail相互指向,構成了一個雙向鏈表。

HeadContext、TailContext的繼承體系結構如下:

從HeadContext和TailContext的繼承結構可以看到:這兩個類具有Context和Handler的雙重屬性,可以這麼說:head和tail既是一個ChannelHandlerContext也是一個ChannelHandler,原因在於:

1、這兩個類均繼承的是AbstractChannelHandlerContext這個類

2、均實現了ChannelHandler接口,只是HeadContext實現的是ChannelOutboundHandler,而TailContext實現的是ChannelInboundHandler接口。

而AbstractChannelHandlerContext類有如下兩個屬性:

    abstract class AbstractChannelHandlerContext extends DefaultAttributeMap implements ChannelHandlerContext {

        volatile AbstractChannelHandlerContext next;
        volatile AbstractChannelHandlerContext prev;

因此,可以得到其實在 DefaultChannelPipeline 中, 維護了一個以 AbstractChannelHandlerContext 爲節點的雙向鏈表,其中此鏈表是 以head(HeadContext)作爲頭,以tail(TailContext)作爲尾的雙向鏈表,這個鏈表是 Netty 實現 Pipeline 機制的關鍵.

下面看下HeadContext、TailContext這兩個類的構造函數

        HeadContext(DefaultChannelPipeline pipeline) {
            super(pipeline, null, HEAD_NAME, false, true);
            unsafe = pipeline.channel().unsafe();
        }

        TailContext(DefaultChannelPipeline pipeline) {
            super(pipeline, null, TAIL_NAME, true, false);
        }
AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutorGroup group, String name,
                                  boolean inbound, boolean outbound) {

        if (name == null) {
            throw new NullPointerException("name");
        }

        channel = pipeline.channel;
        this.pipeline = pipeline;
        this.name = name;

        if (group != null) {
            // Pin one of the child executors once and remember it so that the same child executor
            // is used to fire events for the same channel.
            EventExecutor childExecutor = pipeline.childExecutors.get(group);
            if (childExecutor == null) {
                childExecutor = group.next();
                pipeline.childExecutors.put(group, childExecutor);
            }
            executor = childExecutor;
        } else {
            executor = null;
        }

        this.inbound = inbound;
        this.outbound = outbound;
    } 

HeadContext、TailContext這兩個構造函數都是調用了父類AbstractChannelHandlerContext如上的構造函數,只是參數有所不同。 有兩個參數需要額外注意:

1、對於HeadContex,傳入的參數:inbound=false,outbound=true;

2、對於TailContext,傳入的參數與HeadContext相反:inbound=true,outbound=false;

可以這麼來理解:inbound和outbound這兩個標誌用來區分鏈表的節點到底是inboundHandler還是outboundHandler。例如:從上面HeadContext和TailContext的繼承結構中可以得到HeadContext就是outboundHandler,而TailContext就是InboundHandler

自定義的handler是如何被添加到Pipeline所持有的雙向鏈表中的呢

一般情況,我們在初始化Bootstrap中,經常會看到如下類似的代碼,

            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new SimpleServerHandler())
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                        }
                    });

其中SimpleServerHandler是我們自定義的Handler,那麼這個SimpleServerHandler是如何被添加到上面所介紹的Pipeline的雙向鏈表中去呢?

在博文 Netty源碼分析:服務端啓動全過程中可以看到在initAndRegister()方法中調用的init(channel)中有如下代碼:

    final ChannelFuture initAndRegister() {
        final Channel channel = channelFactory().newChannel();
        try {
            init(channel);
        }
        //...
    }
    ServerBootstrap.java
    @Override
    void init(Channel channel) throws Exception {
       //...
        ChannelPipeline p = channel.pipeline();
        if (handler() != null) {
            p.addLast(handler());
        }
        //...
    } 

結合前面的分析,我們知道 ChannelPipeline p = channel.pipeline();得到的就是channel所持有的pipeline對象,而handler()方法返回就是通過b.handler(new SimpleServerHandler())設置的我們自定義的 SimpleServerHandler對象。然後將此Handler插入到Pipeline的雙向鏈表中。

下面我們來跟下addLast方法

DefaultChannelPipeline.java

    @Override
    public ChannelPipeline addLast(ChannelHandler... handlers) {
        return addLast(null, handlers);
    }  
    @Override
    public ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
        if (handlers == null) {
            throw new NullPointerException("handlers");
        }

        for (ChannelHandler h: handlers) {
            if (h == null) {
                break;
            }
            addLast(executor, generateName(h), h);
        }

        return this;
    }  

    @Override
    public ChannelPipeline addLast(EventExecutorGroup group, final String name, ChannelHandler handler) {
        synchronized (this) {
            checkDuplicateName(name);//檢查是否有重複的名字

            AbstractChannelHandlerContext newCtx = new DefaultChannelHandlerContext(this, group, name, handler);
            addLast0(name, newCtx);
        }

        return this;
    } 

這是addLast的調用鏈,下面主要分析最後一個addLast這個重載的方法。

基本邏輯爲:

1、首先調用 checkDuplicateName方法來檢查是否有重複名字的handler,如果有則拋異常。

    private void checkDuplicateName(String name) {
        if (name2ctx.containsKey(name)) {
            throw new IllegalArgumentException("Duplicate handler name: " + name);
        }
    }

其中:

    private final Map<String, AbstractChannelHandlerContext> name2ctx =
        new HashMap<String, AbstractChannelHandlerContext>(4);   

在DefaultChannelPipeline中搜索此字段,可以發現在所有添加handler的方法中addFirst0、addLast0等中出現瞭如下代碼語句:

name2ctx.put(name, newCtx);//重點

上面的name2ctx是一個Map,key爲handler的名字,而value則是handler本身。

即name2ctx中保存的是Pipeline中存在的所有handler,因此就可以在checkDuplicateName方法中利用 name2ctx.containsKey(name)來判斷是否重明。

既然介紹到了這裏,就有必要說下handler的name是如何產生的?從上面的第2個addLast重載方法中可以看到是通過調用generateName(h)方法產生的,下面來看下這個方法:

    private String generateName(ChannelHandler handler) {
        WeakHashMap<Class<?>, String> cache = nameCaches[(int) (Thread.currentThread().getId() % nameCaches.length)];
        Class<?> handlerType = handler.getClass();
        String name;
        synchronized (cache) {
            name = cache.get(handlerType);
            if (name == null) {
                name = generateName0(handlerType);
                cache.put(handlerType, name);
            }
        }

        synchronized (this) {
            // It's not very likely for a user to put more than one handler of the same type, but make sure to avoid
            // any name conflicts.  Note that we don't cache the names generated here.
            if (name2ctx.containsKey(name)) {
                String baseName = name.substring(0, name.length() - 1); // Strip the trailing '0'.
                for (int i = 1;; i ++) {
                    String newName = baseName + i;
                    if (!name2ctx.containsKey(newName)) {
                        name = newName;
                        break;
                    }
                }
            }
        }

        return name;
    }

    private static String generateName0(Class<?> handlerType) {
        return StringUtil.simpleClassName(handlerType) + "#0";
    } 

該函數的功能爲:如果nameCache中沒有該handler類的名字,則調用generatName0方法產生,產生的名字爲:類名+“#0”,例如在本例中SimpleServerHandler這個handler所產生的名字爲:SimpleServerHandler#0;如果nameCache中有該handler類的名字且name2ctx Map中有包括此名字的handler(添加了多個同類型的Handler到Pipeline中就會出現這種情況),則遞增後面的那個數字作爲名字直至不重複。

2、將handler包裝爲Context

爲什麼要利用如下的語句獎handler包裝成Context呢?

 AbstractChannelHandlerContext newCtx = new DefaultChannelHandlerContext(this, group, name, handler);

原因在於:Pipeline中是以AbstractChannelHandlerContext爲節點的雙向鏈表。

下面我們來看下DefaultChannelHandlerContext這個類如下的構造函數

    DefaultChannelHandlerContext(
            DefaultChannelPipeline pipeline, EventExecutorGroup group, String name, ChannelHandler handler) {
        super(pipeline, group, name, isInbound(handler), isOutbound(handler));
        if (handler == null) {
            throw new NullPointerException("handler");
        }
        this.handler = handler;
    }

從DefaultChannelHandlerContext的繼承關係圖中可以得到該類繼承了AbstractChannelHandlerContext,與前面所介紹的HeadContext、TailContext一樣。還記不記得如下的一段話:

HeadContext、TailContext這兩個構造函數都是調用了父類AbstractChannelHandlerContext如上的構造函數,只是參數有所不同。 有兩個參數需要額外注意:

1)、對於HeadContex,傳入的參數:inbound=false,outbound=true;

2)、對於TailContext,傳入的參數與HeadContext相反:inbound=true,outbound=false;

類似,這裏的DefaultChannelHandlerContext構造函數也是調用了父類AbstractChannelHandlerContext的構造函數,只是,inbound、outbound這兩個參數是通過函數isInbound(handler)和isOutbound(handler)來得到,當handler實現了ChannelInboundHandler接口,則isInbound方法返回true,當handler實現了ChannelOutboundHandler接口,則isOunbound方法返回true。

    private static boolean isInbound(ChannelHandler handler) {
        return handler instanceof ChannelInboundHandler;
    }

    private static boolean isOutbound(ChannelHandler handler) {
        return handler instanceof ChannelOutboundHandler;
    }

就如前面所說:inbound和outbound這兩個標誌用來區分鏈表的節點到底是inboundHandler還是outboundHandler。

在我們的例子中SimpleServerHandler實現的是ChannelInboundHandlerAdapter類,該類的繼承結構如下,即可得我們自定義的SimpleServerHandler所對應的 DefaultChannelHandlerContext 的 inbound = true, outbound = false 。記住這一點,後面有用

    private static class SimpleServerHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("channelActive");
        }

        @Override
        public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
            System.out.println("channelRegistered");
        }

        @Override
        public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
            System.out.println("handlerAdded");
        }
    }

3、調用addLast0方法將handler加入到Pipeline的雙向鏈表的末尾

    private void addLast0(final String name, AbstractChannelHandlerContext newCtx) {
        checkMultiplicity(newCtx);

        AbstractChannelHandlerContext prev = tail.prev;
        newCtx.prev = prev;
        newCtx.next = tail;
        prev.next = newCtx;
        tail.prev = newCtx;

        name2ctx.put(name, newCtx);

        callHandlerAdded(newCtx);
    } 

addLast0方法中就是典型的在鏈表中插入節點的代碼。即當調用了 addLast 方法後, 此 handler 就被添加到雙向鏈表中 tail 元素之前的位置了。

注意:上面addLast0方法中的最後一行代碼:callHandlerAdded(newCtx);就是第一次使用我們自定義的handler(SimpleServerHandler)的地方,稍後將進行分析。

以上就分析了我們自定義的handler是如何被添加的Pipeline中去的。

那麼問題來了,那我們自定義的handler在哪裏被使用了呢?

自定義的handler在哪使用了呢

一般服務器端的代碼如下所示:

    public final class SimpleServer {

        public static void main(String[] args) throws Exception {
            EventLoopGroup bossGroup = new NioEventLoopGroup(1);
            EventLoopGroup workerGroup = new NioEventLoopGroup();

            try {
                ServerBootstrap b = new ServerBootstrap();
                b.group(bossGroup, workerGroup)
                        .channel(NioServerSocketChannel.class)
                        .handler(new SimpleServerHandler())
                        .childHandler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            public void initChannel(SocketChannel ch) throws Exception {
                            }
                        });

                ChannelFuture f = b.bind(8887).sync();

                f.channel().closeFuture().sync();
            } finally {
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        }

        private static class SimpleServerHandler extends ChannelInboundHandlerAdapter {
            @Override
            public void channelActive(ChannelHandlerContext ctx) throws Exception {
                System.out.println("channelActive");
            }

            @Override
            public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
                System.out.println("channelRegistered");
            }

            @Override
            public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
                System.out.println("handlerAdded");
            }
        }
    }

在運行此代碼時,在控制檯會輸出如下的內容:

    handlerAdded
    channelRegistered
    channelActive   

既然這三行的內容被打印出來了,就說明我們自定義的SimpleServerHandler的這些方法在某處依次被調用了,是吧,下面就主要看下。

1、SimpleServerHandler 的 handlerAdded 是在哪裏被調用了呢

在本博文稍前面一點,我說過如下的一段話:

上面addLast0方法中的最後一行代碼:callHandlerAdded(newCtx);就是第一次使用我們自定義的handler(SimpleServerHandler)的地方,稍後將進行分析。

下面將來分析下callHandlerAdded(newCtx)方法

DefaultChannelPipiline.java

    private void callHandlerAdded(final ChannelHandlerContext ctx) {
        if (ctx.channel().isRegistered() && !ctx.executor().inEventLoop()) {
            ctx.executor().execute(new Runnable() {
                @Override
                public void run() {
                    callHandlerAdded0(ctx);
                }
            });
            return;
        }
        callHandlerAdded0(ctx);
    }

上面方法主要是調用了callHandlerAdded0(ctx);方法,代碼如下:

    private void callHandlerAdded0(final ChannelHandlerContext ctx) {

            ctx.handler().handlerAdded(ctx);
            //...
    }

如前面所介紹我們知道:ctx就是封裝了我們自定義的handler(SimpleServerHandler)的DefaultChannelHandlerContext實例,ctx.handler()返回的就是SimpleServeHandler實例,進行調用的此類中如下的 handlerAdded方法,進而在控制檯輸出:handlerAdded

            @Override
            public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
                System.out.println("handlerAdded");
            }

2、SimpleServerHandler 的 channelRegistered 是在哪裏被調用了呢

跟蹤b.bind(8887)代碼邏輯,發現在如下的initAndRegister()方法的註冊階段調用的register0()方法中出現瞭如下的代碼片段:

    final ChannelFuture initAndRegister() {
        final Channel channel = channelFactory().newChannel();
        init(channel); 

        ChannelFuture regFuture = group().register(channel);
        //...省略了一些此時不關注的代碼邏輯
    }

    AbstractChannel.java

        private void register0(ChannelPromise promise) {
                //...
                doRegister();
                registered = true;
                safeSetSuccess(promise);
                pipeline.fireChannelRegistered();
                if (isActive()) {
                    pipeline.fireChannelActive();
                }
                //...

        } 

先看下:pipeline.fireChannelRegistered();

DefaultChannelPipeline.java

    @Override
    public ChannelPipeline fireChannelRegistered() {
        head.fireChannelRegistered();
        return this;
    } 

這裏直接調用了head(HeadContext實例)的fireChannelRegistered()方法,如下:

AbstractChannelHandlerContext.java

    @Override
    public ChannelHandlerContext fireChannelRegistered() {
        final AbstractChannelHandlerContext next = findContextInbound();//分析
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeChannelRegistered();
        } else {
            executor.execute(new OneTimeTask() {
                @Override
                public void run() {
                    next.invokeChannelRegistered();
                }
            });
        }
        return this;
    }

    private AbstractChannelHandlerContext findContextInbound() {
        AbstractChannelHandlerContext ctx = this;
        do {
            ctx = ctx.next;
        } while (!ctx.inbound);
        return ctx;
    } 

該方法首先是調用如上的findContextInbound()方法從鏈頭head開始尋找第一個inbound=true的節點,看到沒,這就是前面特別強調inbound、outbound這兩個變量。很明顯,這裏找到的就是封裝我們自定義的SimpleServerHandler的DefaultChannelHandlerContext實例。

下面將看下 next.invokeChannelRegistered();方法

    private void invokeChannelRegistered() {
        try {
            ((ChannelInboundHandler) handler()).channelRegistered(this);
        } catch (Throwable t) {
            notifyHandlerException(t);
        }
    }  

顯然,封裝我們自定義的SimpleServerHandler的DefaultChannelHandlerContext實例對象調用如上方法中的handler()方法的就是我們自定義的 SimpleServerHandler 實例。然後調用此類的如下代碼的channelRegistered方法,這樣就在控制檯輸出了: channelRegistered。

            @Override
            public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
                System.out.println("channelRegistered");
            } 

3、SimpleServerHandler 的 channelActive方法 是在哪裏被調用了呢

接下來,看下:pipeline.fireChannelActive();要說明的是在register0()方法中isActive()方法返回的false,因此在register0()方法中是不會執行此語句的,至於在哪裏執行了該語句,可以參考博文 Netty源碼分析:服務端啓動全過程

DefaultChannelPipeline.java

    @Override
    public ChannelPipeline fireChannelActive() {
        head.fireChannelActive();

        if (channel.config().isAutoRead()) {
            channel.read();
        }

        return this;
    }   

    @Override
    public ChannelHandlerContext fireChannelActive() {
        final AbstractChannelHandlerContext next = findContextInbound();
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeChannelActive();
        } else {
            executor.execute(new OneTimeTask() {
                @Override
                public void run() {
                    next.invokeChannelActive();
                }
            });
        }
        return this;
    }

    //AbstractChannelHandlerContext    
    private void invokeChannelActive() {
        try {
            ((ChannelInboundHandler) handler()).channelActive(this);
        } catch (Throwable t) {
            notifyHandlerException(t);
        }
    }  

pipeline.fireChannelActive()這個方法與上面分析的fireChannelRegistered()方法類似,也是通過調用head.fireChannelActive();語句來從Pipeline的頭節點開始找到第一個Inbound=true的節點:包裝了我們自定義handler的DefaultChannelHandlerContext實例。然後調用此實例的invokeChannelActive()方法進而調用了我們自定的SimpleServerHandler的 channelActive方法,進而在控制檯輸出:channelActive。

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("channelActive");
        } 

小結

本博文從源碼的角度分析了ChannelPipeline這個類。瞭解了我們自定義的handler是被加入到Pipeline所持有的雙向鏈表中的,瞭解了我們自定義的handler中重載的幾種方法在哪裏被調用的。

需要記住的幾點如下:

1、在 Netty 中每個 Channel 都有且僅有一個 ChannelPipeline 與之對應。

2、ChannelPipeline是一個維護了一個以 AbstractChannelHandlerContext 爲節點的雙向鏈表,其中此鏈表是 以head(HeadContext)作爲頭,以tail(TailContext)作爲尾的雙向鏈表.

如上兩點關係用圖表示如下:(注:圖片截圖於參考資料)

在最後,感謝參考資料所列出的博文的作者,寫作思路相當清晰,自己看的很爽,然後自己也對照的源碼過了這整個過程,理解的更深刻了一些。

參考資料

1、https://segmentfault.com/a/1190000007308934

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