线程模型
对于处理channel的IO读写事件,Netty提供了EventLoopGroup和EventLoop两个接口。
- EventLoop是一个IO线程,但是与普通IO线程不一样的是,注册到或者说绑定到这个线程的channel,在该channel的整个生命周期内,即客户端和服务端的交互,都是在这个eventLoop线程处理的,不会切换到其他线程,这样就没有线程安全问题,这也是命名为eventLoop(IO事件循环)的原因。一个EventLoop线程可以处理多个channel的IO事件,但是一个channel只能绑定到一个EventLoop线程。
- EventLoopGroup:eventLoop线程池实现,继承于ScheduledExecutorService,维护一个eventLoop线程池,在register方法中,通过调用next方法从线程池中获取一个eventLoop线程,然后将channel绑定到该eventLoop线程。
NioEventLoopGroup:事件处理线程池
NioEventLoopGroup底层是基于NIO,即非阻塞IO的。
- 线程池:NioEventLoopGroup继承于MultithreadEventLoopGroup,MultithreadEventLoopGroup提供了线程池实现,默认线程池大小为:机器处理器个数的2倍,如下:
SelectorProvider:selectorProvider用来创建Java NIO selector实例,类似于一个selector的工厂类。
selectorProvider自身实例是通过SelectorProvider的静态provider方法创建的,且该静态provider方法线程安全的,使用了synchronized锁。
selectorProvider实例只创建一次,后续调用provider方法,则直接返回,所以该selectorProvider实例是被所有EventLoopGroup所共享的。selectorProvider的具体类型,则可以根据应用自身指定,如下方法:loadProviderFromProperty,loadProviderAsService,如果都没有指定,则根据操作系统来获取,如操作系统是Linux,2.6及以上版本内核使用EPollSelectorProvider,以下则是使用PollSelectorProvider。
- selectorProvider的所有实例方法都是线程安全,如下创建selector用到的openSelector。
- selector的创建:selectorProvider生产selector,具体为通过openSelector方法创建的,这个是抽象实例方法,不是静态方法,由具体selectorProvider实现类,如EPollSelectorProvider,负责实现创建selector的逻辑,如下:
在每个NioEventLoop中都包含一个NIO selector,eventLoop通过该selector获取绑定到该eventLoop的channels的IO事件。
NioEventLoop:事件处理线程
NioEventLoop可以被多个channels绑定,channel将自己注册到NioEventLoop实例的selector,通过selector监听和获取该channel的IO事件。(每个channel只能绑定到一个eventLoop)每个channel整个生命周期内的IO事件都由该EventLoop线程处理。
- NIO selector: Java NIO Selector是一个IO多路复用器,是实现非阻塞IO的核心。NioEventLoop自身包含一个Java NIO的selector实例,由该selector负责监听所有绑定到该eventLoop线程的channel的所有IO事件。如下NioEventLoop的selector相关的字段:
事件循环机制:channel整个生命周期IO事件由同一个eventLoop线程处理
简单来说,就是创建channel时,将channel注册到NioEventLoop的selector, NioEventLoop监听自身的selector,获取有IO事件到来的channel并处理。由于NioEventLoop自身就是一个线程实现类,故channel整个生命周期的IO事件都是在这个线程处理的,而不会由其他线程处理。
具体过程如下分析:
channel绑定NioEventLoop线程,其实是绑定到NioEventLoop线程的selector
- EventLoopGroup从eventLoop线程池获取一个eventLoop线程:
由上面的分析以及Bootstrap源码分析Netty源码分析-BootStrap服务启动类可知:channel是通过注册到EventLoopGroup,然后由EventLoopGroup从自身所管理的eventLoop线程池中获取一个eventLoop线程,然后将channel绑定到这个eventLoop线程的。具体如何将channel绑定到eventLoop线程是由eventLoop自身控制的。如下图:next()方法为返回一个eventLoop。
- eventLoop线程通过register方法,完成channel到eventLoop的绑定:将eventLoop作为参数,调用channel自身的register完成到eventLoop线程的绑定:
channel的register:在AbstractChannel中实现,channel自身类实现绑定到eventLoop的逻辑。
对channel的eventLoop进行赋值,然后看当前执行线程是否就是eventLoop线程,是则直接执行register0;不是则使用eventLoop.execute -> register0
- register0:将channel绑定到eventLoop,并通知pipeline中的channelHandler这个注册事件。源码如下:
步骤如下:
- doRegister:将该channel注册到eventLoop的selector中,具体为在doRegister方法完成channel到selector的注册,doRegister为抽象方法,由具体实现类,即selector的类型,实现。这个方法在注册失败时,抛异常,则后面步骤不执行。
- pipeline.fireChannelRegistered():产生注册registered事件放到pipeline,使得pipeline中的channelHandlers按需处理该事件,即在channelRegistered方法定义处理逻辑;
- isActive:对应SocketChannel来说,是ch.isOpen() && ch.isConnected(),即channel已经connected成功,可以处理IO事件了:新的channel则在pipeline中传播active事件,重新注册registered的,则beginRead,继续读取,具体为在selector中注册监听OP_READ事件。如下AbstractNioChannel的beginRead的底层实现:
- doRegister的实现类
- AbstractNioChannel的doRegister实现:要么成功注册,要么失败抛出异常,通知调用方注册失败了。
- javaChannel().register方法:selector注册channel并产生select key,添加到select key集合,最终完成channel到eventLoop的selector的注册。
NioEventLoop对channel的IO请求处理
- NioEventLoop在run方法for循环轮询,监听有IO事件的Select keys集合
- processSelectedKeys方法处理对应channel的IO事件,方法调用关系如下:
processSelectedKeys -> processSelectedKeysPlain(selector.selectedKeys()) ->
processSelectedKey(SelectionKey k, AbstractNioChannel ch),该过程均在NioEventLoop线程内完成。 - processSelectedKey(SelectionKey k, AbstractNioChannel ch):获取到对应的channel和事件类型SelectedKey,在eventLoop线程中使用channel来完成数据的读写:底层调用如下,获取到对应的channel和事件类型SelectedKey,在eventLoop线程中使用channel来完成数据的读写:
(1)对于读相关如OP_READ,OP_ACCEPT,则channel交给pipeline;
(2)对于写相关如OP_WRITE,OP_CONNECT,则通过底层Java NIO SocketChannel交给操作系统socket发送出去。