Netty的NIO的常用概念
ServerBootstrap
ServerBootstrap 是一个用于帮助配置和启动服务器的类。它是Netty中用于创建服务器端应用程序的主要入口点
Channel
在计算机网络编程中,Channel(通道) 是一种抽象概念,代表着数据在源和目标之间的连接。通道是数据传输的通路,通道的两端可以是网络连接、文件、套接字等。
Channel类型
- 文件通道 (FileChannel): 用于对文件进行读写操作。
- 套接字通道 (SocketChannel, ServerSocketChannel, DatagramChannel): 用于进行网络通信,这些通道是基于 Java NIO 提供的 SelectableChannel 的实现。它们通常通过 EventLoop、ChannelPipeline 和自定义的处理器来实现异步和事件驱动的网络编程。
SocketChannel 是用于 TCP 客户端通信的通道。
它可以连接到远程服务器,并进行双向通信。
SocketChannel 支持阻塞和非阻塞的模式。
ServerSocketChannel 是用于 TCP 服务器通信的通道。它监听客户端的连接请求,并创建一个新的 SocketChannel 用于与客户端通信。
ServerSocketChannel 也支持阻塞和非阻塞的模式。
DatagramChannel 是用于 UDP 通信的通道。UDP 是面向消息的,DatagramChannel 支持无连接的数据报传输。
DatagramChannel 与 SocketChannel 相比更轻量,适用于不需要可靠性传输的场景。
- 管道 (Pipe.SinkChannel, Pipe.SourceChannel): 用于在两个线程之间进行通信。
通道(Channel)和流(Stream)有一些相似之处,但也有一些关键的区别。
相似之处
数据传输: 通道和流都用于在程序和数据源/目的地之间进行数据传输。
字节流和字符流: 通道和流都可以用于处理字节数据(字节流)和字符数据(字符流)。
区别
阻塞非阻塞
通道: 通道通常是非阻塞的,可以使用选择器(Selector)来实现多路复用,从而监控多个通道的状态。
流: 流通常是阻塞的,即在读写操作时,如果没有数据可读或没有足够的空间可写,程序会阻塞等待。
双向性
通道: 通道是双向的,可以支持读和写操作。例如,文件通道 (FileChannel) 可以同时支持读取和写入文件。
流: 流通常是单向的,即要么是输入流(从数据源读取数据),要么是输出流(向数据源写入数据)。需要使用两个流才能实现双向传输。
底层实现
通道: 通道通常直接映射到操作系统的底层 I/O 操作,因此性能可能更好。在 Java NIO 中,通道与 Selector 搭配使用,可以实现非阻塞 I/O。
流: 流通常是在通道的基础上建立的高级抽象,提供了更高层次的 API。在 Java IO 中,流是基于字节或字符的 I/O 操作。
直接缓冲区
在 Java NIO 中,通道(Channel)支持直接缓冲区(DirectBuffer),这使得数据可以直接从内存中的缓冲区传输到通道,或者从通道直接传输到内存中的缓冲区,而不需要中间步骤。
通道: 通道支持直接缓冲区,允许将数据直接从内存中的缓冲区传输到通道,而不需要中间步骤。
流: 流通常不支持直接缓冲区,需要通过中间的数组或缓冲区来进行数据传输。
通常有两种类型的缓冲区:堆缓冲区(Heap Buffer)和直接缓冲区(Direct Buffer)。
堆缓冲区: 在堆上分配的缓冲区,数据位于Java堆内存中。这是默认的缓冲区类型。
直接缓冲区: 直接分配在操作系统的内存中,而不是Java堆中。直接缓冲区可以通过 ByteBuffer.allocateDirect() 方法来创建。
直接缓冲区的优势之一是可以通过 FileChannel 的 transferTo() 和 transferFrom() 方法直接在通道之间传输数据,而无需通过中间缓冲区。
当使用非直接缓冲区(Heap Buffer)时,通常的操作是先将数据从通道读取到堆缓冲区,然后再进行其他操作。同样,当写入时,数据也会从堆缓冲区写入到通道。
Channel内部有成员变量unsafe和Pipeline,unsafe是Channel网络io的底层实现,ChannelPipeline 负责管理该 Channel 上的处理器链。
SelectionKey
简单源码
public interface SelectionKey {
// 表示对读事件感兴趣
static final int OP_READ = 1;
// 表示对写事件感兴趣
static final int OP_WRITE = 4;
// 表示对连接事件感兴趣
static final int OP_CONNECT = 8;
// 表示对接受事件感兴趣
static final int OP_ACCEPT = 16;
// 返回与此键关联的通道
SelectableChannel channel();
// 返回选择器
Selector selector();
// 返回表示感兴趣的事件的操作集合
int interestOps();
// 设置感兴趣的事件的操作集合
SelectionKey interestOps(int ops);
// 返回表示已准备就绪的操作的操作集合
int readyOps();
。。。。。。。
}
ChannelPipline
ChannelPipline是ChannelHandler的容器,维护了一个Handler的链表和迭代器。
当事件发生时,在eventloop的中它会被传递给ChannelPipeline,然后从头到尾依次经过每个ChannelHandler。
使用ServerBootstrap启动服务器netty会自动为每个Channel创建一个独立的pipepline,我们只要将想用的ChannelHandle加入即可。
ChannelHandler
事务的执行是通过channelpipeline,连接,读写等网络io操作是由channelpipeline的Handler执行(Handler中调用unsafe的方法)。
Eventloop
Eventloop作为NIO框架的Reactor线程,其中有一个Select的成员变量,用来实现多路复用。
Channel需要注册到EventLoop的多路复用器上,EventLoop本质上就是处理网络读写任务的Reactor线程,在Neety中,它还可以用力啊处理定时任务和用户自定义NioTask任务。
Eventloop的run方法是其核心,伪代码:
public void run() {
while (true) {
try {
// 阻塞直到有事件发生
int readyChannels = selector.select();
// 处理所有已经就绪的事件
if (readyChannels > 0) {
Set<SelectionKey> selectedKeys = selector.selectedKeys();
for (SelectionKey key : selectedKeys) {
if (key.isAcceptable()) {
// 处理连接事件
// 处理连接事件的逻辑
} else if (key.isReadable()) {
// 处理读事件
// 处理读事件的逻辑
} else if (key.isWritable()) {
// 处理写事件
// 处理写事件的逻辑
}
// 可以处理更多类型的事件,根据实际需要扩展
}
selectedKeys.clear();
}
// 处理任务队列中的任务
while (!tasks.isEmpty()) {
Runnable task = tasks.poll();
task.run();
}
} catch (IOException e) {
// 处理异常情况
e.printStackTrace();
}
}
EventGroup
EventLoopGroup 负责管理一组 EventLoop 实例,每个 EventLoop 实例与一个线程相关联。
EventLoopGroup 提供了 shutdownGracefully() 方法,用于优雅地关闭所有关联的 EventLoop。
服务端启动时候,创建了两个NioEventLoopGroup,他们是两个独立的Reactor线程池,一个用来接收客户端的TCP连接,另一个用来处理IO相关的读写操作,或者执行系统Task,定时任务Task等。
接收客户端请求的线程池职责
- 接收客户端的TCP连接,初始化Channel参数。
- 将链路状态变更事件通知给ChannelPipeline。
处理IO操作的线程池的职责
- 异步读取通信数据包,发送读事件给ChannelPipeline;
- 异步发送消息,通过发送读事件给ChannelPipeline。
- 执行系统Task和定时Task;
核心类之间的关系
给ServerBootstrap配置两个EventLoopGroup,一个建立连接,一个处理网络io。
EventLoopGroup给EventLoop分配线程。
在 Netty 中,EventLoop 通过不断轮询 Selector 来检测 Channel 上发生的事件,当 Channel 上的事件到达时,EventLoop 会将事件传入 相应的Channel 的成员变量 ChannelPipeline 中,经过所有ChannelHandler 来处理这些事件。