Netty——新连接接入

280 阅读5分钟

NioEventLoop#processSelectedKey方法里这么一段代码

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

这里就有处理新连接接入的事件,现在跟进去到AbstractNioMessageChannel内部类NioMessageUnsaferead方法

public void read() {
    assert eventLoop().inEventLoop();
    final ChannelConfig config = config();
    final ChannelPipeline pipeline = pipeline();
    //allocHandle主要用来控制读取连接的速率
    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) {
            //省略
        }

        int size = readBuf.size();
        for (int i = 0; i < size; i ++) {
            readPending = false;
            //处理每个新接入的连接,为每个新接入的连接分配NioEventLoop和注册selector
            pipeline.fireChannelRead(readBuf.get(i));
        }
        readBuf.clear();
        allocHandle.readComplete();
        pipeline.fireChannelReadComplete();

        //关闭和处理异常,省略
    } finally {
        //省略
    }
}

概括一下这里主要做的几件事

  • 检测新连接,控制新连接接入速率
  • 为每个新连接创建NioSocketChannel
  • 为每个NioSocketChannel分配EventLoop,注册channel,向selector上注册读事件

检测新接入的连接

来看看检测新接入的连接这段代码

do {
    //读取新接入的连接并返回读取的数目
    int localRead = doReadMessages(readBuf);
    if (localRead == 0) {
        //若读取不到新连接,跳出循环
        break;
    }
    if (localRead < 0) {
        closed = true;
        break;
    }

    //增加已连接的客户端数
    allocHandle.incMessagesRead(localRead);
} while (allocHandle.continueReading());

这里来看看循环条件里的continueReading方法,这个方法在DefaultMaxMessagesRecvByteBufAllocator类的内部类MaxMessageHandle

public boolean continueReading(UncheckedBooleanSupplier maybeMoreDataSupplier) {
    return config.isAutoRead() &&
        (!respectMaybeMoreData || maybeMoreDataSupplier.get()) &&
        totalMessages < maxMessagePerRead &&
        totalBytesRead > 0;
}

简单概括一下,就是控制每次总共读取的连接数不能超过一定数目

创建NioSocketChannel

跟进上面的doReadMessages方法,来到NioServerSocketChannel#doReadMessages这个方法

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

    try {
        if (ch != null) {
            //注意这里将新创建的NioSocketChannel放入list中,后面处理新连接接入需要用到
            buf.add(new NioSocketChannel(this, ch));
            return 1;
        }
    } catch (Throwable t) {
        //省略
    }

    return 0;
}

可以看到,这里创建了一个NioSocketChannel

继续跟进看NioSocketChannel的构造函数

public NioSocketChannel(Channel parent, SocketChannel socket) {
    super(parent, socket);
    config = new NioSocketChannelConfig(this, socket.socket());
}

super方法调用父类构造函数,调用configureBlocking(false)设置非阻塞,设置id,unsafe,pipeline等成员变量,并且设置关注的事件为read事件(注意只是设置成员变量,并未注册到selector)

另外值得注意的是,super方法调用父类AbstractNioByteChannel的构造函数里

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

注意这里传入了一个SelectionKey.OP_READ,这个值会在后面向selector注册读事件时用到

NioSocketChannel的构造函数里还创建了一个NioSocketChannelConfig,值得注意的是这个类的构造函数调用父类禁止了Nagle算法(Nagle算法将小的数据包封装成大的数据包再发送出去,而netty禁止这个算法,让数据包能够及时发送出去)

分配NioEventLoop和注册selector

接下来看处理每次新接入的连接的代码

int size = readBuf.size();
for (int i = 0; i < size; i ++) {
    readPending = false;
    //pipeline为服务端的pipeline
    pipeline.fireChannelRead(readBuf.get(i));
}

readBuf是一个list,里面装的就是之前创建的NioSocketChannel

之前解析过服务端启动的代码,在那里提到,在往pipeline里添加handler时,会额外添加一个特殊的handler,即ServerBootstrapAcceptor,这个handler就是用来处理服务端的接入的

一般情况下,服务端pipeline里的handler排列情况一般是 Head——ServerBootstrapAcceptor——Tail 这种样

现在这里pipeline发送了一个read事件,那么就会从head节点开始传播到ServerBootstrapAcceptor

接下来看看ServerBootstrapAcceptorchannelRead方法,注意这个类是ServerBootstrap的内部类

public void channelRead(ChannelHandlerContext ctx, Object msg) {
    final Channel child = (Channel) msg;

    //添加childHandler
    child.pipeline().addLast(childHandler);
    //设置用户传入的childOptions(用于channel)
    setChannelOptions(child, childOptions, logger);
    //设置用户传入的childAttrs(tcp参数等)
    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);
    }
}

添加的childHandler通常是是用户在调用childHandler时传入的一个特殊的handler即ChannelInitializer,这个handler在被添加后会调用我们重写的initChannel方法添加我们自己的handler

接下来register方法,这里会调用到MultithreadEventLoopGroup#register方法

public ChannelFuture register(Channel channel) {
    return next().register(channel);
}

这里的next方法会调用线程选择器选择一个NioEventLoop(线程选择器在netty——NioEventLoopGroup创建里说过)

接下来这里的register方法会调用到SingleThreadEventLoop#register方法,最终调用到AbstractUnsafe类的register方法(这个类在AbstractChannel类里)

public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    ObjectUtil.checkNotNull(eventLoop, "eventLoop");
    //省略无关代码

    AbstractChannel.this.eventLoop = eventLoop;

    //由于当前是在服务端的eventloop里而非选择器保存的eventloop里,所以此处会走向else分支
    if (eventLoop.inEventLoop()) {
        register0(promise);
    } else {
        try {
            eventLoop.execute(new Runnable() {
                @Override
                public void run() {
                    register0(promise);
                }
            });
        } catch (Throwable t) {
            //省略
        }
    }
}

接下来看看register0方法

private void register0(ChannelPromise promise) {
    try {
        if (!promise.setUncancellable() || !ensureOpen(promise)) {
            return;
        }
        boolean firstRegistration = neverRegistered;
        //调用jdk底层的SelectableChannel类的register方法注册channel
        doRegister();
        neverRegistered = false;
        registered = true;

        //发送handler添加事件
        pipeline.invokeHandlerAddedIfNeeded();

        safeSetSuccess(promise);
        //发送channel注册成功事件
        pipeline.fireChannelRegistered();
        if (isActive()) {
            if (firstRegistration) {
                //这里是第一次注册,会发送channelActive事件
                pipeline.fireChannelActive();
            } else if (config().isAutoRead()) {
                beginRead();
            }
        }
    } catch (Throwable t) {
        //省略
    }
}

这里主要做了

  • 调用jdk底层的SelectableChannel类的register方法注册channel
  • 发送handler添加事件
  • 发送channel注册成功事件
  • 发送channelActive事件

注意到,这里发送ChannelActive事件会从头结点开始传播Active事件,这其中会调用到DefaultChannelPipeline#channelActive这个方法

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

    readIfIsAutoRead();
}

readIfIsAutoRead这个方法里,最终会调用到AbstractNioChannel#doBeginRead这个方法

protected void doBeginRead() throws Exception {
    // Channel.read() or ChannelHandlerContext.read() was called
    final SelectionKey selectionKey = this.selectionKey;
    if (!selectionKey.isValid()) {
        return;
    }

    readPending = true;

    final int interestOps = selectionKey.interestOps();
    if ((interestOps & readInterestOp) == 0) {
        selectionKey.interestOps(interestOps | readInterestOp);
    }
}

在这里,就会向selector注册读事件