文章摘要(AI生成)
线程组的作用在上面NIO中,我们已经按职责对服务端和客户端中的操作进行了线程分配:线程池事件相应操作客户端-主线程组客户端初始化连接服务端客户端-主线程组客户端工作发送请求客户端-主线程组客户端工作处理结果客户端-解析线程组客户端工作数据解码与编码客户端-主线程组服务端初始化绑定端口服务端-主线程组
线程组的作用
在上面NIO中,我们已经按职责对服务端和客户端中的操作进行了线程分配:
线程池 | 事件 | 相应操作 |
---|---|---|
客户端-主线程组 | 客户端初始化 | 连接服务端 |
客户端-主线程组 | 客户端工作 | 发送请求 |
客户端-主线程组 | 客户端工作 | 处理结果 |
客户端-解析线程组 | 客户端工作 | 数据解码与编码 |
客户端-主线程组 | 服务端初始化 | 绑定端口 |
服务端-主线程组 | 服务端初始化 | 监听端口连接 |
服务端-主线程组 | 服务端工作 | 接收连接请求 |
服务端-主线程组 | 服务端工作 | 创建连接通道 |
服务端-轮询线程组 | 服务端工作 | 接收请求 |
服务端-轮询线程组 | 服务端工作 | 返回结果 |
服务端-轮询线程组 | 服务端工作 | 数据解码与编码 |
总结下来共有两个线程组:
- 主线程组:负责连接建立、端口绑定等操作
- 轮询线程组:轮询通道,读取和写入通道数据
- 解析线程组:负责在通道数据读写时对数据进行编码和解码
一个线程组负责了一个通道的维护,客户端侧通道维护简单,只需要一个线程组即可完成通道操作。服务端则是需要多个线程组,一个负责监听连接,其他线程组负责监听不同客户端的读写请求。如果我们编解码交给了一个线程组来执行,那么我们服务端需要维护客户端读写请求的就需要两个线程组。
轮询的范围
在NIO中,监听各通道的数据变更我们是通过selector(底层是通过poll命令或epoll命令)实现的,这里我们需要思考一下:在网络交互中,有哪些操作都是需要我们进行定时轮询的呢?
从上述的总结中,我们不难看出需要定时轮询的操作:
- 监听网络通道变更
- 新连接请求
- 连接超时判定
- 通道数据读写
这些操作在NIO中都是异步的,需要定时轮询确定状态。另外,有些操作虽然不是需要通过定时轮询来确定状态,但是可以通过任务队列的方式将顺序执行的任务放到任务队列中依次进行异步执行:
- 向轮询器中注册通道
- 连接客户端
其中,除了监听网络通道变更之外,其他都是需要我们放到定时的任务队列中进行循环执行的。所以我们可以定义这样一个死循环线程,它每次循环中负责处理两件事情:
- 执行通道轮询
- 执行任务队列中的任务
概念梳理
在上述流程中,我们已经具象了两个对象:死循环线程和线程组。线程组为我们按通道的维度进行了网络事件的划分,死循环线程则负责网络事件的执行:
这个概念已经接近Netty中的网络模型了,只不过在Netty中,死循环线程被称为事件循环(即循环处理网络事件),而线程池组则被称为事件循环组。而我们死循环线程轮询到的变更通道则交给了通道处理器进行处理。
对通道变更数据的读写,我们可以通过设计模式中的责任链模式,对变更数据进行依次处理:
总结下来我们可以得到如下几个角色:
事件循环:主要负责进行执行轮询(接收连接、确认连接、数据读取)和其他(发起连接、通道注册、超时判断)任务,一个事件循环对应一个NIO中的轮询器,而一个轮询其只注册一个通道。
事件循环组:维护一组事件循环,例如对于服务端来说,一个事件循环组包括一个服务端通道的事件循环和多个客户端通道的事件循环。
单线程的事件循环和多线程的事件循环组都是继承自线程池,不同的是事件循环组中维护了一个事件循环数组,而事件循环中则通过线程池启动状态限制只有一个线程在做事件循环以保证任务队列顺序执行。
通道:主要包含了NIO channel相关的调用
启动器中包含了一个通道创建的工厂类,通过这个工厂类我们可以在通道注册的时候创建对应的通道。
服务端通道和客户端通道的职责不同,所以各自在底层调用NIO的操作方法又有各自的UnSafe内部实现类。
通道处理器:负责管道初始化、管道数据读取
通道处理器又分为入道处理器器和出道处理器,入道处理器只负责消息的加工和传递;而出道处理器负责与调用NIO通道的底层方法进行实际的注册、写入等操作
评论区