公众号|松华说|Netty源码探究之启动服务与构建连接
  • 分享在京东工作的技术感悟,还有JAVA技术和业内最佳实践,大部分都是务实的、能看懂的、可复现的

扫一扫
关注公众号

Netty源码探究之启动服务与构建连接

博客首页文章列表 松花皮蛋me 2020-02-29 14:10

一、主线

二、源码分析

1、初始化NioEventLoopGroup

在MultithreadEventExecutorGroup的构造器中创建EventExecutor组children,然后根据组的个数返回一个选择器,用于后续选择Executor。

这里newChild方法会创建一个NioEventLoop实例。在NioEventLoop实例中会真正触发channel的实例化。

同时参数依次转化为SelectorProvider、SelectStrategy、RejectedExecutionHandler。如果在服务端没有配置时,比如在EchoServer的Demo中没有配置时,都会在NioEventLoop的构造器中设置默认值,方便我们开箱即用。

比如:SelectorProvider的默认值是KQueueSelectorProvider,SelectStrategy默认是DefaultSelectStrategyFactory,RejectedExecutionHandlers的逻辑默认是抛出”RejectedExecutionException”异常。

回到NioEventLoop的构造器,我们可以看到这里有关键的两个步骤,第一步调用父类SingleThreadEventLoop创建任务队列,本质是由链表构成有界阻塞队列LinkedBlockingQueue。第二步是创建一个多路复用器,调用KQueueSelectorProvider的openSelector方法。

2、注册与绑定

我们先看initAndRegister方法探探个究竟,可以看到通过channelFactory创建了一个Channel对象,再进行init设置,最后再进行注册register。

那这个channelFactory是什么东西呢?其实是包装了ChannelClass的ReflectiveChannelFactory。我们在”EchoServer”配置的ChannelClass为NioServerSocketChannel.class。所以这里执行newChannel方法,实际是在通过反射的方式调用NioServerSocketChannel的无参构造方法。

我们再来看下init(channel)方法。

可以看到,init方法处理是在设置配置项、创建ChannelPipline、在pipleline中添加ServerBootstrapAcceptor处理器。

事实上,一个Channel中包含一个ChannelPipeline,用来处理Channel中的事件,一个ChannelPipeline中可以包含很多个handler。同时我们也注意到在hadler中继承了ChannelInboundHandlerAdapter类并实现了他的一些方法,比如:channelRead,channelActive,channelInactive等等,我们看到这些方法中都有一个参数:ChannelHandlerContext ctx。这个ChannelHandlerContext就是handler的上下文对象,有了这个ChannelHandlerContext你就获得了一切,你可以获得通道,获得事件的控制权。

我们再来看下init之后的注册方法。

这个next方法其实是在通过chooser选择一个EventExecutor。如果线程数为偶数的话使用的是PowerOfTwoEventExecutorChooser进行选择,它是通过位运算选择的,效率更高。

我们知道newChooser的参数是EventLoop对象组,同时EventLoop的注册方法委托给父类SingleThreadEventLoop实现。

继续跟下去会走到AbstractChannel的内部类AbstractUnsafe中。根据inEventLoop方法判断当前线程是否在事件循环线程中,如果是则可以直接执行,否则需要提交给eventLoop线程去执行。这样可以保证一个Channel上的事件处理的线程安全。

在register0方法,服务端刚启动时是不具备Active条件的,也就是说最下面红框圈出来的逻辑在这时是得不到机会执行的。


继续跟下去到了AbstractNioChannel的doRegister方法,调用JDK原生方法将channel注册到selector中,得到selectionKey。

需要注意的是,这里注册并不是监听OP_ACCEPT。因为这里的ops值为0。我们知道NIO编程中会根据ops进行位运算,决定当前应该处理”读、写、客户端连接请求、服务端连接接收”中的哪一个事件。

回到ChannelFuture doBind(final SocketAddress localAddress) 方法中。调用initAndRegister得到一个异步ChannelFuture对象,如果该任务已完成则执行doBind0进行真正的绑定逻辑。

pipeline串行操作中有很多的handler。我们在head上用”jump to source type”的方式找到我们想要的类。

继续跟下去就会到AbstractChannel的内部类AbstractUnsafe中。

doBind绑定的逻辑就是监听指定端口。

再来看看pipeline.fireChannelActive()的逻辑,我们继续用在head上”jump to source type”的方式找到我们想要的类。


重点看下readIfIsAutoRead方法,它其实也是一个pipline操作,继续找到实际执行的类。

从doBeginRead方法可以得出结论,pipeline.fireChannelActive()其实是触发我们监听的事件,这里是触发监听OP_ACCEPT。

3、总结:启动服务的本质

4、总结:接受连接的本质