作者:星巴刻
在 Netty NIO 编程中,NioEventLoop 是一个核心类,发挥着极其重要的作用,要深入 Netty 的核心,必须深刻地对这个类进行探究和理解。
服务端
在服务端程序中有两类 NioEventLoop。一类称为 parent (也称 boss),这一类 NioEventLoop 一般也就只有一个,专门负责发现客户端连接。另一类是 child (也称 worker),这一类 NioEventLoop 要设置有限的若干个,默认的个数只是为 CPU 核数的 2 倍,它负责处理客户端连接的信息读写。使用 NIO 模式的服务端程序,child 的个数可远远小于客户端连接数。作为 parent 或者 child 的 NioEventLoop 都可以注册不限制一个的 Channel 实例进行服务。
parent 要负责的是表示服务端侦听端口的 NioServerSocketChannel 实例,NioServerSocketChannel 实例只有一个。把这个 NioServerSocketChannel 注册到 parent 后,parent 就开始负责发现该 NioServerSocketChannel 上新的客户端连接,触发对新来的客户端连接进行初始化工作,引导到后续的信息读写等处理程序。
child 要负责的是表示客户端连接的 NioSocketChannel 实例。NioSocketChannel 实例和客户端连接数一一对应,因此程序中同时会有很多 NioSocketChannel 实例。由于 child 数量有限(8 核 CPU 下默认是 16 个 child),因此一个 child 要同时负责多个 NioSocketChannel,发现这些连接的可读可写事件,触发应用程序从系统缓冲区读取字节流、对消息进行编解码、调用逻辑程序处理、继续对先前中断的系统缓冲区的写入等。
最简单地说,NioEventLoop 的首要职责就是为注册在它上的 channels 服务,发现这些 channels 上发生的新连接事件、读写等 I/O 事件,然后将事件转交 channel 流水线处理。
下表是一个服务端程序的一个简单快照。每一行代表一个 NioEventLoop,第一行是 parent,负责处理NioServerSocketChannel。随后是16行的 child,每一个 child 负责多个NioSocketChannel。
随着程序的进行,表格的行数并不会变化,但是每行的 channels 会随着连接的建立和断开不断地变化着。Netty 并不保证每个 NioEventLoop 负责的 channels 个数要进行均衡, Netty 对新建立的连接采用简单的寻找下一个 NioEventLoop 的策略,并且在连接断开后也不会进行重新分布;或许某些极端情况下,可能出现某些 NioEventLoop 负责的 channels 比其他 NioEventLoop 负责的要多很多,但这似乎不是问题。
说完服务端说客户端
客户端比服务端简单,客户端程序只有一类 NioEventLoop,直接就是 parent ,没有 child。一般地,由于客户端和服务端只保留一个连接,所以只有一个 channel,即一个 parent 只负责 NioEventChannel 的 I/O 事件。但对于需要同一个服务端保持多个连接的,或者需要和多个服务端保持连接的,则程序里依然会存在多个 NioEventChannel 实例,但在实践上还是建议这些不同的的连接使用同一个 NioEventLoop,即都统一归到 parent 负责:
下一个问题:NioEventLoop 是怎么发现其所负责的 channels 发生了 I/O 事件?
2017-11-18