linux通用基础

多路io

1. select

  • 最大监听 1024 个 fd(内核宏限制)
  • 每次调用都要把整个 fd 集合从用户态拷贝到内核态
  • 内核通过遍历所有 fd 找就绪的
  • 水平触发(LT)
  • 可移植性最好(几乎所有系统支持)

返回值:

>0:就绪的 fd 总数

0:超时

-1:出错(errno)

使用步骤:

定义 fd_set

FD_ZERO 清空

FD_SET 把需要监听的 fd 加进去

调用 select

用 FD_ISSET 遍历判断哪个就绪

需知道的套配宏

cpp 复制代码
FD_ZERO(&set);      // 清空集合
FD_SET(fd, &set);   // 把 fd 加入监听
FD_CLR(fd, &set);   // 从集合删除 fd
FD_ISSET(fd, &set); // 判断 fd 是否就绪
cpp 复制代码
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

FD_ZERO(fd_set *);
FD_SET(int fd, fd_set *);
FD_ISSET(int fd, fd_set *);
cpp 复制代码
#include <stdio.h>
#include <sys/select.h>
#include <unistd.h>

int main() {
    fd_set rfds;
    struct timeval tv;
    int ret;

    while (1) {
        // 1. 清空集合
        FD_ZERO(&rfds);
        // 2. 添加要监听的 fd
        FD_SET(0, &rfds);

        tv.tv_sec = 1;
        tv.tv_usec = 0;

        // 3. 监听:最大fd+1,读集合,超时
        ret = select(1, &rfds, NULL, NULL, &tv);

        if (ret == -1) {
            perror("select");
            break;
        } else if (ret == 0) {
            printf("timeout...\n");
            continue;
        }

        // 4. 判断是否就绪
        if (FD_ISSET(0, &rfds)) {
            char buf[128];
            read(0, buf, 127);
            printf("select 读到: %s", buf);
        }
    }
    return 0;
}

2. poll

  • 没有 1024 数量限制
  • 原理和 select 几乎一样:仍遍历、仍全量拷贝
  • 只是数据结构换成链表,去掉上限
  • 水平触发(LT)

返回值:

>0:就绪 fd 数量

0:超时

-1:错误

使用步骤:

定义 struct pollfd 数组

填入 fd 和 events

调用 poll

遍历数组,判断 revents & POLLIN 等

cpp 复制代码
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

struct pollfd {
    int fd;
    short events;  // POLLIN/POLLOUT
    short revents; // 内核返回
};
cpp 复制代码
#include <stdio.h>
#include <poll.h>
#include <unistd.h>

int main() {
    struct pollfd fds[1];
    fds[0].fd = 0;
    fds[0].events = POLLIN; // 监听可读

    while (1) {
        // 监听 1 个 fd,超时 1000ms
        int ret = poll(fds, 1, 1000);

        if (ret == -1) {
            perror("poll");
            break;
        } else if (ret == 0) {
            printf("timeout...\n");
            continue;
        }

        // 判断返回事件
        if (fds[0].revents & POLLIN) {
            char buf[128];
            read(0, buf, 127);
            printf("poll 读到: %s", buf);
        }
    }
    return 0;
}

3. epoll(高性能王者)

  • Linux 特有
  • 三个 API:epoll_create / epoll_ctl / epoll_wait
  • 内核使用红黑树 管理 fd,只在添加 / 删除时拷贝一次
  • 用回调机制 ,只返回就绪的 fd,不用遍历
  • 支持 LT(水平) + ET(边缘) 两种触发方式
  • 高并发下性能碾压 select/poll

返回值:

>0:就绪 fd 个数

0:超时

-1:错误

使用步骤:

epoll_create 创建句柄

构建 epoll_event,调用 epoll_ctl(ADD) 加入 fd

循环 epoll_wait 等待事件

直接遍历返回的就绪数组,不用全扫

cpp 复制代码
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
cpp 复制代码
#include <stdio.h>
#include <sys/epoll.h>
#include <unistd.h>

int main() {
    int epfd = epoll_create(1);

    struct epoll_event ev, events[1];
    ev.events = EPOLLIN;
    ev.data.fd = 0;

    // 添加监听
    epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &ev);

    while (1) {
        // 等待事件,最多返回 1 个,超时 1000ms
        int n = epoll_wait(epfd, events, 1, 1000);

        if (n == -1) {
            perror("epoll_wait");
            break;
        } else if (n == 0) {
            printf("timeout...\n");
            continue;
        }

        if (events[0].data.fd == 0) {
            char buf[128];
            read(0, buf, 127);
            printf("epoll 读到: %s", buf);
        }
    }
    return 0;
}

4、性能对比

特性 select poll epoll
监听上限 1024 无 无
遍历方式 遍历全部 fd 遍历全部 fd 只返回就绪 fd
数据拷贝 每次都全量拷贝 每次都全量拷贝 仅增删时拷贝
效率 O(n) O(n) O(1)
平台 全平台 全平台 Linux 专属
触发方式 LT LT LT + ET