Netty源码阅读系列-连接如何创建

656 阅读3分钟

「我正在参与掘金会员专属活动-源码共读第一期,点击参与

前面的文章分析过,NioServerSocketChannel创建、初始化后会被注册到bossGroup事件循环中去。

客户端与服务器的连接创建后会回调serverBootstrapAcceptor的channelRead方法,参数包含NioSocketChannel类型的对象,该NioSocketChannel类型的对象即代表着一条与客户端的连接。

那么NioSocketChannel类型的对象是何时被创建的?

本文分析bossGroup(NioEventLoop)的run方法,了解事件循环的工作细节,连接如何被创建。

一、bossGroup的线程在做什么?

Netty server启动起来后,观察正在运行的线程,可以看到bossGroup的线程始终处于RUNNING的状态,通过Suspend操作可以定位到当前该线程执行到的代码行。

image.png

可以看到,线程在执行select(curDeadlineNanos)方法,其底层就是调用JDK Nio选择器的select()方法。

回过头来看run方法的开头处


strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());

这里会计算一个strategy值:

  1. 如果hasTasks()为true,即有任务待执行,则返回selectNow()的值,selectNow()可以理解为非阻塞版本的select(),返回值是一个非负数。

  2. 如果hasTasks()为false,则返回SelectStrategy.SELECT(值为-1)。

根据上面计算的strategy值,如果是非负数,代表有任务可做,在下面就不会进入select(curDeadlineNanos)的执行。直接执行后面的processSelectedKeys和runAllTasks。

否则进入select(curDeadlineNanos),阻塞直到超时或select得到结果。

image.png

image.png

这段代码中还有个ioRatio的变量值得注意,该变量的默认值是50,看代码的意思,该变量是控制任务处理时长与IO时长的比例。processSelectedKeys()的执行时长算作IO时长,可以根据IO时长与ioRatio计算出留给runAllTasks的时长。在实际使用中可以关注下这个值的影响,根据业务场景的不同做调优。

image.png

总结一下run方法的for(;;)循环中做了以下事情:

  1. 计算strategy值(主要是看是否有待执行的任务),决定是否进入阻塞的select()方法。

  2. 进入或不进入阻塞的select()方法。

  3. 根据需要执行processSelectedKeys(),可以理解为处理IO事件。

  4. 执行runAllTasks(),可以理解为处理非IO事件。

二、连接是如何创建的

当有客户端与服务端进行连接建立时,阻塞中的select()方法会因检测到accept就绪事件而跳出阻塞,在select()后面打断点进行调试可以观察到这点。

image.png

image.png

接下来是处理IO事件,随着调用栈的深入,先后进入processSelectedKeys(),processSelectedKeysOptimized(),

processSelectedKey(k, (AbstractNioChannel) a)这几个方法。

image.png

在processSelectedKey方法中,会通过调用SelectionKey的readyOps()方法确认是哪种事件就绪,打断点可以看到在这里该值是16,代表OP_ACCEPT事件。

image.png

继续进入unsafe.read()。

image.png

继续进入方法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中。

image.png

回到AbstractNioMessageChannel$NioMessageUnsafe的read()方法,下一步是触发pipeline的ChannelRead事件,依次调用pipeline上handler的channelRead方法。前面提到的ServerBootstrapAcceptor就是这个pipeline的handler之一。

打断点可以看到回调channelRead方法时带着readBuf作为参数,在连接建立时该参数的类型是NioSocketChannel。

image.png

image.png

三、本篇总结

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)做后续的处理。