「我正在参与掘金会员专属活动-源码共读第一期,点击参与」
前面的文章分析过,NioServerSocketChannel创建、初始化后会被注册到bossGroup事件循环中去。
客户端与服务器的连接创建后会回调serverBootstrapAcceptor的channelRead方法,参数包含NioSocketChannel类型的对象,该NioSocketChannel类型的对象即代表着一条与客户端的连接。
那么NioSocketChannel类型的对象是何时被创建的?
本文分析bossGroup(NioEventLoop)的run方法,了解事件循环的工作细节,连接如何被创建。
一、bossGroup的线程在做什么?
Netty server启动起来后,观察正在运行的线程,可以看到bossGroup的线程始终处于RUNNING的状态,通过Suspend操作可以定位到当前该线程执行到的代码行。
可以看到,线程在执行select(curDeadlineNanos)方法,其底层就是调用JDK Nio选择器的select()方法。
回过头来看run方法的开头处
strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
这里会计算一个strategy值:
-
如果hasTasks()为true,即有任务待执行,则返回selectNow()的值,selectNow()可以理解为非阻塞版本的select(),返回值是一个非负数。
-
如果hasTasks()为false,则返回SelectStrategy.SELECT(值为-1)。
根据上面计算的strategy值,如果是非负数,代表有任务可做,在下面就不会进入select(curDeadlineNanos)的执行。直接执行后面的processSelectedKeys和runAllTasks。
否则进入select(curDeadlineNanos),阻塞直到超时或select得到结果。
这段代码中还有个ioRatio的变量值得注意,该变量的默认值是50,看代码的意思,该变量是控制任务处理时长与IO时长的比例。processSelectedKeys()的执行时长算作IO时长,可以根据IO时长与ioRatio计算出留给runAllTasks的时长。在实际使用中可以关注下这个值的影响,根据业务场景的不同做调优。
总结一下run方法的for(;;)循环中做了以下事情:
-
计算strategy值(主要是看是否有待执行的任务),决定是否进入阻塞的select()方法。
-
进入或不进入阻塞的select()方法。
-
根据需要执行processSelectedKeys(),可以理解为处理IO事件。
-
执行runAllTasks(),可以理解为处理非IO事件。
二、连接是如何创建的
当有客户端与服务端进行连接建立时,阻塞中的select()方法会因检测到accept就绪事件而跳出阻塞,在select()后面打断点进行调试可以观察到这点。
接下来是处理IO事件,随着调用栈的深入,先后进入processSelectedKeys(),processSelectedKeysOptimized(),
processSelectedKey(k, (AbstractNioChannel) a)这几个方法。
在processSelectedKey方法中,会通过调用SelectionKey的readyOps()方法确认是哪种事件就绪,打断点可以看到在这里该值是16,代表OP_ACCEPT事件。
继续进入unsafe.read()。
继续进入方法doReadMessages(readBuf)。看到这部分代码,事情变得明朗了。
在这里SocketChannel ch = SocketUtils.accept(javaChannel())是调用JDK ServerSocketChannel的accept方法,返回一个SocketChannel类型的channel。
SocketChannel类型在JDK NIO中代表着与客户端的连接。
下面可以看到Netty NioSocketChannel是将JDK SocketChannel包装了一层。
将JDK SocketChannel作为入参,创建了一个Netty NioSocketChannel对象。将该NioSocketChannel放入readBuf中。
回到AbstractNioMessageChannel$NioMessageUnsafe的read()方法,下一步是触发pipeline的ChannelRead事件,依次调用pipeline上handler的channelRead方法。前面提到的ServerBootstrapAcceptor就是这个pipeline的handler之一。
打断点可以看到回调channelRead方法时带着readBuf作为参数,在连接建立时该参数的类型是NioSocketChannel。
三、本篇总结
Netty事件循环(NioEventLoop)的run方法会调用JDK NIO selector的select方法,直到有IO事件就绪。
在处理accept事件时,Netty会调用JDK ServerScoketChannel的accept方法获取SocketChannel类型的对象。并将该对象包装成Netty的NioSocketChannel放入readBuf中。
后续会触发pipeline的channelRead事件,依次回调pipieline上各个handler的channelRead方法。
在连接创建的场景中,前面分析的ServerBootstrapAcceptor就是pipeline上的handler之一,它会对ChildChannel(NioSocketChannel)做后续的处理。