
抓到Netty一个Bug,聊一下Netty是如何高效接收网络连接的(五)
5. doReadMessages接收客户端连接
• 通过javaChannel()获取封装在Netty服务端NioServerSocketChannel中的JDK 原生 ServerSocketChannel。
• 通过JDK NIO 原生的ServerSocketChannel的accept方法获取JDK NIO 原生客户端连接SocketChannel。
这一步就是我们在?《聊聊Netty那些事儿之从内核角度看IO模型》介绍到的调用监听Socket的accept方法,内核会基于监听Socket创建出来一个新的Socket专门用于与客户端之间的网络通信这个我们称之为客户端连接Socket。这里的ServerSocketChannel就类似于监听Socket。SocketChannel就类似于客户端连接Socket。
由于我们在创建NioServerSocketChannel的时候,会将JDK NIO 原生的ServerSocketChannel设置为非阻塞,所以这里当ServerSocketChannel上有客户端连接时就会直接创建SocketChannel,如果此时并没有客户端连接时accept调用就会立刻返回null并不会阻塞。
5.1 创建客户端NioSocketChannel
这里会根据ServerSocketChannel的accept方法获取到JDK NIO 原生的SocketChannel(用于底层真正与客户端通信的Channel),来创建Netty中的NioSocketChannel。
创建客户端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。
2:向Reactor注册的IO事件不同
客户端NioSocketChannel向Sub Reactor注册的是SelectionKey.OP_READ事件,而服务端NioServerSocketChannel向Main Reactor注册的是SelectionKey.OP_ACCEPT事件。
3: 功能属性不同造成继承结构的不同
NioSocketChannel.png
NioServerSocketChannel.png
客户端NioSocketChannel继承的是AbstractNioByteChannel,而服务端NioServerSocketChannel继承的是AbstractNioMessageChannel。它们继承的这两个抽象类一个前缀是Byte,一个前缀是Message有什么区别吗??
客户端NioSocketChannel主要处理的是服务端与客户端的通信,这里涉及到接收客户端发送来的数据,而Sub Reactor线程从NioSocketChannel中读取的正是网络通信数据单位为Byte。
服务端NioServerSocketChannel主要负责处理OP_ACCEPT事件,创建用于通信的客户端NioSocketChannel。这时候客户端与服务端还没开始通信,所以Main Reactor线程从NioServerSocketChannel的读取对象为Message。这里的Message指的就是底层的SocketChannel客户端连接。
以上就是NioSocketChannel与NioServerSocketChannel创建过程中的不同之处,后面的过程就一样了。
• 在AbstractNioChannel 类中封装JDK NIO 原生的SocketChannel,并将其底层的IO模型设置为非阻塞,保存需要监听的IO事件OP_READ。
• 为客户端NioSocketChannel创建全局唯一的channelId,创建客户端NioSocketChannel的底层操作类NioByteUnsafe,创建pipeline。
• 在NioSocketChannelConfig的创建过程中,将NioSocketChannel的RecvByteBufAllocator类型设置为AdaptiveRecvByteBufAllocator。
• 在Bug修复后的版本中服务端NioServerSocketChannel的RecvByteBufAllocator类型设置为ServerChannelRecvByteBufAllocator
最终我们得到的客户端NioSocketChannel结构如下:
NioSocketChannel.png
