Linux—I/O复用---select、poll、epoll
目录
1. select
2. poll
3. epoll
4、数据复制情况分析
(1)select 和 poll
(2)epoll
5、代码示例:
(1)select 函数的简单 TCP 服务器,用于处理多个客户端的连接和数据收发代码:
代码功能概述
代码结构分析
1. 函数定义
2. main 函数
(2)poll实现代码:
(3)epoll代码示例:
不理解的代码:
epoll_ctl 函数概述
函数原型:
参数说明
工作模式(面试问区别):
水平触发(Level Triggered,LT)
定义
特点
示例场景
边缘触发(Edge Triggered,ET)
定义
特点
示例场景
(1)I/O复用是什么?
I/O 复用是一种同步 I/O 模型,它允许应用程序在多个 I/O 事件(如读、写事件)上进行监听,并在这些事件发生时得到通知,从而能够高效地处理多个 I/O 操作,而无需为每个 I/O 操作创建一个单独的线程或进程。
有数据的优先处理。
Linux中如何处理I/O服用:
select、poll、epoll
select
和poll
是比较传统的 I/O 复用机制,具有较好的跨平台性,但在处理大量文件描述符时性能较差。epoll
是 Linux 特有的高效 I/O 复用机制,适用于处理大量并发连接的场景,性能优于select
和poll
。
1.
select
- 原理:
select
函数会监视多个文件描述符的集合,程序会将需要监视的文件描述符添加到对应的集合(读集合、写集合、异常集合)中,然后调用select
函数进行阻塞等待。当集合中的某个文件描述符上有对应的事件发生(如可读、可写、异常),select
函数会返回,同时修改集合以指示哪些文件描述符上有事件发生。- 优点:具有良好的跨平台性,几乎所有的类 Unix 系统都支持。
- 缺点:
- 支持的文件描述符数量有限,通常默认最大为 1024。
- 每次调用
select
时都需要将文件描述符集合从用户空间复制到内核空间,开销较大。- 需要遍历整个文件描述符集合来确定哪些文件描述符上有事件发生,时间复杂度为 O(n)。
2.
poll
- 原理:
poll
函数与select
类似,也是用于监视多个文件描述符的事件。它使用一个pollfd
结构体数组来指定要监视的文件描述符和对应的事件,然后调用poll
函数进行阻塞等待。当有事件发生时,poll
函数会返回,并在pollfd
结构体中标记哪些文件描述符上有事件发生。- 优点:
- 没有文件描述符数量的限制。
- 不需要像
select
那样每次调用都重新设置文件描述符集合,使用起来相对方便。- 缺点:
- 仍然需要将
pollfd
结构体数组从用户空间复制到内核空间,有一定的开销。- 同样需要遍历
pollfd
结构体数组来确定哪些文件描述符上有事件发生,时间复杂度为 O(n)。
3.
epoll
- 原理:
epoll
是 Linux 特有的 I/O 复用机制,它通过epoll_create
创建一个epoll
实例,然后使用epoll_ctl
函数向该实例中添加、修改或删除要监视的文件描述符和对应的事件。最后,使用epoll_wait
函数进行阻塞等待,当有事件发生时,epoll_wait
函数会返回,并将有事件发生的文件描述符信息存放在一个数组中。- 优点:
- 采用事件驱动机制,只返回有事件发生的文件描述符,避免了遍历整个文件描述符集合,时间复杂度为 O(1)。
- 内核和用户空间之间的数据传输采用了内存映射的方式,减少了数据复制的开销。
- 支持水平触发(LT)和边缘触发(ET)两种模式,边缘触发模式可以进一步提高效率。
- 缺点:
- 只在 Linux 系统上可用,不具备跨平台性。
poll和select 实现都是以轮询的方式,时间复杂度是O(n);
epoll :内核实现以 注册回调函数实现,时间复杂度O(1);
epoll_create : 创建内核事件表-----收集描述符------本质是红黑树
epoll_ctl:向内核事件表中添加、移除、修改描述符(加、删、改红黑树节点)
epoll_wait:获取就绪描述符-------从就绪队列中获取
epoll:相当于直接告诉程序取谁的数据,谁的数据就绪,select、poll要进行遍历,看谁的事件就绪了。
epoll:存放描述符的数据结构定义在内核空间,描述符从用户空间往内核空间只需要copy一次,而select、poll,是每一轮循环都要把描述符从用户空间往内核空间copy一次。
4、数据复制情况分析
(1)select
和poll
- 工作原理:在使用
select
或poll
时,每次调用select
或poll
函数时,都需要将需要监听的文件描述符集合从用户空间复制到内核空间。这是因为select
和poll
函数是通过将文件描述符集合传递给内核,让内核去检查这些描述符上的事件状态。- 复制次数:由于每次调用
select
或poll
函数都需要传递文件描述符集合,因此在每一轮循环中,都需要将描述符从用户空间复制到内核空间。这种频繁的数据复制会带来一定的性能开销,尤其是当需要监听的文件描述符数量较多时,性能问题会更加明显。
(2)epoll
- 工作原理:
epoll
使用epoll_ctl
函数来管理文件描述符,该函数会将文件描述符添加到内核中的红黑树中。红黑树是一种自平衡的二叉搜索树,用于高效地存储和查找文件描述符。一旦文件描述符被添加到红黑树中,就不需要再次复制到内核空间。当有事件发生时,内核会将就绪的文件描述符添加到就绪队列中,应用程序通过epoll_wait
函数从就绪队列中获取就绪的文件描述符。- 复制次数:
epoll
只需要在使用epoll_ctl
函数添加文件描述符时,将描述符从用户空间复制到内核空间一次。后续的epoll_wait
函数调用不需要再次复制描述符,因为内核已经在红黑树中存储了这些描述符。这种设计减少了数据复制的次数,提高了性能。
5、代码示例:
(1)select
函数的简单 TCP 服务器,用于处理多个客户端的连接和数据收发代码:
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<time.h>
#include<sys/select.h>#define MAXFD 10
int socket_init();void fds_init(int fds[])
{for(int i= 0; i < MAXFD ;i++){fds[i] = -1; //-1 reset为空}
}void fds_add(int fds[],int fd)
{