什么是IO多路复用/ select、 poll、 epoll使用

IO多路复用(IO multiplexing)是一种通过单一的系统调用来监视多个文件描述符(通常是套接字),以确定是否有数据可读或可写的技术。它允许单个线程处理多个IO操作,提高了程序的并发性能和效率。

select 函数

  • select 函数是比较早期的多路复用技术,在多个文件描述符上等待数据可读、可写或异常情况。
    #include <iostream>
    #include <vector>
    #include <sys/select.h>
    #include <unistd.h>
    
    int main() {
        fd_set read_fds;
        struct timeval timeout;
    
        // Initialize file descriptor set and timeout
        FD_ZERO(&read_fds);
        FD_SET(STDIN_FILENO, &read_fds);
    
        timeout.tv_sec = 5;
        timeout.tv_usec = 0;
    
        // Wait for input on stdin or timeout
        int ret = select(STDIN_FILENO + 1, &read_fds, NULL, NULL, &timeout);
        if (ret == -1) {
            perror("select");
            return 1;
        } else if (ret == 0) {
            std::cout << "Timeout occurred!\n";
        } else {
            if (FD_ISSET(STDIN_FILENO, &read_fds)) {
                std::cout << "Data is available on stdin!\n";
            }
        }
    
        return 0;
    }
    

    程序通过 select 函数监听标准输入(stdin),如果5秒内有输入则打印消息,否则打印超时消息

  • poll 函数

  • poll 函数与 select 类似,但是提供了更灵活的事件管理和更高的性能
    #include <iostream>
    #include <vector>
    #include <poll.h>
    #include <unistd.h>
    
    int main() {
        struct pollfd fds[1];
        int timeout = 5000;  // timeout in milliseconds
    
        // Initialize pollfd structure
        fds[0].fd = STDIN_FILENO;
        fds[0].events = POLLIN;
    
        // Wait for input on stdin or timeout
        int ret = poll(fds, 1, timeout);
        if (ret == -1) {
            perror("poll");
            return 1;
        } else if (ret == 0) {
            std::cout << "Timeout occurred!\n";
        } else {
            if (fds[0].revents & POLLIN) {
                std::cout << "Data is available on stdin!\n";
            }
        }
    
        return 0;
    }
    

    epoll 函数

  • epoll 是Linux特有的IO多路复用机制,性能比 selectpoll 更好,特别是在处理大量并发连接时效果显著。
    #include <iostream>
    #include <sys/epoll.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <cstring>
    
    int main() {
        int epoll_fd = epoll_create1(0);
        if (epoll_fd == -1) {
            perror("epoll_create1");
            return 1;
        }
    
        struct epoll_event event;
        event.events = EPOLLIN;
        event.data.fd = STDIN_FILENO;
    
        if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &event) == -1) {
            perror("epoll_ctl");
            close(epoll_fd);
            return 1;
        }
    
        struct epoll_event events[1];
        int timeout = 5000;  // timeout in milliseconds
    
        int ret = epoll_wait(epoll_fd, events, 1, timeout);
        if (ret == -1) {
            perror("epoll_wait");
            close(epoll_fd);
            return 1;
        } else if (ret == 0) {
            std::cout << "Timeout occurred!\n";
        } else {
            if (events[0].events & EPOLLIN) {
                std::cout << "Data is available on stdin!\n";
            }
        }
    
        close(epoll_fd);
        return 0;
    }
    

    当使用 epoll 函数来实现IO多路复用时,需要按照以下步骤进行:

    步骤分析:

  • 创建 epoll 实例

    • 使用 epoll_create1 函数创建一个 epoll 实例,它会返回一个文件描述符,该描述符用于后续的 epoll 操作。
      int epoll_fd = epoll_create1(0);
      if (epoll_fd == -1) {
          perror("epoll_create1");
          return 1;
      }
      
  • 注册文件描述符

    • 创建一个 struct epoll_event 结构体实例,用于指定需要监听的事件类型和与之关联的文件描述符。这里我们关注的是读事件 EPOLLIN,即数据可读。
      struct epoll_event event;
      event.events = EPOLLIN;  // 监听可读事件
      event.data.fd = STDIN_FILENO;  // 监听标准输入的文件描述符
      
  • 将文件描述符添加到 epoll 实例中

    • 使用 epoll_ctl 函数将要监听的文件描述符注册到 epoll 实例中。
      • epoll_ctl 的第一个参数是 epoll 实例的文件描述符,第二个参数是操作类型(这里是添加),第三个参数是要添加或修改的文件描述符,第四个参数是一个指向 epoll_event 结构体的指针,用于指定事件类型和数据。
        if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &event) == -1) {
            perror("epoll_ctl");
            close(epoll_fd);
            return 1;
        }
        
  • 等待事件发生

    • 创建一个数组用于存放事件的结构体,并调用 epoll_wait 函数等待事件发生。
      • epoll_wait 的第一个参数是 epoll 实例的文件描述符,第二个参数是一个数组,用于存放发生事件的结构体,第三个参数是数组的大小,即最多处理的事件数,第四个参数是超时时间。
        struct epoll_event events[1];  // 这里指定监听一个事件
        int timeout = 5000;  // 超时时间,单位是毫秒
        
        int ret = epoll_wait(epoll_fd, events, 1, timeout);
        if (ret == -1) {
            perror("epoll_wait");
            close(epoll_fd);
            return 1;
        } else if (ret == 0) {
            std::cout << "Timeout occurred!\n";
        } else {
            // 处理事件
            if (events[0].events & EPOLLIN) {
                std::cout << "Data is available on stdin!\n";
            }
        }
        
  • 处理事件

    • epoll_wait 返回后,检查事件结构体数组中的事件类型。这里主要关注 EPOLLIN 事件,表示有数据可读。
      if (events[0].events & EPOLLIN) {
          std::cout << "Data is available on stdin!\n";
          // 在这里可以读取标准输入的数据,进行相应处理
      }
      
  • 关闭 epoll 实例

    • 使用 close 函数关闭 epoll 实例的文件描述符,释放资源。
      close(epoll_fd);
      

注意:

在 epoll 中,可以通过设置 EPOLLONESHOT 来指定 ET 触发方式,否则默认是 LT 触发方式。

  • Edge Triggered (ET) 触发方式:

    • 使用 EPOLLONESHOT 标志将事件设置为 ET 模式。
    • ET 模式下,当文件描述符上有事件发生时,只会通知一次,直到下一次调用 epoll_wait 时才会再次通知。
    • 为了继续监听该文件描述符上的事件,需要重新使用 epoll_ctl 添加该文件描述符到 epoll 实例中。
  • Level Triggered (LT) 触发方式:

    • LT 是 epoll 的默认模式,不需要额外的标志。
    • LT 模式下,当文件描述符上有事件发生时,如果事件没有被处理完,下次调用 epoll_wait 仍然会通知。

在示例中,如果希望使用 ET 模式,可以在使用 epoll_ctl 函数注册文件描述符时,添加 EPOLLONESHOT 标志。

event.events = EPOLLIN | EPOLLONESHOT;  // 设置 ET 模式
event.data.fd = STDIN_FILENO;

 然后,在处理完事件后,需要重新添加文件描述符到 epoll 实例中:

if (events[0].events & EPOLLIN) {
    std::cout << "Data is available on stdin!\n";

    // 处理事件后,重新添加文件描述符到 epoll 实例
    event.events = EPOLLIN | EPOLLONESHOT;
    event.data.fd = STDIN_FILENO;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, STDIN_FILENO, &event) == -1) {
        perror("epoll_ctl");
        close(epoll_fd);
        return 1;
    }
}

  • 24
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值