linux下的多路复用io(select,poll,epoll)

多路复用 I/O 是在网络编程中处理多个文件描述符的常用技术,允许程序在一个线程内同时监控多个 I/O 事件(如读、写或异常),而不必为每个 I/O 操作创建一个线程。`select`、`poll` 和 `epoll` 是 Linux 中常见的多路复用 I/O 机制,它们在功能上相似,但在性能和使用方式上有所不同。

1. `select`
`select` 是最早实现的多路复用 I/O 机制,几乎在所有操作系统上都能找到它的实现。

工作原理
- `select` 通过一个文件描述符集合来监视多个文件描述符的 I/O 状态。当一个或多个文件描述符的状态发生变化(例如可读、可写或出现异常),`select` 返回这些文件描述符。

优点
- 简单易用,几乎在所有平台都可用。
- 适用于文件描述符数量较少的情况。

缺点
- 文件描述符限制: 每个进程能够监视的文件描述符数量有限制,通常是1024。对于高并发场景,这是一个瓶颈。
- 性能问题: `select` 每次调用都需要线性扫描整个文件描述符集合,随着文件描述符数量增加,性能显著下降。
- 修改集合: `select` 调用后,集合会被内核修改,因此每次都需要重新初始化集合。

2. `poll`
`poll` 是 `select` 的增强版,克服了 `select` 的一些局限性,但依然存在一些性能问题。

工作原理
- `poll` 使用一个由 `pollfd` 结构体组成的数组来监视多个文件描述符的 I/O 状态。与 `select` 类似,`poll` 也会返回状态变化的文件描述符。

优点
- 无文件描述符限制: `poll` 通过数组来监视文件描述符,没有 `select` 的1024个文件描述符的限制。
- 统一的接口: `poll` 的接口相比 `select` 更加灵活统一,容易扩展。

缺点
- 性能问题: 虽然 `poll` 没有文件描述符数量限制,但它的实现依然需要线性扫描整个 `pollfd` 数组,性能在高并发下依然不理想。
- 修改集合: 与 `select` 类似,每次调用 `poll` 后需要重新初始化结构体数组。

3. `epoll`
`epoll` 是 Linux 特有的多路复用 I/O 机制,设计上更加高效,特别适合处理大量并发连接。

工作原理
- `epoll` 使用一个事件驱动机制,通过一个文件描述符管理多个文件描述符。当被监视的文件描述符上有事件发生时,`epoll` 会将这些事件通知用户空间。
- `epoll` 提供两种工作模式:
  - 水平触发(Level-triggered, LT)**: 与 `select` 和 `poll` 类似,当文件描述符有事件发生时,`epoll_wait` 会持续返回该事件,直到处理完为止。
  - 边缘触发(Edge-triggered, ET)**: 当文件描述符的状态从未准备好到准备好时触发事件,仅通知一次。这种模式下,程序需要立即处理 I/O 事件,否则不会再收到通知。

优点
- 高效: `epoll` 内部使用红黑树和双向链表管理文件描述符,避免了每次调用时扫描整个文件描述符集合,大大提高了性能。
- **无文件描述符限制**: `epoll` 可以监视的文件描述符数量仅受系统内存限制。
- **持续监视**: 使用 `epoll_ctl` 注册的文件描述符可以持续被监视,不需要像 `select` 和 `poll` 每次都重新初始化。

缺点
- 复杂性: `epoll` 的使用相对复杂,特别是边缘触发模式,需要开发者更加细致地处理 I/O 事件。

适用场景总结

- `select`: 适用于文件描述符数量较少,平台通用性要求较高的场景。由于其在高并发下的性能问题,通常不适合处理大量并发连接。
  
- `poll`: 比 `select` 更灵活,无文件描述符数量限制,适合稍大规模的并发场景。仍然不适合非常高的并发。

- `epoll`: 适用于 Linux 平台下的高并发网络服务器,能够高效处理大量并发连接,特别是当文件描述符数量很大时,`epoll` 的性能优势非常明显。

下面是 `select`、`poll` 和 `epoll` 的函数组成及其参数解释。

1. `select`

函数原型

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
 

参数解释

- `nfds`: 
  - 整数类型,表示监听的文件描述符范围。通常为所监视的文件描述符中最大值加1。
  
- `readfds`: 
  - 指向 `fd_set` 类型的指针,指定要监视可读性的文件描述符集合。如果不关注某类文件描述符,可以传递 `NULL`。

- `writefds`: 
  - 指向 `fd_set` 类型的指针,指定要监视可写性的文件描述符集合。如果不关注某类文件描述符,可以传递 `NULL`。

- `exceptfds`: 
  - 指向 `fd_set` 类型的指针,指定要监视异常情况的文件描述符集合。如果不关注某类文件描述符,可以传递 `NULL`。

- `timeout`: 
  - 指向 `timeval` 结构体的指针,用于设置超时时间。如果 `timeout` 为 `NULL`,则 `select` 将无限期阻塞,直到有文件描述符就绪。如果 `timeout` 的 `tv_sec` 和 `tv_usec` 都为 `0`,则 `select` 是非阻塞的。

返回值
- 成功时返回就绪文件描述符的数量,0 表示超时,-1 表示出错。

2. `poll`

函数原型

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
 

参数解释

- `fds`: 
  - 指向 `pollfd` 结构体数组的指针,`pollfd` 结构体定义如下:

    struct pollfd {
        int   fd;        // 监视的文件描述符
        short events;    // 需要监视的事件
        short revents;   // 实际发生的事件
    };

  - `fd`: 文件描述符。
  - `events`: 监视的事件,如 `POLLIN`(可读)、`POLLOUT`(可写)、`POLLERR`(错误)等。
  - `revents`: `poll` 返回后实际发生的事件。

- `nfds`: 
  - 监视的文件描述符的数量,即 `fds` 数组的大小。

- `timeout`: 
  - 以毫秒为单位的超时时间。如果 `timeout` 为负数,则 `poll` 将无限期阻塞;如果为 0,则 `poll` 是非阻塞的。

返回值
- 成功时返回就绪文件描述符的数量,0 表示超时,-1 表示出错。

3. `epoll`

`epoll` 的函数主要有三个:`epoll_create`、`epoll_ctl` 和 `epoll_wait`。

1. `epoll_create`

函数原型

int epoll_create(int size);
 

参数解释

- `size`: 
  - 这个参数已经被忽略,但依然是必需的,通常传递大于 0 的值即可。

返回值
- 返回一个 `epoll` 实例的文件描述符,失败时返回 `-1`。

2. `epoll_ctl`

函数原型

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
 

参数解释

- `epfd`: 
  - 由 `epoll_create` 返回的 `epoll` 实例的文件描述符。

- `op`: 
  - 控制操作的类型,有以下几种:
    - `EPOLL_CTL_ADD`: 将文件描述符添加到 `epoll` 实例中。
    - `EPOLL_CTL_MOD`: 修改已经注册的文件描述符上的事件。
    - `EPOLL_CTL_DEL`: 从 `epoll` 实例中删除文件描述符。

- `fd`: 
  - 要监视的文件描述符。

- `event`: 
  - 指向 `epoll_event` 结构体的指针,`epoll_event` 结构体定义如下:

    struct epoll_event {
        uint32_t events;    // 需要监视的事件,如 EPOLLIN, EPOLLOUT 等
        epoll_data_t data;  // 用户数据
    };

  - `events`: 需要监视的事件,如 `EPOLLIN`(可读)、`EPOLLOUT`(可写)、`EPOLLERR`(错误)、`EPOLLET`(边缘触发)等。
  - `data`: 用户自定义的数据,通常用于保存文件描述符。

返回值
- 成功时返回 0,失败时返回 `-1`。

3. `epoll_wait`

函数原型
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
 

参数解释

- `epfd`: 
  - 由 `epoll_create` 返回的 `epoll` 实例的文件描述符。

- `events`: 
  - 指向 `epoll_event` 结构体数组的指针,用于保存发生事件的文件描述符信息。

- `maxevents`: 
  - `events` 数组的大小,即一次调用最多返回的事件数量。

- `timeout`: 
  - 超时时间,单位为毫秒。如果 `timeout` 为 `-1`,则 `epoll_wait` 将无限期阻塞;如果为 `0`,则 `epoll_wait` 是非阻塞的。

返回值
- 返回就绪事件的数量,0 表示超时,-1 表示出错。

你可能感兴趣的:(服务器,数据库,运维)