Netty的服務端和客戶端TCP建鏈流程分析,你看這一篇就夠了

開篇

在此前的章節http://www.lxweimin.com/p/e38844b145fb,我們介紹了Netty的引導啟動類,但是并沒有詳細解析系統啟動后的建鏈過程,本文將會就這一過程進行深入解析。

服務端

1、監聽初始化

服務端從doBind()開始

    private ChannelFuture doBind(final SocketAddress localAddress) {
        // 完成初始化以及注冊操作
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {
            return regFuture;
        }

        if (regFuture.isDone()) {
            ChannelPromise promise = channel.newPromise();
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        } else {
            // 忽略
        }
    }

話不多說,直接看initAndRegister()

    final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
             // 創建NioServerSocketChannel
            channel = channelFactory.newChannel();
            // 初始化channel
            init(channel);
        } catch (Throwable t) {
            // 忽略 
        }

        // 注冊channel到EventLoop的selector
        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }
        return regFuture;
    }

采用反射方式創建NioServerSocketChannel,會調到其無參構造函數,最終會調用

    public NioServerSocketChannel(ServerSocketChannel channel) {
        super(null, channel, SelectionKey.OP_ACCEPT);
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }

可以看到會將channel的readInterestOp初始化為SelectionKey.OP_ACCEPT,用于接受建鏈請求。channel創建好后,開始進行初始化

    void init(Channel channel) {
        setChannelOptions(channel, newOptionsArray(), logger);
        setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));

        ChannelPipeline p = channel.pipeline();

        // 這個childGroup就是在引導類中配置的workerGroup
        final EventLoopGroup currentChildGroup = childGroup;
        final ChannelHandler currentChildHandler = childHandler;
        final Entry<ChannelOption<?>, Object>[] currentChildOptions;
        synchronized (childOptions) {
            currentChildOptions = childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY);
        }
        final Entry<AttributeKey<?>, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);

        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler();
                if (handler != null) {
                    // 將在啟動類中配置的channelHandler加入到NioServerSocketChannel對應的pipeline中
                    pipeline.addLast(handler);
                }

                // 將serverBootstrapAcceptor這個channelHandler加入到nioServerSocketChannel對應的pipeline中
                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }

ServerBootstrapAcceptor就是用來接收建鏈請求的。

        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            final Channel child = (Channel) msg;
            // 將啟動類中配置的childHandler添加到NioSocketChannel對應的pipeline中
            child.pipeline().addLast(childHandler);

            setChannelOptions(child, childOptions, logger);
            setAttributes(child, childAttrs);

            try {
                // 將NioSocketChannel注冊到EventLoop的selector上
                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);
            }
        }

好,我們返回到initAndRegister,至此NioServerSocketChannel已經創建并且初始化完畢,開始進行注冊操作

ChannelFuture regFuture = config().group().register(channel);

會調到MultithreadEventLoopGroup的register方法

    public ChannelFuture register(Channel channel) {
        return next().register(channel);
    }
    
    // 從引導類中配置的EventLoop線程池中輪詢一個EventLoop
    public EventLoop next() {
        return (EventLoop) super.next();
    }

接著往下,最終會調到AbstractChannel的register方法

        public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            // ...省略

            // 由于當前eventLoop的thread還是null,所以這里返回false
            if (eventLoop.inEventLoop()) {
                register0(promise);
            } else {
                try {
                    eventLoop.execute(new Runnable() {
                        @Override
                        public void run() {
                            register0(promise);
                        }
                    });
                } catch (Throwable t) {
                    logger.warn(
                            "Force-closing a channel whose registration task was not accepted by an event loop: {}",
                            AbstractChannel.this, t);
                    closeForcibly();
                    closeFuture.setClosed();
                    safeSetFailure(promise, t);
                }
            }
        }

進入到eventLoop的execute方法

    private void execute(Runnable task, boolean immediate) {
        boolean inEventLoop = inEventLoop();
        addTask(task);
        if (!inEventLoop) {
            startThread();
            if (isShutdown()) {
                boolean reject = false;
                try {
                    if (removeTask(task)) {
                        reject = true;
                    }
                } catch (UnsupportedOperationException e) {
                }
                if (reject) {
                    reject();
                }
            }
        }

        if (!addTaskWakesUp && immediate) {
            wakeup(inEventLoop);
        }
    }

將task(也即register0方法)放入隊列,判斷當前線程非EventLoop自己的線程,開始創建線程

    private void startThread() {
        if (state == ST_NOT_STARTED) {
            if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
                boolean success = false;
                try {
                    doStartThread();
                    success = true;
                } finally {
                    if (!success) {
                        STATE_UPDATER.compareAndSet(this, ST_STARTED, ST_NOT_STARTED);
                    }
                }
            }
        }
    }

    private void doStartThread() {
        assert thread == null;
        executor.execute(new Runnable() {
            @Override
            public void run() {
                // 將EventLoop的線程綁定為當前線程
                thread = Thread.currentThread();
                if (interrupted) {
                    thread.interrupt();
                }

                boolean success = false;
                updateLastExecutionTime();
                try {
                    // EventLoop線程開始死循環,處理IO事件和自定義任務以及定時任務
                    SingleThreadEventExecutor.this.run();
                    success = true;
                } catch (Throwable t) {
                    logger.warn("Unexpected exception from an event executor: ", t);
                } finally {
                    }

調用executor的execute,會新創建一個線程執行task,這個線程就是EventLoop綁定的線程,在整個系統的生命周期范圍內,這種綁定關系都是固定的,不會發生變更。下面來分解NioEventLoop的run方法

                    case SelectStrategy.SELECT:
                        long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
                        if (curDeadlineNanos == -1L) {
                            curDeadlineNanos = NONE; // nothing on the calendar
                        }
                        nextWakeupNanos.set(curDeadlineNanos);
                        try {
                            if (!hasTasks()) {
                                // 最終調用Java NIO的Selector的select方法處理IO事件
                                strategy = select(curDeadlineNanos);
                            }
                        } finally {
                            nextWakeupNanos.lazySet(AWAKE);
                        }
                    final long ioStartTime = System.nanoTime();
                    try {
                        // 處理準備好的IO事件
                        processSelectedKeys();
                    } finally {
                        final long ioTime = System.nanoTime() - ioStartTime;
                        ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                    }
                    // 從任務隊列取任務執行(還記得之前已經加入到隊列里的task,也即register0方法嗎?)
                    ranTasks = runAllTasks(0);

從任務隊列中取出任務,開始執行之前加入到任務隊列中的register0方法

        private void register0(ChannelPromise promise) {
            try {
                if (!promise.setUncancellable() || !ensureOpen(promise)) {
                    return;
                }
                boolean firstRegistration = neverRegistered;
                // 調用Java NIO方法,將channel注冊到selector
                doRegister();
                neverRegistered = false;
                // 設置已經注冊的標志位
                registered = true;

                pipeline.invokeHandlerAddedIfNeeded();

                safeSetSuccess(promise);
                pipeline.fireChannelRegistered();
                // isActive是通過channel是open狀態并且已經綁定本地地址和端口來判斷的
                if (isActive()) {
                    if (firstRegistration) {
                        pipeline.fireChannelActive();
                    } else if (config().isAutoRead()) {
                        beginRead();
                    }
                }
            } catch (Throwable t) {
                // 忽略
            }
        }

    protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                // 調用Java NIO方法,將channel注冊到selector,注意這里的監聽事件設置的為0,目的是要讓
                // channel執行完其他操作后,再設置OP_ACCEPT的監聽
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
                return;
            } 
            // 忽略
        }
    }

注意這里的register將channel注冊到selector時,傳遞的監聽事件為0,意思是什么都不監聽,等channel完成其他操作后,再設置OP_ACCEPT監聽,那么是在哪里設置的呢?

  1. 如果isActive方法返回true,并且autoRead是true(默認是true),則會進入beginRead,最終執行doBeginRead,所以注冊完成后,就會設置OP_ACCEPT事件監聽。
    protected void doBeginRead() throws Exception {
        final SelectionKey selectionKey = this.selectionKey;
        if (!selectionKey.isValid()) {
            return;
        }

        readPending = true;

        final int interestOps = selectionKey.interestOps();
        // 還記得之前創建NioServerSocketChannel時,設置readInterestOp為OP_ACCEPT嗎?將該事件加入 
        // 到NioServerSocketChannel對應的Selector的監聽序列中
        if ((interestOps & readInterestOp) == 0) {
            selectionKey.interestOps(interestOps | readInterestOp);
        }
    }
  1. 如果isActive方法返回false,又是在哪里將OP_ACCEPT事件加到監聽序列的呢?因為Netty的很多任務都是封裝成task,放入隊列中,再由EventLoop線程從隊列中取出執行,所以直接從代碼中跳躍著找不是很方便,我們可以在doBeginRead方法打個斷點,看下調用棧


    doBeginRead調用棧1.PNG
doBeginRead調用棧2.PNG

沒錯!我們在這里看到了熟悉的doBind0,這就是在本文的一開始的doBind方法中設置的監聽回調

            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) {
                        promise.setFailure(cause);
                    } else {
                        promise.registered();
                        doBind0(regFuture, channel, localAddress, promise);
                    }
                }
            });

也就是在上面的NioServerSocketChannel初始化并注冊成功后,會調到doBind0方法,在這個方法中將Channel的bind操作封裝成一個任務,加入到EventLoop的任務隊列

    private static void doBind0(
            final ChannelFuture regFuture, final Channel channel,
            final SocketAddress localAddress, final ChannelPromise promise) {
        channel.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                if (regFuture.isSuccess()) {
                    channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
                } else {
                    promise.setFailure(regFuture.cause());
                }
            }
        });
    }

接下來,這個bind操作會在NioServerSocketChannel對應的pipeline上的outBound方向的channelHandler之間傳遞,也就是tail handler--> 其他的handler -> head headler,最終在head handler的bind方法中調用AbstractChannel的bind真正執行綁定操作

        public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
           // 省略...
            boolean wasActive = isActive();
            try {
                // 調用Java NIO接口真正執行綁定本地地址和端口的操作
                doBind(localAddress);
            } catch (Throwable t) {
                safeSetFailure(promise, t);
                closeIfClosed();
                return;
            }

            if (!wasActive && isActive()) {
                invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        // 觸發channelActive事件
                        pipeline.fireChannelActive();
                    }
                });
            }

            safeSetSuccess(promise);
        }

    protected void doBind(SocketAddress localAddress) throws Exception {
        if (PlatformDependent.javaVersion() >= 7) {
            javaChannel().bind(localAddress, config.getBacklog());
        } else {
            javaChannel().socket().bind(localAddress, config.getBacklog());
        }
    }

可以看到,在綁定操作執行完后,觸發了channelActive事件,這個事件首先會被head handler的channelActive方法處理

        public void channelActive(ChannelHandlerContext ctx) {
            ctx.fireChannelActive();

            readIfIsAutoRead();
        }

        private void readIfIsAutoRead() {
            if (channel.config().isAutoRead()) {
                channel.read();
            }
        }

這個read操作同樣的跟上面的bind操作一樣,會在NioServerSocketChannel對應的pipeline上的outBound方向的channelHandler之間傳遞,也就是tail handler--> 其他的handler -> head headler,最終調到head handler的read方法

        public void read(ChannelHandlerContext ctx) {
            unsafe.beginRead();
        }

        public final void beginRead() {
            assertEventLoop();

            if (!isActive()) {
                return;
            }

            try {
                doBeginRead();
            } catch (final Exception e) {
                invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.fireExceptionCaught(e);
                    }
                });
                close(voidPromise());
            }
        }

經過百轉千回終于走到doBeginRead方法了!有沒有被繞暈,哈哈。不要著急,現在只是完成了本地地址和端口的綁定,NioServerSocketChannel的selector開始監聽OP_ACCEPT事件,做好了接收建鏈請求的準備。
那么,當有客戶端建鏈請求進來時,又是怎樣處理的?接著往下看。

2、接收建鏈請求

回到EventLoop的run方法,當有IO事件被監聽到時,會調用processSelectedKeys方法

                    final long ioStartTime = System.nanoTime();
                    try {
                        processSelectedKeys();
                    } finally {
                        // Ensure we always run tasks.
                        final long ioTime = System.nanoTime() - ioStartTime;
                        ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                    }

在processSelectedKey方法中,判斷是OP_ACCEPT事件,則會調用unsafe的read方法

            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                unsafe.read();
            }

        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 {
                        // 調用NioServerSocketChannel的doReadMessages方法
                        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;
                }

在NioServerSocketChannel的doReadMessages方法中調用Java NIO的accept方法真正接受建鏈請求,并new一個NioSocketChannel用于后續的讀寫IO操作。

    protected int doReadMessages(List<Object> buf) throws Exception {
        SocketChannel ch = SocketUtils.accept(javaChannel());

        try {
            if (ch != null) {
                // 新建一個NioSocketChannel用于后續的讀寫請求
                buf.add(new NioSocketChannel(this, ch));
                return 1;
            }
        } catch (Throwable t) {
            logger.warn("Failed to create a new channel from an accepted socket.", t);

            try {
                ch.close();
            } catch (Throwable t2) {
                logger.warn("Failed to close a socket.", t2);
            }
        }

        return 0;
    }

再回到read方法,這時候的readBuf中已經加入了新創建的NioSocketChannel,所以會觸發channelRead事件在pipeline中傳遞。

               int size = readBuf.size();
               for (int i = 0; i < size; i ++) {
                   readPending = false;
                   pipeline.fireChannelRead(readBuf.get(i));
               }

還記得我們在channel初始化的時候,把ServerBootstrapAcceptor這個handler加入到pipeline了嗎,來看下它的channelRead回調處理

        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            // 這個msg,其實就是NioSocketChannel
            final Channel child = (Channel) msg;
            // 將啟動類中配置的channelInitializer加入到NioSocketChannel對應的pipeline
            child.pipeline().addLast(childHandler);
            // 設置NioSocketChannel的屬性
            setChannelOptions(child, childOptions, logger);
            setAttributes(child, childAttrs);

            try {
                // 將NioSocketChannel注冊到childGroup的EventLoop的selector,準備接收和發送數據
                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);
            }
        }

就是在這里,初始化由NioServerSocketChannel創建的NioSocketChannel,并將其注冊到在啟動類中配置的childGroup線程池中的某個EventLoop上,準備接收發送數據。
至此,服務端的啟動初始化以及建鏈流程分析完畢,總結如下:

  1. 利用反射,創建NioServerSocketChannel,并進行初始化,同時將ServerBootstrapAcceptor加入到pipeline;
  2. 將NioServerSocketChannel注冊到EventLoop的selector上,在這個過程中會創建新線程,開始執行NioEventLoop的run方法,run方法內部會監聽IO事件,同時從任務隊列中取任務執行,這個注冊操作其實也是隊列中的任務;
  3. 在注冊成功的回調里面執行綁定本地地址和端口的操作;
  4. 綁定成功后,會觸發channelActive事件,這個事件首先會被每個pipeline都有的head handler攔截處理;
  5. 在head handler觸發read操作,最終會執行到doBeginRead,將之前創建NioServerSocketChannel時,設置的OP_ACCEPT事件加入到NioServerSocketChannel對應的Selector的監聽序列中,至此,監聽初始化完畢。
  6. NioEventLoop的run方法監聽到OP_ACCEPT事件;
  7. 在processSelectedKey中調用NioServerSocketChannel的doReadMessages,新建NioSocketChannel,用于處理后續的讀寫請求,同時觸發channelRead事件;
  8. ServerBootstrapAcceptor攔截channelRead事件,初始化NioSocketChannel,并將NioSocketChannel注冊到childGroup的EventLoop上,這個注冊流程和NioServerSocketChannel相似,只是設置的readInterestOp為OP_READ,用于讀取消息,不再贅述。
  9. 至此,NioSocketChannel真正接管NioServerSocketChannel accept的底層socket鏈路,開始收發消息。

客戶端

有了以上服務端的啟動、初始化、建鏈流程的分析后,客戶端的流程就相對來說簡單很多了。
客戶端從doResolveAndConnect開始

    private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();

        if (regFuture.isDone()) {
            if (!regFuture.isSuccess()) {
                return regFuture;
            }
            return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());

和服務端一樣,也是調用initAndRegister進行channel的初始化,并將channel注冊到EventLoop上,不同的是,初始化的監聽事件是OP_READ,而不是OP_ACCEPT,具體過程不再重復。

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

接下來調用doResolveAndConnect0和doConnect方法,將connect操作封裝成task加入到任務隊列中

    private static void doConnect(
            final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) {
        final Channel channel = connectPromise.channel();
        channel.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                if (localAddress == null) {
                    channel.connect(remoteAddress, connectPromise);
                } else {
                    channel.connect(remoteAddress, localAddress, connectPromise);
                }
                connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
            }
        });
    }

EventLoop的線程從隊列中取出任務執行。connect操作在pipeline上流轉,connect是outBound方向的操作,所以先從tail handler開始,最終到head handler,調用NioSocketChannel的doConnect方法

    protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
        if (localAddress != null) {
            doBind0(localAddress);
        }

        boolean success = false;
        try {
            // 調用Java NIO的接口發送建鏈請求
            boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);
            if (!connected) {
                // 將監聽事件改為OP_CONNECT
                selectionKey().interestOps(SelectionKey.OP_CONNECT);
            }
            success = true;
            return connected;
        } finally {
            if (!success) {
                doClose();
            }
        }
    }

當監聽到OP_CONNECT事件時,調用Java NIO的finishConnect結束建鏈流程,標識已經建鏈成功,為收發消息做好準備。

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

                unsafe.finishConnect();
            }

至此,客戶端的建鏈流程分析完畢,整個流程,相對服務端簡單很多。

總結

本文詳細分析了Netty作為客戶端和服務端的整個初始化和建鏈過程,其中會涉及到許多Netty的核心概念,后面會針對這些概念逐個進行分析。Netty的整體框架是基于異步編程的概念構建的,跟我們平時的編碼習慣可能有些差別,不過,沒關系,平時多看看源碼,看多了也就習慣了。Netty的源碼也有很多值得我們借鑒的編碼習慣或者說技巧,在我們平時的編碼中可以加以應用,針對這點,后面我也會專門寫一篇文章來總結。
需要說明一下,本文是基于目前(2020.8.18)Netty最新的4.1.52版本的源碼進行解析的。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。