抓到Netty一个Bug,聊一下Netty是如何高效接收网络连接的(五)

r660926
发布于 2022-8-9 18:56
浏览
0收藏

5. doReadMessages接收客户端连接

public class NioServerSocketChannel extends AbstractNioMessageChannel
                             implements io.netty.channel.socket.ServerSocketChannel {

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

}

 • 通过javaChannel()获取封装在Netty服务端NioServerSocketChannel中的JDK 原生 ServerSocketChannel

    @Override
    protected ServerSocketChannel javaChannel() {
        return (ServerSocketChannel) super.javaChannel();
    }

 • 通过JDK NIO 原生的ServerSocketChannelaccept方法获取JDK NIO 原生客户端连接SocketChannel
 

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

这一步就是我们在?《聊聊Netty那些事儿之从内核角度看IO模型》介绍到的调用监听Socketaccept方法,内核会基于监听Socket创建出来一个新的Socket专门用于与客户端之间的网络通信这个我们称之为客户端连接Socket。这里的ServerSocketChannel就类似于监听SocketSocketChannel就类似于客户端连接Socket

 

由于我们在创建NioServerSocketChannel的时候,会将JDK NIO 原生的ServerSocketChannel设置为非阻塞,所以这里当ServerSocketChannel上有客户端连接时就会直接创建SocketChannel,如果此时并没有客户端连接时accept调用就会立刻返回null并不会阻塞。

    protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch;
        this.readInterestOp = readInterestOp;
        try {
            //设置Channel为非阻塞 配合IO多路复用模型
            ch.configureBlocking(false);
        } catch (IOException e) {
          ..........省略.............
        }
    }

5.1 创建客户端NioSocketChannel

public class NioServerSocketChannel extends AbstractNioMessageChannel
                             implements io.netty.channel.socket.ServerSocketChannel {

    @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) {
          .........省略.......
        }

        return 0;
    }

}

这里会根据ServerSocketChannelaccept方法获取到JDK NIO 原生的SocketChannel(用于底层真正与客户端通信的Channel),来创建Netty中的NioSocketChannel

public class NioSocketChannel extends AbstractNioByteChannel implements io.netty.channel.socket.SocketChannel {

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

}

创建客户端NioSocketChannel的过程其实和之前讲的创建服务端NioServerSocketChannel大体流程是一样的,我们这里只对客户端NioSocketChannel和服务端NioServerSocketChannel在创建过程中的不同之处做一个对比。

 

具体细节部分大家可以在回看下?《详细图解Netty Reactor启动全流程》一文中关于NioServerSocketChannel的创建的详细细节。

 

5.3 对比NioSocketChannel与NioServerSocketChannel的不同


1:Channel的层次不同
在我们介绍Reactor的创建文章中,我们提到Netty中的Channel是具有层次的。由于客户端NioSocketChannel是在main reactor接收连接时在服务端NioServerSocketChannel中被创建的,所以在创建客户端NioSocketChannel的时候会通过构造函数指定了parent属性为NioServerSocketChanel。并将JDK NIO 原生SocketChannel封装进Netty的客户端NioSocketChannel中。

 

而在Reactor启动过程中创建NioServerSocketChannel的时候parent属性指定是null。因为它就是顶层的Channel,负责创建客户端NioSocketChannel

    public NioServerSocketChannel(ServerSocketChannel channel) {
        super(null, channel, SelectionKey.OP_ACCEPT);
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }

2:向Reactor注册的IO事件不同


客户端NioSocketChannel向Sub Reactor注册的是SelectionKey.OP_READ事件,而服务端NioServerSocketChannel向Main Reactor注册的是SelectionKey.OP_ACCEPT事件。

public abstract class AbstractNioByteChannel extends AbstractNioChannel {

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

}

public class NioServerSocketChannel extends AbstractNioMessageChannel
                             implements io.netty.channel.socket.ServerSocketChannel {

   public NioServerSocketChannel(ServerSocketChannel channel) {
        //父类AbstractNioChannel中保存JDK NIO原生ServerSocketChannel以及要监听的事件OP_ACCEPT
        super(null, channel, SelectionKey.OP_ACCEPT);
        //DefaultChannelConfig中设置用于Channel接收数据用的buffer->AdaptiveRecvByteBufAllocator
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }
}

3: 功能属性不同造成继承结构的不同

抓到Netty一个Bug,聊一下Netty是如何高效接收网络连接的(五)-鸿蒙开发者社区 NioSocketChannel.png

抓到Netty一个Bug,聊一下Netty是如何高效接收网络连接的(五)-鸿蒙开发者社区 NioServerSocketChannel.png


客户端NioSocketChannel继承的是AbstractNioByteChannel,而服务端NioServerSocketChannel继承的是AbstractNioMessageChannel。它们继承的这两个抽象类一个前缀是Byte,一个前缀是Message有什么区别吗??

 

客户端NioSocketChannel主要处理的是服务端与客户端的通信,这里涉及到接收客户端发送来的数据,而Sub Reactor线程从NioSocketChannel中读取的正是网络通信数据单位为Byte。


服务端NioServerSocketChannel主要负责处理OP_ACCEPT事件,创建用于通信的客户端NioSocketChannel。这时候客户端与服务端还没开始通信,所以Main Reactor线程NioServerSocketChannel的读取对象为Message。这里的Message指的就是底层的SocketChannel客户端连接。
 
以上就是NioSocketChannelNioServerSocketChannel创建过程中的不同之处,后面的过程就一样了。

 

 • 在AbstractNioChannel 类中封装JDK NIO 原生的SocketChannel,并将其底层的IO模型设置为非阻塞,保存需要监听的IO事件OP_READ

    protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch;
        this.readInterestOp = readInterestOp;
        try {
            //设置Channel为非阻塞 配合IO多路复用模型
            ch.configureBlocking(false);
        } catch (IOException e) {

        }
    }

 • 为客户端NioSocketChannel创建全局唯一的channelId,创建客户端NioSocketChannel的底层操作类NioByteUnsafe,创建pipeline。

  protected AbstractChannel(Channel parent) {
        this.parent = parent;
        //channel全局唯一ID machineId+processId+sequence+timestamp+random
        id = newId();
        //unsafe用于底层socket的读写操作
        unsafe = newUnsafe();
        //为channel分配独立的pipeline用于IO事件编排
        pipeline = newChannelPipeline();
    }

 • 在NioSocketChannelConfig的创建过程中,将NioSocketChannel的RecvByteBufAllocator类型设置为AdaptiveRecvByteBufAllocator

    public DefaultChannelConfig(Channel channel) {
            this(channel, new AdaptiveRecvByteBufAllocator());
    }

 • 在Bug修复后的版本中服务端NioServerSocketChannel的RecvByteBufAllocator类型设置为ServerChannelRecvByteBufAllocator

 

最终我们得到的客户端NioSocketChannel结构如下:

抓到Netty一个Bug,聊一下Netty是如何高效接收网络连接的(五)-鸿蒙开发者社区

 NioSocketChannel.png

标签
已于2022-8-9 18:56:29修改
收藏
回复
举报
回复
    相关推荐