总体概述
类关系
给ServerBootstrap配置两个EventLoopGroup,一个建立连接,一个处理网络io。
EventLoopGroup给EventLoop分配线程。
在 Netty 中,EventLoop 通过不断轮询 Selector 来检测 Channel 上发生的事件,当 Channel 上的事件到达时,EventLoop 会将事件传入 相应的Channel 的成员变量 ChannelPipeline 中,经过所有ChannelHandler 来处理这些事件。
执行流程
当一个事件传入时,会判断是连接事件还是其他事件,如果是连接事件交给eventloopgroup中的建立连接的eventloop,其中的nioserverchannel会处理连接事件,如果是其他事件,那么会交给处理网络io的eventloop中的channel,eventloop轮询发现有channel有事件需要执行,那么就在这个eventloop线程中调用channel的channelpipeline,执行相应的channelhander。
其中:eventloopgroup可以指定eventloop的数量,eventloopgroup就像线程池一样,会负载均衡地分配建立连接的channel给不同的eventloop。
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;
Promise和Future
Promise 是 Future 的扩展,它继承了 Future 接口。它们都代表了一个尚未完成的操作,可以用于异步操作的结果通知和处理。
区别:文章来源:https://www.toymoban.com/news/detail-823033.html
可写性: Future 是只读的,一旦创建就不能被修改。而 Promise 是可写的,可以通过它来设置操作的结果。
操作结果的设置: 在使用 Future 时,你只能等待其完成,而不能主动设置操作的结果。而在使用 Promise 时,你可以主动设置操作的结果,因此它提供了更灵活的控制。
用途: Future 通常用于表示一个异步操作的结果,而 Promise 用于表示一个异步操作的开始和结果的产生。在很多情况下,你会首先创建一个 Promise,然后将它转化为一个 Future 对象,传递给其他部分的代码,使得它们可以等待异步操作的结果。文章来源地址https://www.toymoban.com/news/detail-823033.html
到了这里,关于NIO和netty的常用类的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!