Netty 服务 如何 接收新的连接

源码调试技巧:从基本概念出手,寻找突破点;观察线程栈,寻找关键字眼

在这里插入图片描述

简单回顾。netty 是如何启动的

上一节回顾 netty 是如何启动的

服务启动的时候会创建ServerSocketChannel ,并将之与 ChannelPipeline 、 EventLoop 、 Selector 、 Java 原生 Channel 进行绑定,同时通过 Java 原生 Channel 绑定到一个本地地址(port端口)。

那么,如果有新连接进来,服务是如何接收(或接受, Accept )的呢?
服务启动的时候会往 NioServerSocketChannel 对应的 ChannelPipeline 后面添加一个叫做 ServerBootstrapAcceptor 的 ChannelHandler ,那么,问题是:

  1. ServerBootstrapAcceptor 的作用是什么?
  2. 接收连接的过程是否也要跟 Java 原生 Channel 打交道?
  3. Selector 又是在哪里使用到的?

源码解读,大胆假设法。顾名思义,连接连接。肯定是客户端需要读取数据,inbound 之类的啦,所以
我们知道, Netty 中将 ChannelHandler 分成 inbound 和 outbound 两种类型,既然是接收新连接,那肯定是inbound ,所以,我们先从 ChannelInboundHandler 这个接口出发,分析它有哪些方法。

简单瞄一眼,大致发现有三种可能跟接收新连接有关系的
channelRegistered ()/channelUnregistered () ,服务启动的时候有看到 Register 相关的调用
channelActive ()/channelInactive () ,服务启动绑定完地址的时候有看到 Active 相关的调用
channelRead ()/channelReadComplete () ,暂未看到在哪里使用的。

大胆猜测应该是 channelRead ()/channelReadComplete ()

io.netty.channel.nio.NioEventLoop#processSelectedKeysOptimized

private void processSelectedKeysOptimized() {
        // 遍历SelectionKey
        for (int i = 0; i < selectedKeys.size; ++i) {
            final SelectionKey k = selectedKeys.keys[i];
            // null out entry in the array to allow to have it GC'ed once the Channel close
            // See https://github.com/netty/netty/issues/2363
            // 优化GC
            selectedKeys.keys[i] = null;

            // 取出SelectionKey中的附件,还记得附件是什么吗?
            // 上一节,服务启动的时候把Selector、Java原生Channel、Netty的Channel绑定在一起了
            // 其中,Netty的Channel是通过attachment绑定到SelectionKey中的
            // 所以,针对新连接建立的过程,这里取出来的就是Netty中的NioServerSocketChannel
            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) {
                // null out entries in the array to allow to have it GC'ed once the Channel close
                // See https://github.com/netty/netty/issues/2363
                selectedKeys.reset(i + 1);

                selectAgain();
                i = -1;
            }
        }
    }
 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) {
                // If the channel implementation throws an exception because there is no event loop, we ignore this
                // because we are only trying to determine if ch is registered to this event loop and thus has authority
                // to close ch.
                return;
            }
            // Only close ch if ch is still registered to this EventLoop. ch could have deregistered from the event loop
            // and thus the SelectionKey could be cancelled as part of the deregistration process, but the channel is
            // still healthy and should not be closed.
            // See https://github.com/netty/netty/issues/5125
            if (eventLoop == this) {
                // close the channel if the key is not valid anymore
                unsafe.close(unsafe.voidPromise());
            }
            return;
        }

        try {
            int readyOps = k.readyOps();
            // We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise
            // the NIO JDK channel implementation may throw a NotYetConnectedException.

            // 如果是Connect事件
            if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
                // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
                // See https://github.com/netty/netty/issues/924
                int ops = k.interestOps();
                ops &= ~SelectionKey.OP_CONNECT;
                k.interestOps(ops);

                unsafe.finishConnect();
            }

            // 如果是Write事件
            // Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
            if ((readyOps & SelectionKey.OP_WRITE) != 0) {
                // Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
                ch.unsafe().forceFlush();
            }

            // 如果是Read事件或者Accept事件
            // Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
            // to a spin loop
            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                unsafe.read();
            }

        } catch (CancelledKeyException ignored) {
            unsafe.close(unsafe.voidPromise());
        }
    }

可以看到,这里的写法也是跟我们的 NIO 编程保持一致的,针对不同的事件使用不同的逻辑进行处理,不同的是, Netty 中具体的处理逻辑交给了 Channel 的 unsafe 来处理,对于接收新连接的过程,这里的 unsafe 无疑就是NioServerSocketChannel 中的 unsafe 了。

 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 {
                        // key1 ,读取消息
                        int localRead = doReadMessages(readBuf);
                        if (localRead == 0) {
                            break;
                        }
                        if (localRead < 0) {
                            closed = true;
                            break;
                        }

                        allocHandle.incMessagesRead(localRead);
                    } while (continueReading(allocHandle));
                } catch (Throwable t) {
                    exception = t;
                }

                int size = readBuf.size();
                for (int i = 0; i < size; i ++) {
                    // key2 ,触发 channelRead()
                    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();
                }
            }
        }
    }

1 doReadMessages (readBuf) ,读取消息,如何读取?读取的内容是什么?

2 pipeline.fireChannelRead (readBuf.get (i)) ,触发 ChannelHandler 的channelRead () 方法,最终由谁处理?

doReadMessages (readBuf)

 @Override
    protected int doReadMessages(List<Object> buf) throws Exception {

        // key1 ,看起来跟 Java 原生 Channel 有关系
        SocketChannel ch = SocketUtils.accept(javaChannel());

        try {
            if (ch != null) {
                // key2 ,构造了一个 Netty 的 NioSocketChannel ,并把 Java 原生 SocketChannel 传入
                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;
    }
// io.netty.util.internal.SocketUtils#accept
    public static SocketChannel accept(final ServerSocketChannel serverSocketChannel) throws IOException {
        try {
            return AccessController.doPrivileged(new PrivilegedExceptionAction<SocketChannel>() {
                @Override
                public SocketChannel run() throws IOException {
                    //  调用 Java 原生的 aacept() 方法创建一个 SocketChannel
                    return serverSocketChannel.accept();
                }
            });
        } catch (PrivilegedActionException e) {
            throw (IOException) e.getCause();
        }
    }

可以,
Netty 最终还是调用的 Java 原生的 SeverSocketChannel 的 accept () 方法来创建一个 SocketChannel ,并把这个 SocketChannel 绑定到 Netty 自己的 NioSocketChannel 中,还记得上一节创建 NioServerSocketChannel 的过程吗

NioSocketChannel 的创建过程也是一样的,我就不贴源码了,简单再回顾一下:
1 将 Java 原生 Channel 配置成非阻塞 ch.configureBlocking(false) ;
2 设置感兴趣的事件为 Read 事件, NioServerSocketChannel 感兴趣的事件为 Accept 事件;
3 分配 id ;创建 unsafe ;创建 ChannelPipeline ;

好了,到这里 SocketChannel 就创建好了,但是它的 pipeline 中还是只有 head 和 tail 两个 Handler ,还无法处理消息,那么, ChannelPipeline 的初始化在哪里呢?
这 就 轮 到 pipeline.fireChannelRead(readBuf.get(i)) 这行代码来发挥作用了,这里的 pipeline 实际上是NioServerSocketChannel 对应的 ChannelPipeline ,通过上一节的分析我们知道,服务启动完成后这个ChannelPipeline 中的双向链表为 head<=>LoggingHandler<=>ServerBootstrapAcceptor<=>tail ,很显然,只有
ServerBootstrapAcceptor 这个 ChannelHandler 会往子 ChannelPipeline 中添加子 ChannelHandler ,所以,我们直接看 ServerBootstrapAcceptor 的 channelRead () 方法即可。

// io.netty.bootstrap.ServerBootstrap.ServerBootstrapAcceptor#channelRead

private static class ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter {

        private final EventLoopGroup childGroup;
        private final ChannelHandler childHandler;
        private final Entry<ChannelOption<?>, Object>[] childOptions;
        private final Entry<AttributeKey<?>, Object>[] childAttrs;
        private final Runnable enableAutoReadTask;

        ServerBootstrapAcceptor(
                final Channel channel, EventLoopGroup childGroup, ChannelHandler childHandler,
                Entry<ChannelOption<?>, Object>[] childOptions, Entry<AttributeKey<?>, Object>[] childAttrs) {
            this.childGroup = childGroup;
            this.childHandler = childHandler;
            this.childOptions = childOptions;
            this.childAttrs = childAttrs;

            // Task which is scheduled to re-enable auto-read.
            // It's important to create this Runnable before we try to submit it as otherwise the URLClassLoader may
            // not be able to load the class because of the file limit it already reached.
            //
            // See https://github.com/netty/netty/issues/1328
            enableAutoReadTask = new Runnable() {
                @Override
                public void run() {
                    channel.config().setAutoRead(true);
                }
            };
        }

        @Override
        @SuppressWarnings("unchecked")
        public void channelRead(ChannelHandlerContext ctx, Object msg) {

            //  这里的 msg 就是上面的 readBuf.get(i) ,也就是 NioSocketChannel ,也就是子 Channel
            final Channel child = (Channel) msg;

            //  添加子 ChannelHandler ,这里同样也是以 ChannelInitializer 的形式添加的
            child.pipeline().addLast(childHandler);

            //  设置子 Channel 的配置等信息
            setChannelOptions(child, childOptions, logger);
            setAttributes(child, childAttrs);

            try {
                //  将子 Channel 注册到 workerGroup 中的一个 EventLoop 上
                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);
            }
        }

我们来总结一下服务接收新连接的过程

  1. Netty 中轮询的方法是写在 NioEventLooop 中的;
  2. Netty 底层也是通过 Java 原生 ServerSocketChannel 来接收新连接的;(有点代理的味道了啊。增强一个原生的功能)
  3. Netty 将接收到的 SocketChannel 包装成了 NioSocketChannel ,并给它分配 ChannelPipeline 等元素;
  4. Netty 中通过 ServerBootstrapAcceptor 这个 ChannelHandler 来初始化 NioSocketChannel 的配置并将其注册到 EventLoop 中;
  5. 注册到 EventLoop 中同样也会与这个 EventLoop 中的 Selector 绑定, Netty 的 NioSocketChannel 同样地也是以附件的形式绑定在 SelectionKey 中

对 Java 原生NIO 的增强

至此,一个 SocketChannel 才算真正建立完成,也就可以接收消息了。
通过本节的剖析,我们知道了 Netty 中接收新连接的过程跟 Java 原生 NIO 是同出一辙,或者说是对 Java 原生NIO 的增强,那么,既然连接已经就绪,如何接收消息呢

Netty服务如何接收新的连接
服务接收新连接过程:
1在NioEventLoop中轮询select ,
2调用java原生ServerSocketChannel的accept()方法接收新连接
3.包装java原生SocketChannel,对其进行增强

服务启动的时候会创建 ServerSocketChannel,并将之与
ChannelPipeline、EventLoop、Selector、Java 原生 Channel 进行绑定,
同时通过 Java 原生 Channel 绑定到一个本地地址。

至此,一个 SocketChannel 才算真正建立完成,也就可以接收消息了。
为了照顾大部分同学,我们再回顾下 NIO 编程的大致过程:
1 先是启动 ServerSocketChannel,并注册 Accept 事件;
2 轮询调用 Selector 的 select () 方法,select () 方法还有两个兄弟 ——selectNow (),select (timeout);
3 调用 Selector 的 selectedKeys () 方法,拿到轮询到的 SelectionKey;
4 遍历这些 selectedKeys,处理它们感兴趣到的事件;
5 如果是 Accept 事件,则从 SelectionKey 中取出 ServerSocketChannel,并 accept () 出来一个 SocketChannel;
6 把这个 SocketChannel 也注册到 Selector 上,并注册 Read 事件;

所以,这里我们大胆猜测,Netty 底层接收新连接跟 Java 原生 Channel 是一致的,而且它的 select 是在 NioEventLoop 的 run () 方法中,并在获取到 SelectedKeys 之后,调用了 processSelectedKeys () 方法处理这些 SelectionKey。
至于,我们的猜测对不对呢?打开 NioEventLoop 的 run () 方法看一看:

// io.netty.channel.nio.NioEventLoop#run
@Override
protected void run() {
    int selectCnt = 0;
    // 死循环,还记得NIO中的死循环吗?
    for (;;) {
        try {
            int strategy;
            try {
                strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
                switch (strategy) {
                    case SelectStrategy.CONTINUE:
                        continue;
                    case SelectStrategy.BUSY_WAIT:
                    case SelectStrategy.SELECT:
                        long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
                        if (curDeadlineNanos == -1L) {
                            curDeadlineNanos = NONE; // nothing on the calendar
                        }
                        nextWakeupNanos.set(curDeadlineNanos);
                        try {
                            if (!hasTasks()) {
                                // key1,select()相关的方法
                                strategy = select(curDeadlineNanos);
                            }
                        } finally {
                            nextWakeupNanos.lazySet(AWAKE);
                        }
                    default:
                }
            } catch (IOException e) {
                rebuildSelector0();
                selectCnt = 0;
                handleLoopException(e);
                continue;
            }

            selectCnt++;
            cancelledKeys = 0;
            needsToSelectAgain = false;
            final int ioRatio = this.ioRatio;
            boolean ranTasks;
            if (ioRatio == 100) {
                try {
                    if (strategy > 0) {
                        // key2,处理SelectionKey
                        processSelectedKeys();
                    }
                } finally {
                    ranTasks = runAllTasks();
                }
            } else if (strategy > 0) {
                final long ioStartTime = System.nanoTime();
                try {
                    // key2,处理SelectionKey
                    processSelectedKeys();
                } finally {
                    final long ioTime = System.nanoTime() - ioStartTime;
                    ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                }
            } else {
                ranTasks = runAllTasks(0);
            }
		// 省略其他代码
    }
}

processSelectedKeysOptimized 果然

    private void processSelectedKeysOptimized() {
        // 遍历SelectionKey
        for (int i = 0; i < selectedKeys.size; ++i) {
            final SelectionKey k = selectedKeys.keys[i];
            // null out entry in the array to allow to have it GC'ed once the Channel close
            // See https://github.com/netty/netty/issues/2363
            // 优化GC
            selectedKeys.keys[i] = null;

            // 取出SelectionKey中的附件,还记得附件是什么吗?
            // 上一节,服务启动的时候把Selector、Java原生Channel、Netty的Channel绑定在一起了
            // 其中,Netty的Channel是通过attachment绑定到SelectionKey中的
            // 所以,针对新连接建立的过程,这里取出来的就是Netty中的NioServerSocketChannel
            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) {
                // null out entries in the array to allow to have it GC'ed once the Channel close
                // See https://github.com/netty/netty/issues/2363
                selectedKeys.reset(i + 1);

                selectAgain();
                i = -1;
            }
        }
    }

processSelectedKey

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
    final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
    
    // 省略异常处理等其他代码

    try {
        int readyOps = k.readyOps();
        
        // 如果是Connect事件
        if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
            int ops = k.interestOps();
            ops &= ~SelectionKey.OP_CONNECT;
            k.interestOps(ops);
            unsafe.finishConnect();
        }
        // 如果是Write事件
        if ((readyOps & SelectionKey.OP_WRITE) != 0) {
            ch.unsafe().forceFlush();
        }
        // 如果是Read事件或者Accept事件
        if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
            unsafe.read();
        }
    } catch (CancelledKeyException ignored) {
        unsafe.close(unsafe.voidPromise());
    }
}

今天的源码剖析基本就讲完了,让我们来总结一下服务接收新连接的过程

1 Netty 中轮询的方法是写在 NioEventLooop 中的;
2 Netty 底层也是通过 Java 原生 ServerSocketChannel 来接收新连接的;
3 Netty 将接收到的 SocketChannel 包装成了 NioSocketChannel,并给它分配 ChannelPipeline 等元素;
4 Netty 中通过 ServerBootstrapAcceptor 这个 ChannelHandler 来初始化 NioSocketChannel 的配置并将其注册到 EventLoop 中;
5 注册到 EventLoop 中同样也会与这个 EventLoop 中的 Selector 绑定,Netty 的 NioSocketChannel 同样地也是以附件的形式绑定在 SelectionKey 中;
至此,一个 SocketChannel 才算真正建立完成,也就可以接收消息了。
通过本节的剖析,我们知道了 Netty 中接收新连接的过程跟 Java 原生 NIO 是同出一辙,或者说是对 Java 原生 NIO 的增强,那么,既然连接已经就绪,如何接收消息呢?

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值