Netty5源码分析(三) -- Channel如何注册OP_ACCEPT, OP_READ, OP_WRITE

本文详细分析了Netty如何注册和处理OP_ACCEPT、OP_READ、OP_WRITE事件。从创建ServerSocketChannel、设置非阻塞模式到注册SelectionKey,再到NioServerSocketChannel的doReadMessage处理连接请求,以及NioSocketChannel的OP_READ事件处理,揭示了Netty对Java NIO服务端封装的精妙之处。

抛开Netty,一个典型的Java NIO服务端开发需要做几件事:

1.  创建ServerSocketChannel,设置为非阻塞,并绑定端口

2. 创建Selector对象

3. 给ServerSocketChannel注册SelectionKey.OP_ACCEPT事件

4. 启动一个线程循环,调用Selector的select方法来检查IO就绪事件,一旦有IO就绪事件,就通知用户线程去处理IO事件

5. 如果有Accept事件,就创建一个SocketChannel,并注册SelectionKey_OP_READ

6.    如果有读事件,判断一下是否全包,如果全包,就交给后端线程处理

7.    写事件比较特殊。isWriteable表示的是本机的写缓冲区是否可写。这个在绝大多少情况下都是为真的。在Netty中只有写半包的时候才需要注册写事件,如果一次写就完全把数据写入了缓冲区就不需要注册写事件. 具体关于写事件的处理看这篇 http://blog.youkuaiyun.com/iter_zc/article/details/39291129


一个典型的Java NIO服务端的代码如下:


public NIOServer(int port) throws IOException {  
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();  
        serverSocketChannel.configureBlocking(false);   
        ServerSocket serverSocket = serverSocketChannel.socket();  
        serverSocket.bind(port);  
        selector = Selector.open();  
        // 注册到selector,等待连接  
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);  
    }  



private void eventloop() throws IOException {  
        while (true) {   
            selector.select();   
            Set<SelectionKey> selectionKeys = selector.selectedKeys();  
            Iterator<SelectionKey> iterator = selectionKeys.iterator();  
            while (iterator.hasNext()) {          
                SelectionKey selectionKey = iterator.next();  
                iterator.remove();  
                handleSelectedKey(selectionKey);  
            }  
        }  
    }  

private void handleKey(SelectionKey selectionKey) throws IOException {  
        ServerSocketChannel server = null;  
        SocketChannel client = null;  
 
        if (selectionKey.isAcceptable()) {  
            server = (ServerSocketChannel) selectionKey.channel();    
            client = server.accept();  
            // 配置为非阻塞  
            client.configureBlocking(false);  
            // 注册到selector,等待连接  
            client.register(selector, SelectionKey.OP_READ);  
        } else if (selectionKey.isReadable()) {  
            client = (SocketChannel) selectionKey.channel();  
            client.read(receivebuffer);   
        } else if (selectionKey.isWritable()) {   
            sendbuffer.clear();    
            client = (SocketChannel) selectionKey.channel();  
            client.write(sendbuffer);  
        }  
    }  

那么Netty是如何来注册Accept和读写事件的呢? 首先来看如何注册的OP_ACCPET事件,是在服务器端绑定端口的过程中的,具体的绑定细节看这篇 http://blog.youkuaiyun.com/iter_zc/article/details/39349177 这里只分析注册OP_ACCEPT的过程。

在ServerBootstrap调用createChannel来创建NioServerSocketChannel的时候,会给NioServerSocketChannel传入要绑定的OP_ACCEPT事件,然后依次调用父类的构造函数,把readInterestOp往上传递,直到传递到AbstractNioChannel,作为AbstractNioChannel的一个实例属性readInterestOp.

public NioServerSocketChannel(EventLoop eventLoop, EventLoopGroup childGroup) {
        super(null, eventLoop, childGroup, newSocket(), SelectionKey.OP_ACCEPT);
        config = new DefaultServerSocketChannelConfig(this, javaChannel().socket());
    }

protected AbstractNioMessageServerChannel(
            Channel parent, EventLoop eventLoop, EventLoopGroup childGroup, SelectableChannel ch, int readInterestOp) {
        super(parent, eventLoop, ch, readInterestOp);
        this.childGroup = childGroup;
    }

protected AbstractNioMessageChannel(
            Channel parent, EventLoop eventLoop, SelectableChannel ch, int readInterestOp) {
        super(parent, eventLoop, ch, readInterestOp);
    }

protected AbstractNioChannel(Channel parent, EventLoop eventLoop, SelectableChannel ch, int readInterestOp) {
        super(parent, eventLoop);
        this.ch = ch;
        this.readInterestOp = readInterestOp;
        try {
            ch.configureBlocking(false);
        } catch (IOException e) {
            try {
                ch.close();
            } catch (IOException e2) {
                if (logger.isWarnEnabled()) {
                    logger.warn(
                            "Failed to close a partially initialized socket.", e2);
                }
            }

            throw new ChannelException("Failed to enter non-blocking mode.", e);
        }
    }

可以看到在AbstractNioChannel的构造函数里面并没有把Channel注册到selector之上,只是把OP_ACCEPT赋值给了readInterestOp。在服务器绑定的过程中有一个register的过程,最后调用到了AbstractNioChannel的doRegister方法。这里可以看到调用的ServerSocketChannel得register方法,但是并没有注册具体的事件,传了一个0,表示不注册任何事件。这里做了一件事情就是把Channel作为Attachment绑定到了SelectionKey之上。

protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                selectionKey = javaChannel().register(eventLoop().selector, 0, this);
                return;
            } catch (CancelledKeyException e) {
                if (!selected) {
                    // Force the Selector to select now as the "canceled" SelectionKey may still be
                    // cached and not removed because no Select.select(..) operation was called yet.
                    eventLoop().selectNow();
                    selected = true;
                } else {
                    // We forced a select operation on the selector before but the SelectionKey is still cached
                    // for whatever reason. JDK bug ?
                    throw e;
                }
            }
        }
    }

// SelectionKey的事件的值

public static final int OP_READ = 1 << 0;  // 1
public static final int OP_WRITE = 1 << 2  // 4
public static final int OP_CONNECT = 1 << 3; // 8
public static final int OP_ACCEPT = 1 << 4;  // 16



Channel注册到Selector有两种方式,一种是调用Channel的register方法,第二种是设置SelectionKey的interestOps的值。Netty是用了第二种方式,通过设置SelectionKey的interestOps来注册Channel关心的事件,把实际的注册延迟了。

在服务器端绑定的过程中,在bind完之后会fireChannelActive事件,在fireChannelActive执行之后,会让Channel主动发起一个outbound的读事件,通过Pipeline最后传递到HeadHandler,HeadHandler会实际使用Unsafe来进行读写。HeadHandler处理读时间时,调用了Unsafe的beginRead方法,最后到了AbstractNioChannel来设置SelectionKey的interestOps属性,来最终把Channel绑定到Selector。

 public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
            if (!ensureOpen(promise)) {
                return;
            }

            // See: https://github.com/netty/netty/issues/576
            if (!PlatformDependent.isWindows() && !PlatformDependent.isRoot() &&
                Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
                localAddress instanceof InetSocketAddress &&
                !((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress()) {
                // Warn a user about the fact that a non-root user can't receive a
                // broadcast packet on *nix if the socket is bound on non-wildcard address.
                logger.warn(
                        "A non-root user can't receive a broadcast packet if the socket " +
                        "is not bound to 
### NioEventLoop 的工作机制及组成 #### 1. **NioEventLoop 的基本结构** NioEventLoopNetty 中的核心组件之一,它主要负责处理 I/O 和非 I/O 类型的任务。其内部包含以下几个重要组成部分: - **Selector**: 负责监听注册在其上的 Channel 的 I/O 事件(如读、写)。通过 `select` 方法获取准备好的事件集合并进行后续处理[^4]。 - **TaskQueue**: 存储待执行的普通任务(Runnable),这些任务通常是非 I/O 性质的操作。它们会被依次取出并通过 `runAllTasks` 方法逐一执行[^2]。 - **线程**: 每个 NioEventLoop 都绑定了一个独立的线程,用于运行 `run()` 方法,从而实现对 I/O 和非 I/O 任务的持续处理[^5]。 --- #### 2. **NioEventLoop 的工作流程** ##### (1) 启动阶段 当 NioEventLoop 开始工作时,会调用其核心方法 `run()`。此过程中,线程进入无限循环模式,直到被显式关闭为止。以下是具体步骤概述: - 判断是否有本地任务需要立即处理。如果存在,则优先调用 `selector.selectNow()` 来快速返回当前可用的 SelectionKey 集合[^1]。 - 如果无紧急任务,默认情况下调用 `selector.select(timeout)` 进入阻塞状态等待新的 I/O 事件发生。 ##### (2) 处理 I/O 事件 一旦 Selector 返回了已准备好处理的 KeySet,NioEventLoop 就会对每一个键对应的通道执行相应的操作。这一步骤主要包括以下几方面: - **Accept Events**: 当服务器端接收到新连接请求时,触发 accept 逻辑并将新建的 Channel 注册至 EventLoop 上继续服务。 - **Read/Write Operations**: 对于可读或可写的 Channels,分别调用对应的方法完成数据交互过程。例如对于 Read 操作可能涉及缓冲区填充以及回调通知等动作[^3]。 ##### (3) 执行非 I/O 任务 除了上述提到的 I/O 相关活动外,NioEventLoop 还承担着大量其他类型的工作负载——主要是指那些存放在 TaskQueues 中的对象实例化或者业务层面上定义的各种自定义行为等等。这类作业一般不会直接影响到底层网络通信层面但却同样至关重要不可忽视。 --- #### 3. **I/O 事件的具体处理流程** 针对不同类型 Ready State 下所关联的动作描述如下表所示: | 状态 | 描述 | |--------------|----------------------------------------------------------------------------------------| | OP_ACCEPT | 表明有一个新的客户端尝试建立连接, 此时机应答接受该链接形成一个新的Channel对象 [^2]. | | OP_READ | 表达某个已经存在的channel现在可以安全地从中提取数据而不必担心阻塞现象的发生 . | | OP_WRITE | 告知应用程序此时此刻能够向指定的目标发送信息而无需顾虑资源不足等问题 [^3]. | 每当检测到某项条件满足之后,NioEventLoop便会依据预先设定好的Handler链路去寻找匹配合适的处理器来进行进一步处置. --- #### 4. **代码示例** 下面展示了一个简化版模拟如何利用NioEventLoop来同步管理多路复用器的选择与任务队列清空的过程: ```java public void run() { for (;;) { // Infinite loop until shutdown is triggered. try { int selectedKeys = selector.select(); if (selectedKeys != 0 || hasTasks()) { processSelectedKeys(); if (!isShuttingDown.get()) { executeScheduledAndPendingTasks(); } } else { continue; } } catch (Throwable t) { handleUnexpectedException(t); } } } ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值