Netty新連接的接入

新連接的建立

可以分爲三個步驟

1.檢測到有新的連接

2.將新的連接註冊到worker線程組

3.註冊新連接的讀事件

 檢測到有新連接的接入

我們已經知道,當服務端啓動後,服務端channel已經註冊到boss reactor線程中,reactor不斷檢測有新的事件,直到檢測出有accept事件發生

NioEventLoop.java

 private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
        final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
    
            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                unsafe.read();
            }
        } catch (CancelledKeyException ignored) {
            unsafe.close(unsafe.voidPromise());
        }
    }

上面這段代碼表示boss reactor線程已經輪訓到SelectionKey.OP_ACCEPT事件,說明有新連接接入,此時將調用Unsafe進行實際操作

NioMessageUnsafe
 @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 {
                        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;
                    pipeline.fireChannelRead(readBuf.get(i));
                }
                readBuf.clear();
                allocHandle.readComplete();
                pipeline.fireChannelReadComplete();

                if (exception != null) {
                    closed = closeOnReadError(exception);

                    pipeline.fireExceptionCaught(exception);
                }

                if (closed) {
                    inputShutdown = true;
                    if (isOpen()) {
                        close(voidPromise());
                    }
                }
            } finally {
                // Check if there is a readPending which was not processed yet.
                // This could be for two reasons:
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
                //
                // See https://github.com/netty/netty/issues/2254
                if (!readPending && !config.isAutoRead()) {
                    removeReadOp();
                }
            }
        }
    }

可以調用doReadMessages方法不斷的讀取消息,用readBuf做爲容器,這裏是讀取一個個連接,然後調用pipeline.fireChannelRead(),將每條連接通過pipeline的handler,之後便是清理容器,觸發pipeline.fireChannelReadComplete(),整個過程清晰明瞭。

下面分析一下doReadMessages

@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) {
            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;
    }
  public static SocketChannel accept(final ServerSocketChannel serverSocketChannel) throws IOException {
        try {
            return AccessController.doPrivileged(new PrivilegedExceptionAction<SocketChannel>() {
                @Override
                public SocketChannel run() throws IOException {
                    return serverSocketChannel.accept();
                }
            });
        } catch (PrivilegedActionException e) {
            throw (IOException) e.getCause();
        }
    }

這裏通過serverSocketChannel.accept()標準的jdk的玩法,返回客戶端的channel,然後在初始化NioSocketChannel,初始化類似於初始化NioServerSocketChannel,不在細講。

pipeline.fireChannelRead(NioSocketChannel);

在服務端啓動的時候,在處理新連接的pipeline中,已經自動添加了一個handler爲ServerBootstrapAcceptor,並且已經將用戶代碼中設置的一系列的參數傳入了構造函數,接下來,我們就來看下ServerBootstrapAcceptor

 @Override
        @SuppressWarnings("unchecked")
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            final Channel child = (Channel) msg;

            child.pipeline().addLast(childHandler);

            setChannelOptions(child, childOptions, logger);

            for (Entry<AttributeKey<?>, Object> e: childAttrs) {
                child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
            }

            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);
            }
        }

前面的pipeline.fireChannelRead(NioSocketChannel();最終通過head-unsafe-ServerBootstrapAcceptor的調用到channelRead()方法的,而channelRead的msg就是之前我們new NioSocketChanel,將用戶設置的childChannel,添加到pipeline中。目前pipeline中對應的handler有head->ChannelInitializer->tail,這個後面會提到

childGroup.register(child)

private void register0(ChannelPromise promise) {
            try {
                // 檢查通道是否仍然打開,因爲它可以在寄存器的平均時間內關閉
                // 調用在eventLoop之外

                //promise=DefaultChannelPromise
                if (!promise.setUncancellable() || !ensureOpen(promise)) {
                    return;
                }
                boolean firstRegistration = neverRegistered;
                //調用NioServerSocketChannel 通過反射創建出來nio底層channel的register方法  選擇器看不同操作系統
                doRegister();
                neverRegistered = false;
                registered = true;

                // 確保在實際通知承諾之前調用handlerAdded(…)。這是需要的
                // 用戶可能已經通過ChannelFutureListener中的管道觸發事件。

                //會執行handlerAdded方法
                pipeline.invokeHandlerAddedIfNeeded();

                safeSetSuccess(promise);
                //會執行channelRegistered
                pipeline.fireChannelRegistered();

                // 只有當通道從未被註冊時,才激活該通道。這可以防止解僱
                // 如果取消註冊並重新註冊通道,則多個通道將激活。
                if (isActive()) {
                    if (firstRegistration) {
                        pipeline.fireChannelActive();
                    } else if (config().isAutoRead()) {
                        // 這個通道之前已經註冊,並設置了autoRead()。這意味着我們需要開始讀取
                        // 這樣我們就可以處理入站數據。
                        //
                        // See https://github.com/netty/netty/issues/4805
                        beginRead();
                    }
                }
            } catch (Throwable t) {
                // 直接關閉通道,避免FD泄漏。
                closeForcibly();
                closeFuture.setClosed();
                safeSetFailure(promise, t);
            }
        }

和服務端啓動過程一樣,先是調用doRegister();做真正的註冊流程,如下

protected void doRegister() throws Exception {
    boolean selected = false;
    for (;;) {
        try {
            selectionKey = javaChannel().register(eventLoop().selector, 0, this);
            return;
        } catch (CancelledKeyException e) {
            if (!selected) {
                eventLoop().selectNow();
                selected = true;
            } else {
                throw e;
            }
        }
    }
}

執行完綁定會執行pipeline.invokeHandlerAddedIfNeeded();

最終會調用ChannelInitializer 的 handlerAdded 方法

public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    if (ctx.channel().isRegistered()) {
        initChannel(ctx);
    }
}

 

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