前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >I/O 多路复用:`select`、`poll`、`epoll` 和 `kqueue` 的区别与示例

I/O 多路复用:`select`、`poll`、`epoll` 和 `kqueue` 的区别与示例

作者头像
井九
发布2024-10-12 10:12:40
发布2024-10-12 10:12:40
20500
代码可运行
举报
文章被收录于专栏:四楼没电梯四楼没电梯
运行总次数:0
代码可运行

I/O 多路复用是指在一个线程内同时监控多个文件描述符(File Descriptor, FD),以便高效地处理多个 I/O 事件。在 UNIX/Linux 和 BSD 系统中,selectpollepollkqueue 都是实现 I/O 多路复用的系统调用。它们各有特点,适合不同的应用场景。本文将详细介绍它们的用法、优缺点,并附上相应的代码示例。

1. select

概述

select 是最早的 I/O 多路复用系统调用之一,广泛支持于各类操作系统中。它允许程序同时监视多个文件描述符,但有最大数量的限制(通常是 1024 个文件描述符)。

优点
  • 简单,适用性广泛。
  • 支持几乎所有 UNIX 类操作系统。
缺点
  • 文件描述符数量有限制。
  • 每次调用都需要重新设置文件描述符集合,效率较低。
使用示例
代码语言:javascript
代码运行次数:0
复制
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <unistd.h>

int main() {
    fd_set readfds;
    struct timeval timeout;
    int ret;

    // 初始化文件描述符集合
    FD_ZERO(&readfds);
    FD_SET(STDIN_FILENO, &readfds);

    // 设置超时时间
    timeout.tv_sec = 5;
    timeout.tv_usec = 0;

    printf("Waiting for input, timeout in 5 seconds...\n");
    // 调用 select 函数,监控文件描述符
    ret = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &timeout);

    if (ret == -1) {
        perror("select");
        exit(EXIT_FAILURE);
    } else if (ret == 0) {
        printf("Timeout occurred! No data after 5 seconds.\n");
    } else {
        if (FD_ISSET(STDIN_FILENO, &readfds)) {
            char buffer[1024];
            read(STDIN_FILENO, buffer, sizeof(buffer));
            printf("Data read: %s\n", buffer);
        }
    }

    return 0;
}
解释

在上述代码中,我们使用 select 函数监听标准输入 (STDIN_FILENO) 的可读性。如果用户在 5 秒内没有输入,程序会超时并退出。

2. poll

概述

poll 作为 select 的改进版本,消除了文件描述符数量的限制。它通过一个 pollfd 数组来管理多个文件描述符,解决了 select 的一些局限性。

优点
  • 没有文件描述符数量限制。
  • API 比较简洁,避免了 select 需要重置文件描述符集合的问题。
缺点
  • select 一样,每次调用仍需遍历整个文件描述符集合。
使用示例
代码语言:javascript
代码运行次数:0
复制
#include <stdio.h>
#include <stdlib.h>
#include <poll.h>
#include <unistd.h>

int main() {
    struct pollfd fds[1];
    int ret;

    // 设置文件描述符和事件
    fds[0].fd = STDIN_FILENO;
    fds[0].events = POLLIN;

    printf("Waiting for input, timeout in 5 seconds...\n");
    // 调用 poll 函数,设置 5 秒超时
    ret = poll(fds, 1, 5000);

    if (ret == -1) {
        perror("poll");
        exit(EXIT_FAILURE);
    } else if (ret == 0) {
        printf("Timeout occurred! No data after 5 seconds.\n");
    } else {
        if (fds[0].revents & POLLIN) {
            char buffer[1024];
            read(STDIN_FILENO, buffer, sizeof(buffer));
            printf("Data read: %s\n", buffer);
        }
    }

    return 0;
}
解释

这里的代码使用 poll 来监控标准输入的可读性。与 select 类似,它设置了一个 5 秒的超时时间,但使用 poll 可以处理更多的文件描述符。

3. epoll

概述

epoll 是 Linux 特有的系统调用,它专门为处理大量文件描述符而设计,性能远优于 selectpollepoll 使用一个事件通知机制,避免了每次调用时遍历整个文件描述符集合。

优点
  • 高效,适合处理大量文件描述符。
  • 不需要每次遍历整个文件描述符集合。
缺点
  • 仅支持 Linux 系统。
使用示例
代码语言:javascript
代码运行次数:0
复制
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <unistd.h>

int main() {
    int epfd = epoll_create1(0);
    struct epoll_event event, events[1];
    int ret;

    if (epfd == -1) {
        perror("epoll_create1");
        exit(EXIT_FAILURE);
    }

    // 设置事件
    event.events = EPOLLIN;
    event.data.fd = STDIN_FILENO;

    if (epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &event) == -1) {
        perror("epoll_ctl");
        exit(EXIT_FAILURE);
    }

    printf("Waiting for input, timeout in 5 seconds...\n");
    // 调用 epoll_wait,等待事件
    ret = epoll_wait(epfd, events, 1, 5000);

    if (ret == -1) {
        perror("epoll_wait");
        exit(EXIT_FAILURE);
    } else if (ret == 0) {
        printf("Timeout occurred! No data after 5 seconds.\n");
    } else {
        if (events[0].data.fd == STDIN_FILENO) {
            char buffer[1024];
            read(STDIN_FILENO, buffer, sizeof(buffer));
            printf("Data read: %s\n", buffer);
        }
    }

    close(epfd);
    return 0;
}
解释

在这个示例中,我们使用 epoll 来监控标准输入。epoll_create1 创建了一个 epoll 实例,随后通过 epoll_ctl 添加文件描述符。epoll_wait 用来等待事件发生,效率远高于 selectpoll

4. kqueue

概述

kqueue 是 BSD 系统(包括 macOS)中的高效 I/O 事件通知机制。与 epoll 类似,kqueue 使用事件通知的机制来避免每次遍历整个文件描述符集合。

优点
  • 高效,适合处理大量 I/O 事件。
  • 支持 BSD 系统。
缺点
  • 仅支持 BSD 系统(包括 macOS)。
使用示例
代码语言:javascript
代码运行次数:0
复制
#include <stdio.h>
#include <stdlib.h>
#include <sys/event.h>
#include <sys/time.h>
#include <unistd.h>

int main() {
    int kq = kqueue();
    struct kevent change, event;
    int ret;

    if (kq == -1) {
        perror("kqueue");
        exit(EXIT_FAILURE);
    }

    // 设置事件
    EV_SET(&change, STDIN_FILENO, EVFILT_READ, EV_ADD, 0, 0, NULL);

    printf("Waiting for input, timeout in 5 seconds...\n");
    // 调用 kevent,设置 5 秒超时
    struct timespec timeout = {5, 0};
    ret = kevent(kq, &change, 1, &event, 1, &timeout);

    if (ret == -1) {
        perror("kevent");
        exit(EXIT_FAILURE);
    } else if (ret == 0) {
        printf("Timeout occurred! No data after 5 seconds.\n");
    } else {
        if (event.ident == STDIN_FILENO) {
            char buffer[1024];
            read(STDIN_FILENO, buffer, sizeof(buffer));
            printf("Data read: %s\n", buffer);
        }
    }

    close(kq);
    return 0;
}
解释

在 BSD 系统中,kqueue 提供了一种高效的 I/O 事件通知机制。该代码监控标准输入,超时时间为 5 秒,使用 kevent 等待事件发生。

5. 总结

特性

select

poll

epoll

kqueue

支持的平台

Unix/Linux/BSD

Unix/Linux/BSD

Linux

BSD/macOS

文件描述符限制

有限制(1024)

无限制

无限制

无限制

效率

较低

较低

扩展性

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-10-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. select
    • 概述
    • 优点
    • 缺点
    • 使用示例
    • 解释
  • 2. poll
    • 概述
    • 优点
    • 缺点
    • 使用示例
    • 解释
  • 3. epoll
    • 概述
    • 优点
    • 缺点
    • 使用示例
    • 解释
  • 4. kqueue
    • 概述
    • 优点
    • 缺点
    • 使用示例
    • 解释
  • 5. 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档