netty服務端新連接接入的流程分析

由前面的分析可知NioEventLoop的run()方法是一個無限循環,NioEventLoop會不斷的調用Selector的select(timeout)方法查詢是否有新的IO事件,所以當一個客戶端連接進入的時候會被Boss線程select到,故新連接接入流程的入口爲Bose線程的select方法。

select(boolean oldWakenUp)方法如下所示(僅保留相關代碼):

private void select(boolean oldWakenUp) throws IOException {
 	......
    int selectedKeys = selector.select(timeoutMillis);
	......
    if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
        break;
    }   
    ......
   
}

當客戶端發起連接請求時,int selectedKeys = selector.select(timeoutMillis);返回的selectedKeys的值將會從0變爲1。從而滿足後面selectedKeys != 0 的判斷,進而跳出select()方法,執行後續的操作。

select到IO事件後會進入NIOEventLoop的processSelectedKeys()方法,因爲默認情況netty會對選擇器Selector的SelectionKeys做相關優化(將原先的HashSet通過反射的方式修改爲數組),故會執行NIOEventLoop的processSelectedKeysOptimized方法。

NIOEventLoop的processSelectedKeysOptimized()如下所示,因爲當前是服務端,所以當前ServerSocketChannel是AbstractNioChannel類的子類,故進而由調用processSelectedKey(SelectionKey k, AbstractNioChannel ch)方法處理

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

NIOEventLoop的processSelectedKey(SelectionKey k, AbstractNioChannel ch)方法如下所示

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
    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();
        }

        if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
            unsafe.read();
        }
    } catch (CancelledKeyException ignored) {
        unsafe.close(unsafe.voidPromise());
    }
}

該方法的主要作用是根據SelectionKey中就緒事件的類型,執行不同的邏輯。因爲當前分析的是服務端處理新連接的過程,由下面的代碼片段可知,對於SelectionKey.OP_ACCEPT類型的事件,將會調用Unsafe接口的read()方法進行處理,這裏實際調用的是AbstractNioMessageChannel的read()方法。

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

AbstractNioMessageChannel的read()方法如下:

read()方法中有三個核心的步驟,即:

int localRead = doReadMessages(readBuf);

**pipeline.fireChannelRead(readBuf.get(i));**

**pipeline.fireChannelReadComplete();**	
public void read() {
    assert eventLoop().inEventLoop();//斷言當前線程爲ServerSocketChannel綁定的EventLoop
    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 {
        if (!readPending && !config.isAutoRead()) {
            removeReadOp();
        }
    }
}

doReadMessages(List buf)

@Override
protected int doReadMessages(List<Object> buf) throws Exception {
    SocketChannel ch = SocketUtils.accept(javaChannel());//調用JDK的accpt方法獲取一個客戶端連接通道
    try {
        if (ch != null) {
            buf.add(new NioSocketChannel(this, ch));//創建一個netty的客戶端通道類,並加入到集合中
            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;
}

fireChannelRead(Object msg)方法

該方法會觸發Pipeline上所有的ChannelHandler的channelRead方法,最終會觸發ServerBootstrapAcceptor類的channelRead方法,該實例是在ServerSocketChannel實例化的時候添加到pipeline的尾部的(ServerBootStrap的init(Channel channel)方法)

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

接着分析ServerBootstrapAcceptor的channelRead(ChannelHandlerContext ctx, Object msg)方法

該方法主要邏輯爲:

1、根據ServerBootStrap中的配置,對客戶端通道SocketChannel進行設置

2、在work線程組中選擇一個NioEventLoop,並將當前客戶連接註冊在NioEventLoop的Selector上

當客戶端後續再有read事件被觸發,則會由當前work線程組中獲得的NioEventLoop進行處理

public void channelRead(ChannelHandlerContext ctx, Object msg) {
    final Channel child = (Channel) msg;//客戶端連接channel

    child.pipeline().addLast(childHandler);//添加childHandler

    setChannelOptions(child, childOptions, logger);//設置通道的option
	//設置socketChannel的屬性
    for (Entry<AttributeKey<?>, Object> e: childAttrs) {
        child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
    }
	//在work線程組裏挑選一個EventLoop,並將當前的SocketChannel註冊到EventLoop的Selector上
    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.fireChannelReadComplete();

pipeline.fireChannelReadComplete();方法同上面fireChannelRead方法的流程類似,將Pipeline中的所有ChannelHandler中的channelReadComplete(ChannelHandlerContext ctx)方法都執行一遍

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