事件循环组(EventLoopGroup)
我们可以将事件循环组简单的理解为线程池,它里面包含了多个事件循环线程(也就是EventLoop),初始化事件循环组的时候可以指定创建事件循环个数。
如图:
每个事件循环组包含多个事件循环线程(EventLoop),每个事件循环线程绑定一个任务队列,该任务队列用于处理非IO事件,比如通道注册,端口绑定(后面章节剖析源码的时候会详细说)等等,事件循环组就好比一个线程池,线程池中的EventLoop线程均处于活跃状态,每个EventLoop线程绑定一个选择器(Selector),一个选择器(Selector)注册了多个通道(客户端连接),当通道产生事件的时候,绑定在选择器上的事件循环线程就会激活,并处理事件。
对于BossGroup事件循环组来说,里面的事件循环只监听通道的连接事件(即accept())。
对于WorkerGroup事件循环组来说,里面的事件循环只监听读事件(read())。如果监听到通道的连接事件(accept()),会交给BossGroup事件循环组中某个事件循环处理,处理完之后生成客户端通道(channel)注册至WokerGroup事件循环组中的某个事件循环,并绑定读事件,这个事件循环就会监听读事件,客户端发起读写请求的时候,这个事件循环就会监听到并处理。
事件循环(EventLoop)
事件循环可以理解为事件线程,专用于处理客户端的连接、读写事件。
如图:
对于Netty而言,这里的woker线程实际上也是EventLoop线程,只不过这个事件循环是专门用来解码客户端读写事件的,而图中的事件循环(EventLoop)是专门用来处理客户端连接事件的。也就是说,一个事件循环组(BossGroup)中的事件循环(EventLoop)线程监听到连接事件后创建channel(通道),并将通道注册进另一个事件循环组(WorkerGroup)中的某个事件循环线程(这里指图中的woker线程)所绑定的Selector中,这个事件循环将会监听这个通道后续的读写事件。
选择器(selector)
Selector绑定一个事件循环线程(EventLoop),其上可以注册多个通道(可以简单的理解为客户端连接),Selector负责监听通道的事件(连接、读写),当客户端发起读写请求的时候,Selector所绑定的事件线程(EventLoop)就会唤醒,并从通道中读取事件进行处理。
如图:
任务队列和尾任务队列
一个事件循环绑定一个任务队列和尾队列,用于存储通道事件。
通道(channel)
可以简单的理解为客户端连接,但是它的底层到底是什么呢?
在Linux的世界中,一切皆文件!可能是一个硬盘上的普通文件、FIFO、管道、终端、键盘、显示器,甚至是一个网络连接也是一个文件!一个文件对应一个文件描述符(FD),这有点像JAVA中一个对象引用对应一个实例,Linux程序在执行任何形式的 IO 操作时,都是在操作文件。 Linux程序是通过文件描述符去操作实际的文件,Linux系统维护一个文件描述符到文件的映射表(我们可以简单的理解为Map)。
由于在UNIX系统中支持TCP/IP协议栈,就相当于引入了新的IO操作,也就是Socket IO,这个IO操作专用于网络传输。因此Linux系统把Socket也看作是一种文件。
我们在使用Socket IO发送数据的时候,实际上就是操作文件,首先打开文件,将数据写进文件(文件的上层也有一层缓存,叫文件缓存),再将文件缓存中的数据拷贝至网卡的发送缓冲区,再通过网卡将缓冲区的数据发送至对方的网卡的接收缓冲区,对方的网卡接收到数据后,打开文件,将数据拷贝到文件,再将文件缓存中的数据拷贝至用户缓存,然后再处理数据。
channel是对Socket的封装,因此它的底层也是在操作文件,所以操作channel的就是在操作Socket,操作Socket(本身就是一种文件)就是在操作文件,因此一个Socket对应一个文件描述符(FD),所以一个channel就对应一个文件描述符,所谓的通道事件,实际就是往这个通道(channel)对应的文件描述符所指向的文件写入数据!选择器(Selector)监听这个通道所对应的文件中是否接收数据,一旦监听到某个文件接收到数据,选择器就会激活并对数据进行下一步处理。
管道(pipeline)
管道是以一组编码器为结点的链表,用于处理客户端请求,也是真正处理业务逻辑的地方。
处理器(handler)
处理器,是管道的一个结点,一个客户端请求通常由管道里的所有处理器(handler)逐一的处理。
事件KEY(selectionKey)
当通道(channel)产生事件的时候,Selector就会生成一个selectionKey事件,并唤醒事件线程去处理事件。
如图:
上边我们说将通道注册至选择器,选择器监听通道的事件,实际上底层是怎么实现的呢?
通道底层维护一个事件KEY集合,这个集合表示通道感兴趣的事件,比如通道监听读事件,那么这个集合就只有一个事件KEY元素,就是这个读,而选择器也维护了一个事件KEY集合,但是,选择器维护的是所有通道的感兴趣事件,当选择器监听到某个通道的某个事件的时候,就会从选择器中的事件集合中拿到对应事件的事件KEY元素,然后再通过这个元素拿到对应的通道,然后再从通道读取数据到缓冲区,然后再进行业务处理,具体的细节,我会在后边的章节深入剖析。
缓冲(Buffer)
NIO是面向块的IO,从通道读取数据之后会放进缓存(Buffer),向通道写数据的时候也需要先写进缓存(Buffer),总之既不能直接从通道读数据,也不能直接向通道写数据。\
缓冲池(BufferPool)
这是Netty针对内存的一种优化手段,通过一种池化技术去管理固定大小的内存。(当线程需要存放数据的时候,可以直接从缓冲池中获取内存,不需要的时候再放回去,这样不需要去频繁的重新去申请内存,因为申请内存是需要时间的,影响性能)
以上是Netty的核心组件的简要描述。
组件与组件之间的关系如下:
- 一个事件循环组包含多个事件循环
- 一个选择器只能注册进一个事件循环
- 一个事件循环包含一个任务队列和尾任务队列
- 一个通道只能注册进一个选择器
- 一个通道只能绑定一个管道
- 一个管道包含多个服务编排处理器
- Netty通道和原生NIO通道一一对应并绑定
- 一个通道可以关注多个IO事件
Netty通信全流程图:
也可以点此查看大图:查看原图