基於Netty的服務端開發

Netty 服務端創建源碼分析

當我們直接使用 JDK 的 NIO類庫 開發基於 NIO 的異步服務端時,需要用到 多路複用器Selector、ServerSocketChannel、SocketChannel、ByteBuffer、SelectionKey 等,相比於傳統的 BIO開發,NIO 的開發要複雜很多,開發出穩定、高性能的異步通信框架,一直是個難題。Netty 爲了向使用者屏蔽 NIO通信 的底層細節,在和用戶交互的邊界做了封裝,目的就是爲了減少用戶開發工作量,降低開發難度。ServerBootstrap 是 Socket服務端 的啓動輔助類,用戶通過 ServerBootstrap 可以方便地創建 Netty 的服務端。

Netty 服務端創建時序圖

在這裏插入圖片描述
下面我們對 Netty服務端創建 的關鍵步驟和原理進行詳細解析。

1、創建 ServerBootstrap實例。ServerBootstrap 是 Netty服務端 的 啓動輔助類,它提供了一系列的方法用於設置服務端啓動相關的參數。底層對各種 原生NIO 的 API 進行了封裝,減少了用戶與 底層API 的接觸,降低了開發難度。ServerBootstrap 中只有一個 public 的無參的構造函數可以給用戶直接使用,ServerBootstrap 只開放一個無參的構造函數 的根本原因是 它的參數太多了,而且未來也可能會發生變化,爲了解決這個問題,就需要引入 Builder建造者模式。

2、設置並綁定 Reactor線程池。Netty 的 Reactor線程池 是 EventLoopGroup,它實際上是一個 EventLoop數組。EventLoop 的職責是處理所有註冊到本線程多路複用器 Selector 上的 Channel,Selector 的輪詢操作由綁定的 EventLoop線程 的 run()方法 驅動,在一個循環體內循環執行。值得說明的是,EventLoop 的職責不僅僅是處理 網絡IO事件,用戶自定義的Task 和 定時任務Task 也統一由 EventLoop 負責處理,這樣線程模型就實現了統一。從調度層面看,也不存在從 EventLoop線程 中再啓動其他類型的線程用於異步執行另外的任務,這樣就避免了多線程併發操作和鎖競爭,提升了 IO線程 的處理和調度性能。

3、設置並綁定 服務端Channel。作爲 NIO服務端,需要創建 ServerSocketChannel,Netty 對 原生NIO類庫 進行了封裝,對應的實現是NioServerSocketChannel。對於用戶而言,不需要關心 服務端Channel 的底層實現細節和工作原理,只需要指定具體使用哪種服務端 Channel 即可。因此,Netty 中 ServerBootstrap的基類 提供了 channel()方法,用於指定 服務端Channel 的類型。Netty 通過工廠類,利用反射創建 NioServerSocketChannel對象。由於服務端監聽端口往往只需要在系統啓動時纔會調用,因此反射對性能的影響並不大。相關代
碼如下。

public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {

    /**
     * 通過 參數channelClass 創建一個 Channel實例,
     */
    public B channel(Class<? extends C> channelClass) {
        if (channelClass == null) {
            throw new NullPointerException("channelClass");
        }
        return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
    }
}

4、鏈路建立的時候創建並初始化 ChannelPipeline。ChannelPipeline 並不是 NIO服務端 必需的,它本質就是一個負責處理網絡事件的職責鏈,負責管理和執行 ChannelHandler。網絡事件以事件流的形式在 ChannelPipeline 中流轉,由 ChannelPipeline 根據 ChannelHandler的執行策略 調度 ChannelHandler的執行。典型的網絡事件如下。

  1. 鏈路註冊;
  2. 鏈路激活;
  3. 鏈路斷開;
  4. 接收到請求消息;
  5. 請求消息接收並處理完畢;
  6. 發送應答消息;
  7. 鏈路發生異常;
  8. 發生用戶自定義事件。

5、初始化 ChannelPipeline 完成之後,添加並設置 ChannelHandler。ChannelHandler 是 Netty 提供給用戶定製和擴展的關鍵接口。利用 ChannelHandler 用戶可以完成大多數的功能定製,例如消息編解碼、心跳、安全認證、TSL/SSL 認證、流量控制和流量整形等。Netty 同時也提供了大量的 系統ChannelHandler 供用戶使用,比較實用的 系統ChannelHandler 總結如下。

  1. 系統編解碼框架,ByteToMessageCodec;
  2. 基於長度的半包解碼器,LengthFieldBasedFrameDecoder;
  3. 碼流日誌打印 Handler,LoggingHandler;
  4. SSL 安全認證 Handler,SslHandler;
  5. 鏈路空閒檢測 Handler,IdleStateHandler;
  6. 流量整形 Handler,ChannelTrafficShapingHandler;
  7. Base64 編解碼,Base64Decoder 和 Base64Encoder。
    創建和添加 ChannelHandler 的代碼示例如下。
	.childHandler( new ChannelInitializer<SocketChannel>() {
			@Override
			public void initChannel(SocketChannel ch) throws Exception {
				ch.pipeline().addLast( new EchoServerHandler() );
			}
	});

6、綁定並啓動監聽端口。在綁定監聽端口之前系統會做一系列的初始化和檢測工作,完成之後,會啓動監聽端口,並將 ServerSocketChannel 註冊到 Selector 上監聽客戶端連接。

7、Selector 輪詢。由 Reactor線程 NioEventLoop 負責調度和執行 Selector 輪詢操作,選擇準備就緒的 Channel集合,相關代碼如下。

public final class NioEventLoop extends SingleThreadEventLoop {

    private void select(boolean oldWakenUp) throws IOException {
        Selector selector = this.selector;
				
		......

        int selectedKeys = selector.select(timeoutMillis);
        selectCnt ++;

  		......
    			
    }
}

8、當輪詢到 準備就緒的Channel 之後,就由 Reactor線程 NioEventLoop 執行 ChannelPipeline 的相應方法,最終調度並執行 ChannelHandler,接口如下圖所示。
在這裏插入圖片描述

9、執行 Netty 中 系統的ChannelHandler 和 用戶添加定製的ChannelHandler 。ChannelPipeline 根據網絡事件的類型,調度並執行 ChannelHandler,相關代碼如下。

public class DefaultChannelPipeline implements ChannelPipeline {

    @Override
    public final ChannelPipeline fireChannelRead(Object msg) {
        AbstractChannelHandlerContext.invokeChannelRead(head, msg);
        return this;
    }
}

結合 Netty源碼 對服務端的創建過程進行解析

首先通過構造函數創建 ServerBootstrap實例,隨後,通常會創建兩個 EventLoopGroup實例 (也可以只創建一個並共享),代碼如下。

	EventLoopGroup acceptorGroup = new NioEventLoopGroup();
	EventLoopGroup iOGroup = new NioEventLoopGroup();

NioEventLoopGroup 實際就是一個 Reactor線程池,負責調度和執行客戶端的接入、網絡讀寫事件的處理、用戶自定義任務和定時任務的執行。通過 ServerBootstrap 的 group()方法 將兩個 EventLoopGroup實例 傳入,代碼如下。

public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {

    /**
     * Set the {@link EventLoopGroup} for the parent (acceptor) and the child (client). These
     * {@link EventLoopGroup}'s are used to handle all the events and IO for {@link ServerChannel} and
     * {@link Channel}'s.
     */
    public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
        super.group(parentGroup);
        if (childGroup == null) {
            throw new NullPointerException("childGroup");
        }
        if (this.childGroup != null) {
            throw new IllegalStateException("childGroup set already");
        }
        this.childGroup = childGroup;
        return this;
    }
}

其中 parentGroup對象 被設置進了 ServerBootstrap 的父類 AbstractBootstrap 中,代碼如下。

public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {

    volatile EventLoopGroup group;

    /**
     * The {@link EventLoopGroup} which is used to handle all the events for the to-be-created
     * {@link Channel}
     */
    public B group(EventLoopGroup group) {
        if (group == null) {
            throw new NullPointerException("group");
        }
        if (this.group != null) {
            throw new IllegalStateException("group set already");
        }
        this.group = group;
        return self();
    }
}

該方法會被客戶端和服務端重用,用於設置 工作IO線程,執行和調度網絡事件的讀寫。線程組和線程類型設置完成後,需要設置 服務端Channel 用於端口監聽和客戶端鏈路接入。Netty 通過 Channel工廠類 來創建不同類型的 Channel,對於服務端,需要創建 NioServerSocketChannel。所以,通過指定 Channel類型 的方式創建 Channel工廠。ReflectiveChannelFactory 可以根據 Channel的類型 通過反射創建 Channel的實例,服務端需要創建的是 NioServerSocketChannel實例,代碼如下。

public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {

    private final Constructor<? extends T> constructor;

    public ReflectiveChannelFactory(Class<? extends T> clazz) {
        ObjectUtil.checkNotNull(clazz, "clazz");
        try {
            this.constructor = clazz.getConstructor();
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) +
                    " does not have a public non-arg constructor", e);
        }
    }

    @Override
    public T newChannel() {
        try {
            return constructor.newInstance();
        } catch (Throwable t) {
            throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t);
        }
    }
}

指定 NioServerSocketChannel 後,需要設置 TCP 的一些參數,作爲服務端,主要是設置 TCP 的 backlog參數。

backlog 指定了內核爲此套接口排隊的最大連接個數,對於給定的監聽套接口,內核要維護兩個隊列:未鏈接隊列 和 已連接隊列,根據 TCP三次握手 的 三個子過程來分隔這兩個隊列。服務器處於 listen狀態 時,收到客戶端 syn過程(connect) 時在未完成隊列中創建一個新的條目,然後用三次握手的第二個過程,即服務器的 syn響應客戶端,此條目在第三個過程到達前 (客戶端對服務器 syn 的 ack) 一直保留在未完成連接隊列中,如果三次握手完成,該條目將從未完成連接隊列搬到已完成連接隊列尾部。當進程調用 accept 時,從已完成隊列中的頭部取出一個條目給進程,當已完成隊列爲空時進程將睡眠,直到有條目在已完成連接隊列中才喚醒。backlog 被規定爲兩個隊列總和的最大值,大多數實現默認值爲 5,但在高併發 Web服務器 中此值顯然不夠。 需要設置此值更大一些的原因是,未完成連接隊列的長度可能因爲客戶端 syn 的到達及等待三次握手的第三個過程延時 而增大。Netty 默認的 backlog 爲 100,當然,用戶可以修改默認值,這需要根據實際場景和網絡狀況進行靈活設置。

TCP參數 設置完成後,用戶可以爲啓動輔助類和其父類分別指定 Handler。兩者 Handler 的用途不同:子類中的 Handler 是 NioServerSocketChannel 對應的 ChannelPipeline 的 Handler;父類中的 Handler 是客戶端新接入的連接 SocketChannel 對應的 ChannelPipeline 的 Handler。兩者的區別可以通過下圖來展示。
在這裏插入圖片描述
本質區別就是:ServerBootstrap 中的 Handler 是 NioServerSocketChannel 使用的,所有連接該監聽端口的客戶端都會執行它;父類AbstractBootstrap 中的 Handler 是個工廠類,它爲每個新接入的客戶端都創建一個新的 Handler。

服務端啓動的最後一步,就是綁定本地端口,啓動服務,下面我們來分析下這部分代碼。

public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {

    private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {
            return regFuture;
        }

        if (regFuture.isDone()) {
            // At this point we know that the registration was complete and successful.
            ChannelPromise promise = channel.newPromise();
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        } else {
            // Registration future is almost always fulfilled already, but just in case it's not.
            final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
            regFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    Throwable cause = future.cause();
                    if (cause != null) {
                        // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
                        // IllegalStateException once we try to access the EventLoop of the Channel.
                        promise.setFailure(cause);
                    } else {
                        // Registration was successful, so set the correct executor to use.
                        // See https://github.com/netty/netty/issues/2586
                        promise.registered();

                        doBind0(regFuture, channel, localAddress, promise);
                    }
                }
            });
            return promise;
        }
    }
}

先看下上述代碼調用的 initAndRegister()方法。它首先實例化了一個 NioServerSocketChannel類型 的 Channel對象。相關代碼如下。

    final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            channel = channelFactory.newChannel();
            init(channel);
        } catch (Throwable t) {
            if (channel != null) {
                // channel can be null if newChannel crashed (eg SocketException("too many open files"))
                channel.unsafe().closeForcibly();
                // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
                return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
            }
            // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
            return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
        }

        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }
        return regFuture;
    }

NioServerSocketChannel 創建成功後,對它進行初始化,初始化工作主要有以下三點。

    @Override
    void init(Channel channel) throws Exception {
        final Map<ChannelOption<?>, Object> options = options0();
        synchronized (options) {
            setChannelOptions(channel, options, logger);
        }

        // 1、設置 Socket參數 和 NioServerSocketChannel 的附加屬性
        final Map<AttributeKey<?>, Object> attrs = attrs0();
        synchronized (attrs) {
            for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
                @SuppressWarnings("unchecked")
                AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
                channel.attr(key).set(e.getValue());
            }
        }

        // 2、將 AbstractBootstrap 的 Handler 添加到 NioServerSocketChannel
        //    的 ChannelPipeline 中
        ChannelPipeline p = channel.pipeline();

        final EventLoopGroup currentChildGroup = childGroup;
        final ChannelHandler currentChildHandler = childHandler;
        final Entry<ChannelOption<?>, Object>[] currentChildOptions;
        final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
        synchronized (childOptions) {
            currentChildOptions = childOptions.entrySet().toArray(newOptionArray(0));
        }
        synchronized (childAttrs) {
            currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(0));
        }

        // 3、將用於服務端註冊的 Handler ServerBootstrapAcceptor 添加到 ChannelPipeline 中
        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) throws Exception {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler();
                if (handler != null) {
                    pipeline.addLast(handler);
                }

                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }

到此,Netty 服務端監聽的相關資源已經初始化完畢,就剩下最後一步,註冊 NioServerSocketChannel 到 Reactor線程 的多路複用器上,然後輪詢客戶端連接事件。在分析註冊代碼之前,我們先通過下圖,看看目前 NioServerSocketChannel 的 ChannelPipeline 的組成。
在這裏插入圖片描述
最後,我們看下 NioServerSocketChannel 的註冊。當 NioServerSocketChannel 初始化完成之後,需要將它註冊到 Reactor線程 的多路複用器上監聽新客戶端的接入,代碼如下。

public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {

    protected abstract class AbstractUnsafe implements Unsafe {

        /**
         * 將完成初始化的 NioServerSocketChannel 註冊到 Reactor線程
         * 的多路複用器上,監聽新客戶端的接入
         */
        @Override
        public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            
            ......

            // 首先判斷是否是 NioEventLoop 自身發起的操作。如果是,則不存在併發操作,直接
            // 執行 Channel註冊;如果由其他線程發起,則封裝成一個 Task 放入消息隊列中異步執行。
            // 此處,由於是由 ServerBootstrap 所在線程執行的註冊操作,所以會將其封裝成 Task 投遞
            // 到 NioEventLoop 中執行
            if (eventLoop.inEventLoop()) {
                register0(promise);
            } else {
                try {
                    eventLoop.execute(new Runnable() {
                        @Override
                        public void run() {
                            register0(promise);
                        }
                    });
                } catch (Throwable t) {
                    
                    ......
                    
                }
            }
        }

        private void register0(ChannelPromise promise) {
            try {
                // check if the channel is still open as it could be closed in the mean time when the register
                // call was outside of the eventLoop
                if (!promise.setUncancellable() || !ensureOpen(promise)) {
                    return;
                }
                boolean firstRegistration = neverRegistered;
                // 該方法在本類中是一個空實現,下面看一下它在子類 AbstractNioChannel 中的實現
                doRegister();
                neverRegistered = false;
                registered = true;

                pipeline.invokeHandlerAddedIfNeeded();

                safeSetSuccess(promise);
                pipeline.fireChannelRegistered();
                if (isActive()) {
                    if (firstRegistration) {
                        pipeline.fireChannelActive();
                    } else if (config().isAutoRead()) {
                        beginRead();
                    }
                }
            } catch (Throwable t) {
                closeForcibly();
                closeFuture.setClosed();
                safeSetFailure(promise, t);
            }
        }
	}
}


public abstract class AbstractNioChannel extends AbstractChannel {

    @Override
    protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
            	// 將 NioServerSocketChannel 註冊到 NioEventLoop 的 多路複用器Selector 上
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
                return;
            } catch (CancelledKeyException e) {
                
                ......
                
            }
        }
    }
}

到此,服務端監聽啓動部分源碼已經分析完成。

結合 Netty源碼 對客戶端接入過程進行解析

負責處理網絡讀寫、連接和客戶端請求接入的 Reactor線程 就是 NioEventLoop,下面我們看下 NioEventLoop 是如何處理新的客戶端連接接入的。當 多路複用器 檢測到新的準備就緒的 Channel 時,默認執行 processSelectedKeysOptimized()方法,代碼如下。

public final class NioEventLoop extends SingleThreadEventLoop {

    private void processSelectedKeys() {
        if (selectedKeys != null) {
            processSelectedKeysOptimized();
        } else {
            processSelectedKeysPlain(selector.selectedKeys());
        }
    }

    private void processSelectedKeysOptimized() {
        for (int i = 0; i < selectedKeys.size; ++i) {
            final SelectionKey k = selectedKeys.keys[i];
            selectedKeys.keys[i] = null;

            final Object a = k.attachment();

            if (a instanceof AbstractNioChannel) {
                // 根據就緒的操作位 SelectionKey,執行不同的操作
                processSelectedKey(k, (AbstractNioChannel) a);
            } else {
                @SuppressWarnings("unchecked")
                NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
                processSelectedKey(k, task);
            }

            if (needsToSelectAgain) {
                selectedKeys.reset(i + 1);
                selectAgain();
                i = -1;
            }
        }
    }

    // 根據就緒的操作位 SelectionKey,執行不同的操作
    private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
        // 由於不同的 Channel 執行不同的操作,所以 NioUnsafe 被設計成接口
        // 由不同的 Channel 內部的 NioUnsafe實現類 負責具體實現
        final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
        if (!k.isValid()) {
            final EventLoop eventLoop;
            try {
                eventLoop = ch.eventLoop();
            } catch (Throwable ignored) {
                return;
            }
            if (eventLoop != this || eventLoop == null) {
                return;
            }
            unsafe.close(unsafe.voidPromise());
            return;
        }

        try {
            int readyOps = k.readyOps();
            if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
                int ops = k.interestOps();
                ops &= ~SelectionKey.OP_CONNECT;
                k.interestOps(ops);
                unsafe.finishConnect();
            }

            if ((readyOps & SelectionKey.OP_WRITE) != 0) {
                ch.unsafe().forceFlush();
            }

            // read()方法 的實現有兩個,分別是 NioByteUnsafe 和 NioMessageUnsafe,
            // 對於 NioServerSocketChannel,它使用的是 NioMessageUnsafe
            // 下面看一下 NioMessageUnsafe 對 read() 方法的實現
            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                unsafe.read();
            }
        } catch (CancelledKeyException ignored) {
            unsafe.close(unsafe.voidPromise());
        }
    }
}


public abstract class AbstractNioMessageChannel extends AbstractNioChannel {

    private final class NioMessageUnsafe extends AbstractNioUnsafe {

        private final List<Object> readBuf = new ArrayList<Object>();

        @Override
        public void read() {
            assert eventLoop().inEventLoop();
            final ChannelConfig config = config();
            final ChannelPipeline pipeline = pipeline();
            final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
            allocHandle.reset(config);

            boolean closed = false;
            Throwable exception = null;
            try {
                try {
                    do {
                        // 接收新的客戶端連接並創建 NioSocketChannel
                        int localRead = doReadMessages(readBuf);
                        if (localRead == 0) {
                            break;
                        }
                        if (localRead < 0) {
                            closed = true;
                            break;
                        }
                        allocHandle.incMessagesRead(localRead);
                    } while (allocHandle.continueReading());
                } catch (Throwable t) {
                    exception = t;
                }

                int size = readBuf.size();
                for (int i = 0; i < size; i ++) {
                    readPending = false;
                    // 接收到新的客戶端連接後,觸發 ChannelPipeline 的 channelRead方法。
                    // 事件在 ChannelPipeline 中傳遞,執行 ServerBootstrapAcceptor 的
                    // channelRead方法
                    pipeline.fireChannelRead(readBuf.get(i));
                }
                
                ......
            
            }
        }
    }
}


public class NioServerSocketChannel extends AbstractNioMessageChannel
                             implements io.netty.channel.socket.ServerSocketChannel {

    /**
     * 接收新的客戶端連接並創建 NioSocketChannel
     */
    @Override
    protected int doReadMessages(List<Object> buf) throws Exception {
        SocketChannel ch = SocketUtils.accept(javaChannel());

        try {
            if (ch != null) {
                buf.add(new NioSocketChannel(this, ch));
                return 1;
            }
        } catch (Throwable t) {
            
            ......
            
        }
        return 0;
    }
}


public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {

    private static class ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter {

        /**
         * 該方法主要分爲如下三個步驟。
         */
        @Override
        @SuppressWarnings("unchecked")
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            final Channel child = (Channel) msg;
			// 第一步:將啓動時傳入的 childHandler 加入到客戶端 SocketChannel 的 ChannelPipeline 中
            child.pipeline().addLast(childHandler);
			// 第二步:設置客戶端 SocketChannel 的 TCP參數
            setChannelOptions(child, childOptions, logger);
            for (Entry<AttributeKey<?>, Object> e: childAttrs) {
                child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
            }
			// 第三步:註冊 SocketChannel 到多路複用器
            try {
                childGroup.register(child).addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {
                        if (!future.isSuccess()) {
                            forceClose(child, future.cause());
                        }
                    }
                });
            } catch (Throwable t) {
                forceClose(child, t);
            }
        }
	}
}

下面我們展開看下 NioSocketChannel 的 register()方法。NioSocketChannel 的註冊方法與 ServerSocketChannel 的一致, 也是將 Channel 註冊到 Reactor線程 的多路複用器上。由於註冊的操作位是 0,所以,此時 NioSocketChannel 還不能讀取客戶端發送的消息,下面我們看看 是什麼時候修改監聽操作位爲 OP_READ 的。

執行完註冊操作之後,緊接着會觸發 ChannelReadComplete 事件。我們繼續分析 ChannelReadComplete 在 ChannelPipeline 中的處理流程:Netty 的 Header 和 Tail 本身不關注 ChannelReadComplete事件 就直接透傳,執行完 ChannelReadComplete 後,接着執行 PipeLine 的 read()方法,最終執行 HeadHandler 的 read()方法。

HeadHandler 的 read()方法用來將網絡操作位修改爲讀操作。創建 NioSocketChannel 的時候已經將 AbstractNioChannel 的 readInterestOp 設置爲 OP_ READ,這樣,執行 selectionKey. interestOps(interestOps | readInterestOp)操作 時就會把操作位設置爲 OP_READ。代碼如下。

public abstract class AbstractNioByteChannel extends AbstractNioChannel {

    protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
        super(parent, ch, SelectionKey.OP_READ);
    }
}

到此,新接入的客戶端連接處理完成,可以進行網絡讀寫等 IO操作。

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