Netty-新连接接入源码解读

本片博文来看Netty的服务端是如何处理新连接接入问题的

什么是新连接接入?以及新连接接入前,Netty处于什么状态

netty的服务端NioServerSocketChannel初始化,注册在BossGroup中的一条NioEventLoop中,并且给NioServerSocketChannel中维护的jdk原生的ServerSocketChannel绑定好了端口后, EventLoop启动,开始轮询工作...

这时候 EventLoop 它在轮询什么? 其实它在轮询监听当初NioServerSocketChannel经过二次注册感兴趣的事件时, 告诉 Selector,让Selector关注自己身上可能会出现 OP_ACCEPT 事件, 这合情合理,因为对于Netty的主从Reactor线程模型中, BossGroup中的channel只关心OP_ACCEPT 也就是用户的请求建立连接事件

Netty-新连接接入源码解读

netty的新连接接入要做哪些工作?

看上图,netty的新连接接入,对应这个线程模型中我圈出来的部分, 主要步骤如下

服务端Selector轮询到客户端请求建立连接

处理请求

从服务端维护的JDK 原生ServerSocketChannel中accept()客户端的channel

使用new的方法 将客户端的Channel封装成 NioSocketChannel

层层往上调用super(),初始化channel的组件

创建channel的配置类对象 config

向下传播channelRead事件

给客户端的channel设置相关参数

将客户端的channel注册在 workerGroup 中的轮询算法选出的 EventLoop

将jdk原生的SocketChanel注册进 EventLoop中的选择器中

传播channelregist事件

传播channelActive事件

给客户端的channel二次注册netty可以处理的感兴趣的事件

这是我总结的新连接接入的流程,从上面分析的开始检查新链接,终止的标志是,把客户端的NioSocketChannel二次注册在EventLoop上,称为Netty可以处理的chanel为止

入口:NioEventLoop处理IO事件

当服务端的事件循环检测到有io事件时,使用它的processSelectedKeys();处理,源码如下:

private void processSelectedKeys() { // todo selectedKeys 就是经过优化后的keys(底层是数组) , 默认不为null if (selectedKeys != null) { processSelectedKeysOptimized(); } else { processSelectedKeysPlain(selector.selectedKeys()); } }

当有了新IO请求进来, jdk原生的Selector将SelectionKey放入存放感兴趣的key的集合中,而这个集合现在就是netty通过反射的方式强制替换为以数组为数据结构的selectedKeys, 数组不为空, 跟进processSelectedKeysOptimized();,源码如下: 解析写在源码下面:

private void processSelectedKeysOptimized() { 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 // todo 数组输出空项, 从而允许在channel 关闭时对其进行垃圾回收 // See https://github.com/netty/netty/issues/2363 // todo 数组中当前循环对应的keys质空, 这种感兴趣的事件只处理一次就行 selectedKeys.keys[i] = null; // todo 获取出 attachment,默认情况下就是注册进Selector时,传入的第三个参数 this===> NioServerSocketChannel // todo 一个Selector中可能被绑定上了成千上万个Channel, 通过K+attachment 的手段, 精确的取出发生指定事件的channel, 进而获取channel中的unsafe类进行下一步处理 final Object a = k.attachment(); // todo if (a instanceof AbstractNioChannel) { // todo 进入这个方法, 传进入 感兴趣的key + NioSocketChannel 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; } } }

处理感兴趣的事件, 想想,需要什么他才能进一步处理呢? 需要下面两点:

这个感兴趣的事件是啥?

在这了就是上面的 k

哪个channel出现的Selector感兴趣的事件?

在这里是通过 attachment拿到的 a ,其实不就是服务端的NioServerSocketChannel

另外它把NioServerSocketChannel向上强转成了AbstractNioChannel这是为什么呢?

答:
第一点:
在我写的上一篇Chanel的架构体系中,我们知道,Netty的NioXXXChannel其实是netty的,基于原生的jdk的chanel的封装,而在他的整个继承体系中,这个AbstractNioChannel就负责维护jdk原生的channel, 知道了这有啥用? 当然有用,我们要去给客户端channel接生了,原生服务端channel.accept()==客户端channel

第二点:
针对数据的读写都是unsafe中,回想是哪个类中定义了读取channel中IO数据的抽象模板函数呢? AbstractNioChannel, 是它新增的内部接口,从而进客户端和服务对针对chanel的不同特化read进行不同的实现

好, 有了这两个条件,继续跟进processSelectedKey(k, (AbstractNioChannel) a);看它是如何处理, 源码如下:

获取到服务端的unsafe对象(数据读写)

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpzdgw.html