Bootstrap

什么是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);
      
;