Linux网络:多路转接IO

本期我们接着上一篇,学习IO多路转接的内容

相关内容上传至gitee:楼田莉子/Linux学习

目录

select

作用

[函数表达式(C 语法)](#函数表达式(C 语法))

参数详解

返回值

示例

epoll

作用

函数表达式与参数详解

[(1) epoll_create1 / epoll_create](#(1) epoll_create1 / epoll_create)

[(2) epoll_ctl](#(2) epoll_ctl)

[(3) epoll_wait](#(3) epoll_wait)

水平触发(LT)与边缘触发(ET)的区别

源码


select

select 是传统的IO多路复用系统调用,可同时监视多个文件描述符(socket、普通文件、管道等),等待其中任意一个变为"就绪"状态(可读、可写或异常)。它允许单线程或单进程高效处理数十到数百个并发连接(现代高并发场景一般用 epoll 替代,但 select 仍是理解多路复用的基础)

作用

  • 同时监听多个文件描述符的 可读可写异常 事件。

  • 阻塞直到有描述符就绪、超时或被信号中断。

  • 配合非阻塞IO,可在不占用CPU的前提下实现高并发网络服务。

函数表达式(C 语法)

cpp 复制代码
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

参数详解

  • nfds :需要监视的最大文件描述符值 + 1(即 max_fd + 1)。内核只检查 [0, nfds) 范围内的描述符,提高效率。

  • readfds :指向可读事件的文件描述符集合。如果某个fd在集合中且数据可读(对于socket指接收缓冲区有数据,或连接关闭,或监听socket有新连接),select 返回后该fd在集合中保留,否则会被清除。可设为 nullptr 表示不关心读事件。

  • writefds :指向可写事件的fd集合。对于socket,当发送缓冲区有空闲空间(或非阻塞connect完成)时可写。可设为 nullptr

  • exceptfds :指向异常事件的fd集合。通常用于带外数据(TCP紧急指针)等。可设为 nullptr

  • timeout:最大等待时间。

    • timeout == nullptr:无限阻塞直到有事件。

    • timeout->tv_sec == 0 && timeout->tv_usec == 0:立即返回(非阻塞轮询)。

    • 其他:等待指定时间后即使无事件也返回0。

返回值

  • 成功:返回就绪的文件描述符总数(即三个集合中保留的fd个数)。

  • 超时:返回 0(无就绪fd)。

  • 失败 :返回 -1,并设置 errno(常见:EBADFEINTR 等)。

fd_set 操作宏

  • FD_ZERO(fd_set *set):清空集合

  • FD_SET(int fd, fd_set *set):将fd加入集合

  • FD_CLR(int fd, fd_set *set):将fd从集合中移除

  • FD_ISSET(int fd, fd_set *set):测试fd是否在集合中(通常用于 select 返回后判断哪个fd就绪)

示例

epoll

作用

  • 同时监视大量文件描述符(无上限 ,仅受系统内存限制)的 可读、可写、错误、挂断 等事件。

  • 支持 水平触发(LT,Level Triggered)边缘触发(ET,Edge Triggered) 两种工作模式。

  • 采用事件驱动,每次调用 epoll_wait 仅返回就绪的描述符,复杂度 O(1),非常适合高并发服务器(如 Nginx、Redis)。

函数表达式与参数详解

epoll 包含三个核心函数:

(1) epoll_create1 / epoll_create

cpp 复制代码
#include <sys/epoll.h>
int epoll_create(int size);
int epoll_create1(int flags);
  • 作用:创建一个 epoll 实例,返回一个文件描述符(epfd),后续操作均通过它进行。

  • 参数

    • size(已废弃,但必须 >0):提示内核需要监听的大小,内核会动态扩展。

    • flagsepoll_create1 专用):可取 0EPOLL_CLOEXEC(执行 exec 时自动关闭 epfd)。

  • 返回值 :成功返回 epfd(非负整数);失败返回 -1 并设置 errno(如 ENOMEMEMFILE)。

(2) epoll_ctl

cpp 复制代码
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  • 作用:控制 epoll 实例上的文件描述符事件(添加、修改、删除)。

  • 参数

    • epfdepoll_create1 返回的描述符。

    • op:操作类型,取以下宏:

      • EPOLL_CTL_ADD:添加 fd 到 epoll 实例。

      • EPOLL_CTL_MOD:修改 fd 上监听的事件。

      • EPOLL_CTL_DEL:从 epoll 实例删除 fd

    • fd:要操作的目标文件描述符(socket、普通文件等)。

    • event:指向 struct epoll_event 的指针,描述关心的事件及用户数据。

      cpp 复制代码
      struct epoll_event {
          uint32_t     events;   // 事件掩码(EPOLLIN, EPOLLOUT, EPOLLET...)
          epoll_data_t data;     // 用户数据(通常存 fd 或指针)
      };
      typedef union epoll_data {
          void    *ptr;
          int      fd;
          uint32_t u32;
          uint64_t u64;
      } epoll_data_t;

      常用事件标志

      含义
      EPOLLIN 可读事件(含对端关闭)
      EPOLLOUT 可写事件
      EPOLLRDHUP 对端关闭连接(需内核 2.6.17+)
      EPOLLET 边缘触发模式(默认水平触发)
      EPOLLONESHOT 事件触发一次后自动禁用,需再次重新注册
      EPOLLERR 错误事件(自动监听,无需显式设置)
      EPOLLHUP 挂断事件(自动监听)
  • 返回值 :成功返回 0;失败返回 -1 并设置 errno(如 EBADFEEXISTENOENT 等)。

(3) epoll_wait

cpp 复制代码
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  • 作用:等待 epoll 实例上的事件,返回就绪的事件列表。

  • 参数

    • epfd:epoll 实例描述符。

    • events:输出参数,指向一个 epoll_event 数组,由内核填充就绪的事件。

    • maxeventsevents 数组的大小(最多返回多少个事件)。

    • timeout:超时时间(毫秒)。

      • -1:阻塞直到有事件。

      • 0:立即返回(非阻塞轮询)。

      • >0:等待指定毫秒数。

  • 返回值

    • 成功:返回就绪的文件描述符个数(0 表示超时)。

    • 失败:返回 -1 并设置 errno(如 EBADFEINTR 等)。

水平触发(LT)与边缘触发(ET)的区别

模式 行为 适用场景 注意事项
LT(默认) 只要 fd 还有未处理的数据(如缓冲区非空),每次 epoll_wait 都会返回该 fd。 简单、安全,适合初学者和传统模型。 效率略低于 ET,但不易出错。
ET 仅在 fd 状态发生变化(从无数据变为有数据,或缓冲区从满变为有空闲)时返回一次。 高性能服务,需配合非阻塞 IO 使用。 必须循环读写直到返回 EAGAIN,否则会丢失事件。

源码

cpp 复制代码
#include <iostream>
#include <sys/epoll.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>

int main()
{
    int epfd = epoll_create1(0);
    if (epfd == -1) {
        perror("epoll_create1");
        return 1;
    }

    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = STDIN_FILENO;

    if (epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev) == -1) {
        perror("epoll_ctl");
        close(epfd);
        return 1;
    }

    struct epoll_event events[1];
    std::cout << "epoll on stdin (type something and press Enter, or wait 5s for timeout)..." << std::endl;

    while (true) {
        int nfds = epoll_wait(epfd, events, 1, 5000);
        if (nfds == -1) {
            perror("epoll_wait");
            break;
        }
        if (nfds == 0) {
            std::cout << "Timeout: no data within 5s, polling again..." << std::endl;
            continue;
        }

        if (events[0].events & EPOLLIN) {
            char buf[1024];
            ssize_t n = read(STDIN_FILENO, buf, sizeof(buf) - 1);
            if (n == -1) {
                perror("read");
                break;
            }
            if (n == 0) {
                std::cout << "EOF reached, exiting." << std::endl;
                break;
            }
            buf[n] = '\0';
            std::cout << "Read: " << buf;
        }

        if (events[0].events & (EPOLLERR | EPOLLHUP)) {
            std::cerr << "epoll error/hangup on stdin" << std::endl;
            break;
        }
    }

    close(epfd);
    return 0;
}

结果为:

本期内容到这里就结束了

封面图:

相关推荐
倔强的小石头_1 小时前
密码多了记不住,放云端又怕泄露?我用 NAS 自建了密码保险箱
服务器·password
fengxin_rou1 小时前
MySQL 索引高频面试题全解析:B + 树、联合索引、索引失效
后端·mysql
岳来1 小时前
linux 设备目录/dev 学习
linux·服务器·/dev
红茶要加冰1 小时前
三、条件测试
linux·运维·服务器
宏笋1 小时前
C++ 标准库常用函数(sort, transform, accumulate, reduce等)
c++
图码1 小时前
矩阵中的“对角线强迫症”:如何优雅地判断Toeplitz矩阵?
数据结构·c++·线性代数·算法·青少年编程·矩阵
爱喝水的鱼丶1 小时前
SAP-ABAP:第二篇:实操避坑篇——ABAP Hello World程序创建、语法校验到调试运行全流程指南
运维·服务器·数据库·学习·sap·abap
UXbot1 小时前
AI 原型工具对比(2026):从文字描述到完整 App 界面的 5 款主流平台评测
android·前端·ios·交互·软件构建
jake·tang1 小时前
深度解析 VESC 参数辨识源码:电阻、电感与磁链
arm开发·c++·嵌入式硬件·算法·数学建模·傅立叶分析