Java 越来约卷,要了解的东西太多了 😂 😂 😂 !!!
一、什么是 select 、poll 、epoll ?
首先,读一个文件,是需要先把文件从硬盘读到内存,再从内存读到程序。这三个都是 IO 多路复用的方案,是一种高效事件通知机制。用于实现 I/O 多路复用的系统调用,他们是 I/O 的记录,不是直接操作文件。它们允许一个进程同时监视多个文件描述符,以确定它们中是否有可读、可写或异常等事件发生。这些系统调用在不同的操作系统中有不同的实现方式。
select
:select
是最早出现的多路复用函数,支持所有类型的文件描述符(普通文件、套接字、管道等)。- 它使用了一个包含文件描述符集的数据结构,通过参数来告知操作系统监视哪些文件描述符,以及等待的超时时间。
poll
:poll
是对select
的改进,支持更多的文件描述符。- 它使用一个
pollfd
数组来表示要监视的文件描述符,同时也可以设置超时时间。
epoll
:epoll
是 Linux 特有的多路复用机制,相较于select
和poll
,更为高效,特别适用于处理大量连接。- 它采用了事件驱动的方式,只在有事件发生时通知应用程序,而不需要扫描全部的文件描述符。
epoll
支持水平触发和边缘触发两种工作方式,边缘触发模式通常更高
二、他们的区别
epoll 和其他的区别
1、select 和 poll 都是被动调用,epoll 是主动通知
2、select和poll都有一个动作,每次都要先把用户态的数据copy到内核,操作完后再copy回用户态。epoll不用,他只用copy一次。
select
和poll
在每次调用时都需要将文件描述符集合从用户态复制到内核态,然后再将事件状态从内核态复制回用户态,这可能会导致额外的开销。而epoll
利用回调机制,只需要在初始时复制一次,然后通过回调通知应用程序关于事件的状态,减少了不必要的复制,因此更有效率。这也是为什么epoll
在处理大量并发连接时更加高效的原因之一。
select 和 poll 的区别
要调用 select 方法,需要每次都传递一个文件描述符集合,select 要从集合里循环,找到感兴趣的事件去执行。
poll 就不要,poll 会维护一个文件描述符数组,当 poll 方法被调用完方法自己会更新这个数组,不需要每次都传递。
区别列表
select | poll | epoll | |
---|---|---|---|
操作方式 | 遍历 | 遍历 | 回调 |
底层实现 | 数组 | 链表 | 哈希表 |
IO效率 | 每次调用都进行线性遍历,时间复杂度为O(n) | 每次调用都进行线性遍历,时间复杂度为O(n) | 事件通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到readyList里面,时间复杂度O(1) |
fd拷贝 | 每次调用select,都需要把fd集合从用户态拷贝到内核态 | 每次调用poll,都需要把fd集合从用户态拷贝到内核态 | 调用epoll_ctl时拷贝进内核并保存,之后每次epoll_wait不拷贝 |
产生年份 | 1984年在BSD中出现 | 1997年才实现 | 2002, 大神 Davide Libenzi 实现 |
三、文件描述符
文件描述符(File Descriptor)是一种抽象的资源标识符,用于访问文件、套接字、管道、设备和其他 I/O 相关的资源。在许多操作系统中,文件描述符是用来唯一标识打开的文件或其他 I/O 资源的整数。它充当了应用程序和操作系统之间的桥梁,允许应用程序读取、写入和管理这些资源。
文件描述符上的事件是指可以在特定文件描述符上发生的各种 I/O 事件。这些事件包括:
- 可读事件:表示文件描述符上有数据可供读取。这通常是输入数据到达的标志。应用程序可以使用读取操作来处理这些数据。
- 可写事件:表示文件描述符现在可以接受数据写入。这通常是输出数据的目标。应用程序可以使用写入操作将数据写入文件描述符。
- 异常事件:表示在文件描述符上发生了异常。这可能包括套接字连接中断或错误发生等情况。
这些事件通常由操作系统内核检测并报告给应用程序。应用程序可以通过不同的机制(如 select
、poll
、epoll
在 Linux 中)来等待和处理这些事件。这允许应用程序有效地进行异步 I/O 操作,以便同时管理多个文件描述符上的事件,提高应用程序的并发性能。
四、记录一个epoll从硬盘读取文件的方式
要将文件从硬盘通过 epoll 记录的 I/O 事件读入你的程序中,你需要执行以下步骤:
1. 创建 epoll 实例:首先,你需要创建一个 epoll 实例,可以使用 `epoll_create` 函数来完成。
2. 添加文件描述符到 epoll 实例:将你想要监视的文件描述符(通常是一个已打开的文件或套接字)添加到 epoll 实例。使用 `epoll_ctl` 函数,将文件描述符添加到 epoll 的监视列表中,同时指定你感兴趣的事件类型,例如 `EPOLLIN` 表示读事件。你可以添加多个文件描述符到 epoll 实例,以便同时监视多个文件。
3. 进入 epoll 循环:创建一个循环,在这个循环中,你会不断调用 `epoll_wait` 函数,等待文件描述符上发生感兴趣的事件。当有事件发生时,`epoll_wait` 会返回一个包含触发事件的文件描述符列表。
4. 处理事件:一旦 `epoll_wait` 返回触发的事件,你需要处理这些事件。对于读取文件,当文件描述符上发生可读事件时,你可以使用 `read` 或 `readv` 等系统调用来读取数据。在套接字编程中,你可以使用 `recv` 函数。
5. 处理完事件:在处理完事件后,返回到 epoll 循环,等待下一个事件。
这是一个基本的文件读取过程,使用 epoll 来实现非阻塞的 I/O 操作。请注意,epoll 是一种高效的事件通知机制,适用于需要同时监视多个文件描述符的情况,因此适合于高性能的 I/O 操作。
以下是一个简单示例的伪代码
// 创建 epoll 实例
int epoll_fd = epoll_create(1);
// 添加文件描述符到 epoll 实例
struct epoll_event event;
event.events = EPOLLIN; // 监视可读事件
event.data.fd = your_file_descriptor;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, your_file_descriptor, &event);
// 进入 epoll 循环
while (1) {
struct epoll_event events[MAX_EVENTS];
int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
for (int i = 0; i < num_events; i++) {
if (events[i].events & EPOLLIN) {
// 有数据可读
char buffer[BUFFER_SIZE];
int bytes_read = read(your_file_descriptor, buffer, BUFFER_SIZE);
// 处理读取的数据
}
}
}
// 关闭 epoll 实例
close(epoll_fd);
这个示例展示了如何使用 epoll 来监视文件描述符上的可读事件,并在有数据可读时执行读操作。在实际应用中,你需要适应你的程序逻辑,包括错误处理和文件关闭等。