IO 多路复用模型

GlassCatBlog / 2023-08-08 / 原文

IO 多路复用模型

1. select

为了能够完成IO多路复用机制,可选用 select 函数。

nfds 所监听的最大的文件描述符+1(用来限定范围)
fd_set 文件描述符集合
timeout 超时时间

FD_ZERO 清空监听队列,初始化
FD_SET 加入一个 fd 到 fdset 中。
FD_ISSET 判断一个 fd 是否在就绪队列中
FD_CLR 将 fd 从 fdset 中移除

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

void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

select 缺陷

  • fd_set 从用户态拷贝的内核态。
  • fd_set 是一个位图,1024bit, 更改大小困难。
  • 监听集合和就绪集合高度耦合。
  • 大量监听,少量就绪

2. epoll

epoll 内部使用红黑树作监听集合,线性表作就绪集合。

epoll 的数据结构是文件对象,内部由监听集合和就绪集合组成。(内核态)

当就绪后,会将就绪集合拷贝到用户态。

epoll_create :epoll_create() returns a file descriptor referring to the new epoll instance.
epoll_ctl :add, modify, or remove entries in the interest list of the epoll
epoll_wait :The epoll_wait() system call waits for events on the epoll(7) instance referred to by the file descriptor epfd.

epoll_create(int size);

epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// op 就是 option, 不同的操作
// EPOLL_CTL_ADD
// EPOLL_CTL_MOD
// EPOLL_CTL_DEL
//以下是epoll_event的数据结构
struct epoll_event {
    uint32_t      events;  /* Epoll events 事件属性*/ 
    epoll_data_t  data;    /* User data variable 额外信息*/
};

// 四选一的union
union epoll_data {
    void     *ptr;
    int       fd;
    uint32_t  u32;
    uint64_t  u64;
};
typedef union epoll_data  epoll_data_t;

int epoll_wait(int epfd, 
               struct epoll_event *events,
               int maxevents, 
               int timeout);

示例

void test0() {
    int netfd;
    // 1. 创建epoll文件对象
    int epfd = epoll_create(1);
    // 2. 增加监听,设置监听属性
    struct epoll_event event;
    // 事件的读属性, event 可以多次使用
    event.events = EPOLLIN;
    event.data.fd = STDIN_FILENO;
    epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &event);

    event.events = EPOLLIN;
    event.data.fd = netfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, netfd, &event);
    // 3. 等待文件就绪
    struct epoll_event ready_set[10];
    while (1) {
        int ready_num = epoll_wait(epfd, ready_set, 10, -1);
        // 4. 遍历就绪集合
        for (int i = 0; i < ready_num; i++) {
            if (ready_set[i].data.fd == STDIN_FILENO) {
                // ...
			} else if (ready_set[i].data.fd == netfd){
				//...
			}
        }
    }
}