深入拆解 Linux Socket 五种 I/O 模型:从底层机制到性能适配

110 阅读1小时+

深入拆解 Linux Socket 五种 I/O 模型

引言:I/O 模型对 Linux 网络服务性能的核心影响

在 Linux 网络编程中,I/O 模型的宏观意义是 “数据传输的调度策略”,直接决定服务在高并发场景(如 MMO 游戏多玩家实时走位同步、跨服战场 1000 人同步战斗指令、游戏内限时活动道具领取、玩家组队副本的实时数据交互)下的稳定性与运行效率。若模型选择与业务场景不匹配,易引发三类典型问题:

  • 进程阻塞导致响应超时:大量玩家操作指令因进程等待数据排队,出现角色走位卡顿、技能释放延迟;
  • CPU 空闲却无法处理新请求:进程空等玩家数据时释放 CPU,但模型不支持多任务并行,导致部分玩家请求被 “遗漏”,出现 “操作无响应”;
  • 资源过载崩溃:内存被空等进程占用、CPU 被无效轮询消耗,最终导致游戏服务器宕机,玩家集体掉线。

需先明确:所有 Socket I/O 操作均包含 “等待数据就绪”(内核等待数据满足传输条件)与“数据拷贝”(内核将数据在用户 / 内核空间移动)两个核心阶段,I/O 模型的调度策略正是围绕这两个阶段设计,通过两大维度发挥作用:

  • 进程等待数据的方式:决定进程在 “等待数据就绪” 阶段是否需暂停(阻塞),或可并行处理其他任务(如同时处理多玩家的走位与技能指令),直接影响 CPU 利用率;
  • 数据拷贝的效率:决定 “数据拷贝” 阶段的资源消耗(如 CPU 占用、耗时),直接影响吞吐量与响应延迟(如跨服战斗中指令同步的实时性)。

基于这两个维度,I/O 模型进一步关联三项关键性能指标:

  • 吞吐量:单位时间内可处理的请求数,高效模型(如 I/O 多路复用、异步 I/O)可支持万级甚至十万级并发(如同时承载 5000 人在线的游戏服务器);
  • 响应延迟:从请求发起至结果返回的总时间,核心优化 “等待数据就绪” 阶段可显著降低延迟(如玩家释放技能后,0.1 秒内同步到队友客户端);
  • 资源利用率:避免 “进程空等占 CPU”“内存缓冲区闲置”,提升 CPU 算力、网络带宽、内存的实际使用效率(如减少服务器资源浪费,支持更多玩家同时在线)。

什么是 IO 模型?:从宏观策略到协作机制

IO 模型的本质,是 Linux 内核为用户进程提供的「IO 协作机制」,也是 “数据传输调度策略” 的底层实现 —— 调度策略的性能影响,本质源于协作机制对 “两个核心阶段” 的规则设计。

模型的核心规则维度:协作机制的具体体现

IO 模型通过三个关键规则维度落地协作机制,且直接对应调度策略的两大核心维度(围绕 “两个核心阶段” 展开):

  • 通知规则:用户进程告知内核 “需要执行 IO 操作” 的方式(如 read/write 系统调用、信号注册),是 “数据传输调度” 的起点,决定数据交互的触发效率(如玩家走位指令的快速接收);
  • 反馈规则:内核在 “两个核心阶段”(等待数据就绪、数据拷贝)向用户进程反馈状态的方式(如返回结果码、触发信号),可优化 “进程等待调度”,减少进程无效轮询(如避免服务器反复查询 “是否有新的玩家技能指令”);
  • 阻塞规则:“两个核心阶段” 中,用户进程是否暂停执行(阻塞)并释放 CPU,是 “进程等待调度” 的核心,直接影响 CPU 利用率(如阻塞时可腾出 CPU 处理其他玩家的组队请求)。

Linux 经典 IO 模型分类:协作机制的不同实现

Linux 系统中,遵循 POSIX 标准的经典 IO 模型共 5 种,是协作机制的不同实现版本,核心差异体现在对 “两个核心阶段” 的规则设计(直接决定调度策略的性能表现):

  • 阻塞 IO(Blocking IO) :两个阶段均阻塞,通知规则为 “系统调用触发”,反馈规则为 “拷贝完成后返回结果”;
  • 非阻塞 IO(Non-Blocking IO) :等待数据就绪阶段不阻塞、数据拷贝阶段阻塞,通知规则为 “轮询式系统调用”,反馈规则为 “未就绪时返回错误码”;
  • IO 多路复用(IO Multiplexing) :两个阶段均阻塞(通过单进程监听多 IO 事件优化并发),通知规则为 “注册 IO 事件”,反馈规则为 “有事件就绪时返回”;
  • 信号驱动 IO(Signal-Driven IO) :等待数据就绪阶段不阻塞、数据拷贝阶段阻塞,通知规则为 “注册信号”,反馈规则为 “就绪时触发信号”;
  • 异步 IO(Asynchronous IO) :两个阶段均不阻塞,通知规则为 “异步系统调用”,反馈规则为 “拷贝完成后触发信号”。

规则差异与性能的关联:回归宏观调度策略

五种模型的规则差异,本质是对 “两个核心阶段” 的处理方式不同,直接决定调度策略的性能表现与适用场景:

  • 阻塞 IO 因 “两个阶段均阻塞”,调度效率低,仅适用于低并发、对延迟不敏感的场景(如单人离线游戏的本地数据存档读取);
  • 非阻塞 IO 因 “等待阶段不阻塞但需轮询”,轮询会持续占用 CPU 资源(无数据时仍需频繁调用系统调用),仅适用于 “数据就绪频率高、单次数据量小” 的场景(如游戏中玩家高频微操作指令监听,开发中较少单独使用,需搭配线程池缓解 CPU 消耗);
  • IO 多路复用因 “单进程监听多 IO 事件”,并发调度能力强,无需为每个请求创建进程 / 线程,适用于高并发服务(如支持 5000 人同时在线的 MMO 游戏服务器);
  • 信号驱动 IO 因 “等待阶段靠信号通知”,虽避免轮询,但存在 “信号风暴” 风险(大量数据同时就绪时高频信号打断进程)、信号丢失 / 重入问题,处理逻辑复杂,仅适用于低并发、低延迟的小众场景(如游戏 GM 后台实时指令响应,开发中极少使用);
  • 异步 IO 因 “两个阶段均不阻塞”,内核完成拷贝后再通知进程,资源利用率最高,适用于对延迟敏感、需极致性能的场景(如跨服 1000 人实时战斗的指令同步服务器)。

前置知识:I/O 的本质与核心阶段

前文提到的 “IO 模型协作机制”,其底层逻辑依赖于 I/O 操作的本质(数据在用户空间与内核空间的交互),以及 “两个核心阶段” 的具体流程。理解这部分基础原理,才能更清晰地掌握协作机制中 “通知、反馈、阻塞” 规则的设计依据。

I/O 的本质:用户空间与内核空间的数据交互

Socket 通信的所有 I/O 操作,本质是数据在 “用户空间”(用户进程的专属内存)与 “内核空间”(操作系统内核的管理内存)之间的定向传输—— 这一过程必须由内核主导,应用程序无法直接操作硬件,只能通过系统调用间接触发。这也是 IO 模型协作机制的设计基础:所有 “通知、反馈” 规则,都需围绕 “数据跨空间传输” 的需求展开。

用户空间与内核空间的隔离:内存划分的底层逻辑

现代操作系统通过 “虚拟内存” 机制,将物理内存划分为两个独立区域,既保障系统稳定(避免应用程序破坏内核资源),也为数据传输提供安全边界,同时决定了 “任何 I/O 操作必须经内核中转”:

  • 用户空间(User Space) :应用程序(如游戏服务器进程)的运行区域,权限低,无法直接访问网卡、磁盘等硬件。例如游戏服务器需读取玩家走位指令时,只能通过 read 系统调用请求内核,等待内核将数据拷贝到用户空间的 “用户缓冲区”;
  • 内核空间(Kernel Space) :操作系统内核的运行区域,权限高,可直接操作硬件。例如玩家客户端发送技能指令时,内核先通过网卡驱动接收数据,暂存到内核空间的 “内核缓冲区”,再根据协作机制的规则,决定何时将数据拷贝到用户空间。
数据跨空间移动:I/O 操作的核心流程

所有 I/O 操作(读玩家指令、写战斗日志、接收组队请求)的核心,都是 “数据在两个空间的定向移动”,而这一过程必须通过 “等待数据就绪”、“数据拷贝” 两个阶段完成 —— 这也是 IO 模型协作机制中 “阻塞、反馈” 规则的直接作用对象:

  • 读操作流程:内核先等待网卡接收玩家指令(如技能 ID、走位坐标),待数据存入 “内核缓冲区”(等待数据就绪阶段);再将数据从内核缓冲区拷贝到用户缓冲区(数据拷贝阶段),游戏服务器进程才能读取并处理;
  • 写操作流程:游戏服务器先将处理结果(如战斗伤害值、道具发放通知)写入 “用户缓冲区”;内核等待 “内核缓冲区” 有空闲空间(等待数据就绪阶段),再将数据拷贝到内核缓冲区(数据拷贝阶段),最终通过网卡发送至玩家客户端。

I/O 操作的两个核心阶段:底层流程拆解

“等待数据就绪” 与 “数据拷贝” 是所有 I/O 操作的必经阶段,也是 IO 模型协作机制的核心作用对象 —— 不同模型的规则差异,本质是对这两个阶段的处理方式不同:

“等待数据就绪” 阶段:I/O 操作的前置条件

此阶段由内核独立完成,核心是 “等待数据满足传输条件”,进程需等待该阶段完成后,才能进入 “数据拷贝” 阶段,是 I/O 延迟的主要来源:

  • 读操作场景:内核等待网卡接收玩家数据(如等待玩家释放技能的指令包传输完成),或等待本地文件数据从磁盘读取到内核缓冲区;
  • 写操作场景:内核等待 “内核缓冲区” 释放空闲空间(如前一次发送的玩家状态数据已通过网卡传输,缓冲区腾出空间),或等待磁盘准备好接收写入数据。

以游戏服务器为例:当 1000 名玩家同时在跨服战场释放技能时,内核需逐个等待每个玩家的技能指令包接收完成(等待数据就绪阶段),再根据协作机制的规则,决定是否让服务器进程阻塞等待拷贝。

“数据拷贝” 阶段:资源占用的关键环节

数据就绪后,内核主导数据在两个空间的拷贝,此阶段会占用 CPU 资源(CPU 需持续搬运内存数据),是大数据量场景的性能瓶颈:

  • 读操作场景:内核将 “内核缓冲区” 中的玩家指令数据(如走位坐标)拷贝到 “用户缓冲区”,游戏服务器进程可据此更新玩家在地图中的位置、判定技能命中效果;
  • 写操作场景:内核将 “用户缓冲区” 中的服务器处理结果(如玩家经验值增加、战斗胜利通知)拷贝到 “内核缓冲区”,再通过网卡发送至玩家客户端,完成画面与状态同步。

例如游戏内限时活动开启时,大量玩家同时领取道具,服务器需向每个玩家发送 “道具到账” 通知 —— 此时 “数据拷贝” 阶段的 CPU 占用会显著上升,若 IO 模型的协作机制未优化拷贝效率,可能导致通知发送延迟,出现玩家 “领取成功但道具未到账” 的视觉偏差。

Linux Socket 五种 I/O 模型:回应反射式服务器极简实现

以下将以回声服务器(客户端发送数据,服务器原封不动返回)  为案例,逐一讲解各模型的实现逻辑,配套完整可运行代码,帮助理解 Linux 网络编程的核心思路。

通用客户端代码:所有服务器模型通用

以下客户端可与所有服务器模型通信,用于测试服务器功能(为确保代码简洁易懂,仅保留逻辑核心实现,以下同):

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#define SERVER_IP "127.0.0.1"
#define PORT 5150
#define BUF_SIZE 1024  // 简化缓冲区大小

int main() {
    int client_fd;
    struct sockaddr_in server_addr;
    char buf[BUF_SIZE];
    int ret;

    // 1. 创建TCP套接字
    client_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (client_fd < 0) { perror("socket fail"); return -1; }

    // 2. 初始化服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);

    // 3. 连接服务器
    if (connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("connect fail"); close(client_fd); return -1;
    }
    printf("已连接服务器,输入数据发送(输入'quit'退出):\n");

    // 4. 发送-接收循环
    while (1) {
        printf("客户端发送:");
        fgets(buf, BUF_SIZE, stdin);
        // 去掉换行符
        int len = strlen(buf);
        if (buf[len-1] == '\n') buf[--len] = '\0';
        if (strcmp(buf, "quit") == 0) break;

        // 发送数据
        ret = send(client_fd, buf, len, 0);
        if (ret <= 0) { perror("send fail"); break; }
        printf("发送字节数:%d\n", ret);

        // 接收服务器返回(反射数据)
        memset(buf, 0, BUF_SIZE);
        ret = recv(client_fd, buf, BUF_SIZE, 0);
        if (ret <= 0) { perror("recv fail"); break; }
        printf("服务器返回:%s(接收字节数:%d)\n\n", buf, ret);
    }

    // 5. 清理
    close(client_fd);
    printf("已断开连接\n");
    return 0;
}

编译运行gcc -o client client.c && ./client

客户端的逻辑处理并不复杂,创建套接字,连接服务器,然后便是不停地收发数据。

阻塞 I/O 模型:最简单的模型

阻塞 I/O 是 Linux Socket I/O 中最直观的模型,每个 I/O 操作(连接建立、数据读写)会让进程 / 线程 “停等” 直到操作完成,期间不做任何其他任务。在阻塞 I/O 模型服务器中,典型逻辑是 “主线程阻塞等连接,新线程 / 进程阻塞处理单个客户端”,整体流程简单且易理解。仅适合 “并发连接少、对性能要求不高” 的场景(如单机工具、小流量服务)

核心原理:锚定 I/O 的两个核心阶段:阻塞 I/O 的 “阻塞” 特性贯穿 I/O 操作的全流程,且直接对应 I/O 的两个核心阶段,这是理解其本质的关键:

连接建立阶段:主线程调用 accept() 后进入阻塞态,直到有客户端发起 TCP 连接(三次握手完成),函数才返回新的客户端套接字(client_fd);

数据读写阶段:处理客户端的线程 / 进程调用 recv()/send() 后同样阻塞:

  • 调用 recv() 时,阻塞等待 “内核空间有数据可读”(等待数据就绪阶段),数据就绪后再阻塞等待 “数据从内核空间拷贝到用户空间”(数据拷贝阶段),两阶段完成后才返回读取字节数;
  • 调用 send() 时,阻塞等待 “内核空间有空闲空间”(等待数据就绪阶段),空间就绪后再阻塞等待 “数据从用户空间拷贝到内核空间”(数据拷贝阶段),拷贝完成后才返回发送字节数。

需注意:阻塞时进程 / 线程会主动释放 CPU 资源(进入内核的 “等待队列”),而非占用 CPU 空等;直到 I/O 操作完成,内核才会唤醒进程 / 线程,让其重新抢占 CPU 继续执行。

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <errno.h>  // 用于获取系统错误码(判断异常断开)

#define PORT 5150
#define BUF_SIZE 1024
#define IP_LEN 16  // 足够存储 IPv4 地址(如 "255.255.255.255")

// 线程参数结构体:传递客户端FD、IP、端口
typedef struct {
    int client_fd;          // 客户端套接字
    char client_ip[IP_LEN]; // 客户端IP地址
    int client_port;        // 客户端端口(随机分配)
} ClientParam;

// 处理单个客户端连接(线程函数)
void *handle_client(void *arg) {
    ClientParam *param = (ClientParam *)arg;
    int client_fd = param->client_fd;
    char client_ip[IP_LEN];
    int client_port = param->client_port;

    // 复制客户端IP(避免主线程覆盖地址,线程安全)
    strncpy(client_ip, param->client_ip, IP_LEN - 1);
    client_ip[IP_LEN - 1] = '\0'; // 确保字符串结束符

    free(param); // 释放动态分配的参数内存,避免泄漏
    char buf[BUF_SIZE];
    int ret;

    while (1) {
        // 阻塞接收客户端数据
        memset(buf, 0, BUF_SIZE);
        ret = recv(client_fd, buf, BUF_SIZE, 0);

        if (ret == 0) {
            // 正常断开:ret=0 表示客户端主动调用 close()(如输入quit后退出)
            printf("客户端:%s:%d正常断开\n", client_ip, client_port);
            break;
        } else if (ret < 0) {
            // 异常断开:ret<0 且非中断错误(如强制杀死客户端进程、网络断连)
            // 排除 "系统中断" 错误(EINTR:recv被信号中断,可重试)
            if (errno != EINTR) {
                printf("客户端:%s:%d异常断开\n", client_ip, client_port);
                break;
            }
            continue; // 若为EINTR,重试recv
        }

        // 接收成功,打印数据并反射回传
        printf("接收:%s(%d字节)\n", buf, ret);
        ret = send(client_fd, buf, ret, 0);
        if (ret <= 0) {
            perror("send fail");
            break;
        }
    }

    close(client_fd); // 关闭客户端套接字
    return NULL;
}

int main() {
    int listen_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    pthread_t tid;

    // 1. 创建监听套接字
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd < 0) {
        perror("socket fail");
        return -1;
    }

    // 2. 端口复用 + 绑定地址
    int reuse = 1;
    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
        perror("setsockopt fail");
        close(listen_fd);
        return -1;
    }
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;         // IPv4
    server_addr.sin_port = htons(PORT);       // 端口转网络字节序
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听所有网卡

    if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind fail");
        close(listen_fd);
        return -1;
    }

    // 3. 开始监听(最大等待队列5)
    listen(listen_fd, 5);
    printf("阻塞IO服务器启动:端口=%d\n", PORT);

    // 4. 循环接受客户端连接
    while (1) {
        client_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &client_addr_len);
        if (client_fd < 0) {
            perror("accept fail");
            continue;
        }

        // 获取客户端IP和端口(网络字节序转主机字节序)
        char client_ip[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
        int client_port = ntohs(client_addr.sin_port); // 端口转主机序
        printf("新客户端连接:%s:%d\n", client_ip, client_port);

        // 封装线程参数(传递FD、IP、端口)
        ClientParam *param = (ClientParam *)malloc(sizeof(ClientParam));
        if (param == NULL) {
            perror("malloc fail");
            close(client_fd);
            continue;
        }
        param->client_fd = client_fd;
        strncpy(param->client_ip, client_ip, IP_LEN - 1);
        param->client_ip[IP_LEN - 1] = '\0';
        param->client_port = client_port;

        // 5. 创建子线程处理客户端(分离线程,自动释放资源)
        if (pthread_create(&tid, NULL, handle_client, param) != 0) {
            perror("pthread_create fail");
            close(client_fd);
            free(param); // 创建失败,释放参数内存
        }
        pthread_detach(tid); // 分离线程,避免主线程pthread_join
    }

    close(listen_fd); // 理论上不会执行(循环永久运行)
    return 0;
}

编译运行gcc -o server_block server_block.c -lpthread && ./server_block

非阻塞 IO(Non-Blocking IO)模型:轮询式处理

非阻塞 IO 是为解决 “阻塞 IO 停等浪费” 问题设计的模型,通过主动设置套接字为 “非阻塞模式”,让所有 I/O 操作(连接建立、数据读写)不再 “卡着等结果”,而是无论能否完成都立即返回。若操作暂时无法执行(如无数据可读、无连接可接),会返回特定错误码(EAGAIN/EWOULDBLOCK),由程序通过 “定期轮询” 重试检查状态;期间可处理其他任务,无需空等。适配 “对响应时间敏感、需在等待 I/O 时并行处理其他逻辑” 的场景(如实时监控、高频交互工具),但轮询会额外消耗 CPU 资源。

核心原理:用 “立即返回 + 轮询” 替代 “阻塞等待”

非阻塞模式开启:通过 fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) 给套接字(Socket)添加 “非阻塞标志(O_NONBLOCK)”,此后,该套接字的所有 I/O 操作(accept/recv/send)都会遵循非阻塞规则,是核心前提;

连接建立阶段:主线程调用 accept() 后不再阻塞等待,若暂无客户端连接,accept() 不阻塞,立即返回 -1, errno 设为 EAGAIN 或 EWOULDBLOCK(意为 “当前无连接可接,稍后再试”),否则正常执行后续处理,此外程序需 “轮询” 重试(如循环调用 accept(),或搭配定时器间隔重试),以至获取到有效 client_fd

数据读写阶段:处理客户端的线程 / 进程调用 recv()/send() 后同样立即返回,仅在 “数据拷贝阶段” 短暂阻塞:

  • 调用 recv() 时,等待数据就绪阶段(非阻塞),若内核空间无数据可读,recv() 立即返回 -1errno 设为 EAGAIN/EWOULDBLOCK;若有数据可读,进入数据拷贝阶段(阻塞),阻塞等待 “数据从内核空间拷贝到用户空间”(内存到内存操作,微秒级,耗时极短),拷贝完成后返回读取的字节数;
  • 调用 send() 时,等待空间就绪阶段(非阻塞),若内核发送缓冲区已满(无空闲空间),send() 立即返回 -1errno 设为 EAGAIN/EWOULDBLOCK;若有空闲空间,进入数据拷贝阶段(阻塞),阻塞等待 “数据从用户缓冲区拷贝到内核发送缓冲区”,拷贝完成后返回发送的字节数。

需注意关键差异(对比阻塞 IO):非阻塞 IO 的 “轮询” 是主动行为,程序需反复调用 I/O 函数检查状态(即使无操作可执行),会持续占用 CPU 资源(阻塞 IO 阻塞时会释放 CPU,进入内核等待队列),需设计合理的轮询间隔;此外阻塞 IO 由内核唤醒进程 / 线程,非阻塞 IO 需程序自己处理 “EAGAIN/EWOULDBLOCK” 错误。

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <arpa/inet.h> 

#define PORT 5150
#define BUF_SIZE 1024
#define MAX_CLIENTS 10  // 最大客户端数量
#define IP_LEN 16       // 足够存储IPv4地址

// 客户端参数结构体(包含FD、IP、端口)
typedef struct {
    int client_fd;
    char client_ip[IP_LEN];
    int client_port;
} ClientParam;

// 设置套接字为非阻塞模式
void set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

// 处理单个客户端(非阻塞IO轮询)
void *handle_client(void *arg) {
    ClientParam *param = (ClientParam*)arg;
    int client_fd = param->client_fd;
    char client_ip[IP_LEN];
    int client_port = param->client_port;
    
    // 复制IP地址确保线程安全
    strncpy(client_ip, param->client_ip, IP_LEN - 1);
    client_ip[IP_LEN - 1] = '\0';
    free(param);
    
    char buf[BUF_SIZE];
    int ret;
    
    // 设置客户端套接字为非阻塞
    set_nonblocking(client_fd);

    printf("开始处理客户端:%s:%d\n", client_ip, client_port);
    
    while (1) {
        // 非阻塞接收数据(等待数据阶段不阻塞)
        memset(buf, 0, BUF_SIZE);
        ret = recv(client_fd, buf, BUF_SIZE, 0);
        
        if (ret > 0) {
            // 接收信息
            printf("接收:%s(%d字节)\n", buf, ret);
            send(client_fd, buf, ret, 0);
        }
        else if (ret == 0) {
            // 正常断开提示
            printf("客户端:%s:%d正常断开\n", client_ip, client_port);
            break;
        }
        else {
            // 异常断开提示
            if (errno != EAGAIN && errno != EWOULDBLOCK) {
                printf("客户端:%s:%d异常断开\n", client_ip, client_port);
                break;
            }
            // 没有数据,短暂休眠后继续轮询
            usleep(10000);  // 10ms
        }
    }
    
    close(client_fd);
    return NULL;
}

int main() {
    int listen_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    pthread_t tid;

    // 创建监听套接字
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd < 0) { perror("socket fail"); return -1; }

    // 设置监听套接字为非阻塞
    set_nonblocking(listen_fd);

    // 端口复用+绑定
    int reuse = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind fail"); close(listen_fd); return -1;
    }

    // 监听
    listen(listen_fd, 5);
    // 启动提示
    printf("非阻塞IO服务器启动:端口=%d\n", PORT);

    // 循环接受客户端连接
    while (1) {
        // 非阻塞接受连接
        client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len);
        
        if (client_fd > 0) {
            // 成功接受连接
            char client_ip[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
            int client_port = ntohs(client_addr.sin_port);
            
            // 新客户端连接提示
            printf("新客户端连接:%s:%d\n", client_ip, client_port);
            
            // 封装客户端参数(包含IP和端口)
            ClientParam *param = (ClientParam*)malloc(sizeof(ClientParam));
            param->client_fd = client_fd;
            strncpy(param->client_ip, client_ip, IP_LEN - 1);
            param->client_ip[IP_LEN - 1] = '\0';
            param->client_port = client_port;
            
            // 创建线程处理客户端
            if (pthread_create(&tid, NULL, handle_client, param) != 0) {
                perror("pthread_create fail");  // 错误提示
                close(client_fd);
                free(param);
            }
            pthread_detach(tid);
        }
        else {
            // 非阻塞错误处理
            if (errno != EAGAIN && errno != EWOULDBLOCK) {
                perror("accept error");  // 错误提示
            }
            // 短暂休眠减少CPU占用
            usleep(10000);  // 10ms
        }
    }

    close(listen_fd);
    return 0;
}

编译运行gcc -o server_nonblock server_nonblock.c -lpthread && ./server_nonblock

IO 多路复用(IO Multiplexing)模型:单线程管理多连接的 “高效模型”

I/O 多路复用的核心是用一个进程 / 线程 “同时监听多个套接字”,内核在多个套接字中 “等待数据就绪”,避免阻塞 I/O 的线程膨胀和非阻塞 I/O 的轮询浪费,是高并发服务的主流选择。Linux 提供 selectpollepoll 三种实现,核心差异在于 “套接字管理方式” 和 “效率”。

select 实现:基于 “位集合” 的监听

select 是最早的 I/O 多路复用接口,核心是用 “二进制位集合”(fd_set)管理待监听的套接字,本质是 “让内核一次性检查多个套接字,有就绪的再通知用户进程”。适配并发连接少或需跨平台的场景。

核心原理:“位集合标记监听 + 内核批量检查 + 用户轮询确认” 的三步流程

管理方式:用 “位集合” 当 “监听清单”

使用 fd_set 结构体存储待监听的文件描述符(Socket FD),每个二进制位索引对应一个FD(如第 4 位对应 FD=4),通过 “位的 0/1 状态” 标记 FD 是否需要监听对应事件。

其中,select 依赖的三个独立的 fd_set 位集合,分别对应的 “读、写、异常” 三类事件需明确:

  • 读事件集合(readfds) :监听 FD 的 “数据可读” 场景(如客户端发送数据、监听套接字有新连接请求、FD 接收缓冲区残留数据);
  • 写事件集合(writefds) :监听 FD 的 “数据可写” 场景(如 FD 发送缓冲区有空闲空间,可写入数据发送给客户端);
  • 异常事件集合(exceptfds) :监听 FD 的 “异常状态” 场景(如客户端连接异常断开、FD 发生底层错误)。

例如,若需监听 “监听套接字(listen_fd)的新连接(读事件)” 和 “客户端套接字(client_fd)的异常”,需将 listen_fd 加入读事件集合,client_fd 加入异常事件集合。

并且,其基于位集合的标准操作流程需依赖系统宏函数,确保监听状态准确:

  • 初始化:调用 FD_ZERO(fd_set *set) 分别清空读、写、异常三个 fd_set,将所有位重置为 0,避免旧监听状态干扰新轮次检查;
  • 加监听:调用 FD_SET(int fd, fd_set *set) 将目标 FD 加入对应事件集合,使该 FD 对应位设为 1。需根据事件类型选择集合,同一 FD 可加入多个集合(如同时监听读和异常);
  • 调用 select:传入 int nfds(待监听 FD 的最大值 + 1)、三个 fd_set 地址、timeout(超时时间),触发内核批量检查(将三个 fd_set 从用户空间完整拷贝到内核空间,遍历 0~nfds-1 范围的 FD,检查其在对应集合中的事件是否就绪,未就绪的 FD 则在其对应集合中的位设为 0,就绪则 FD 的位为 1,再将修改后的 fd_set 拷贝回用户空间,唤醒进程并返回 “就绪 FD 的总数”;
  • 查结果:select 返回后,遍历 0~nfds-1 范围的 FD,调用 FD_ISSET(int fd, fd_set *set) 检查 FD 在对应集合中的位是否为 1(位值为 1 则表示该 FD 就绪,可执行后续操作)。

效率瓶颈:“拷贝 + 轮询” 导致性能随规模下降select 的效率瓶颈集中在两点:

  • “位集合”拷贝开销:用户进程要把整个 fd_set 从用户空间拷贝到内核空间(因为内核要检查这些 FD),FD 越多,拷贝的数据量越大,开销越高;
  • 全量轮询确认开销 :内核仅反馈 “有 FD 就绪”,但不明确具体 FD 列表。用户进程必须从 0 遍历到 “当前最大 FD”,逐个用 FD_ISSET 检查,哪怕只有 1 个 FD 就绪,也要轮询所有位,时间复杂度为 O (n)。

硬限制:默认最多监听 1024 个 FDselect 的最大 FD 数量由系统宏 FD_SETSIZE 固定(默认 1024),且该值在编译时确定(在 <sys/select.h> 中)。若需突破限制(如监听 2048 个 FD),需重新编译内核修改宏值,鉴于该操作会影响系统稳定性,且不符合生产环境运维规范,因此 select 天然无法适配万级以上高并发场景。

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/select.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <stdbool.h>  // 用于bool类型

#define PORT 5150
#define BUF_SIZE 1024
#define MAX_FD 1024   // select支持的最大文件描述符数量
#define IP_LEN 16     // IPv4地址存储长度

// 客户端完整信息结构体(整合连接信息与通信状态)
typedef struct {
    bool in_use;           // 标记是否正在使用
    char ip[IP_LEN];       // 客户端IP地址
    int port;              // 客户端端口
    char send_buf[BUF_SIZE];// 待发送数据缓冲区
    int send_len;          // 待发送数据长度
} Client;

// 全局数组:用FD作为索引管理所有客户端
Client clients[MAX_FD];

// 初始化客户端结构体
void init_client(int fd, const char* ip, int port) {
    memset(&clients[fd], 0, sizeof(Client));
    clients[fd].in_use = true;
    strncpy(clients[fd].ip, ip, IP_LEN - 1);
    clients[fd].port = port;
    clients[fd].send_len = 0;
}

// 清理客户端结构体
void cleanup_client(int fd) {
    memset(&clients[fd], 0, sizeof(Client));
    clients[fd].in_use = false;
}

int main() {
    int listen_fd, client_fd, max_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    fd_set read_fds, write_fds, except_fds;
    fd_set tmp_read, tmp_write, tmp_except;
    char recv_buf[BUF_SIZE];
    int ret, i;

    // 初始化客户端数组
    memset(clients, 0, sizeof(clients));
    for (int i = 0; i < MAX_FD; i++) {
        clients[i].in_use = false;
    }

    // 初始化事件集合
    FD_ZERO(&read_fds);
    FD_ZERO(&write_fds);
    FD_ZERO(&except_fds);

    // 创建监听套接字
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd < 0) { perror("socket fail"); return -1; }
    FD_SET(listen_fd, &read_fds);
    FD_SET(listen_fd, &except_fds);
    max_fd = listen_fd;

    // 端口复用+绑定+监听
    int reuse = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    listen(listen_fd, 5);
    printf("select服务器(IO多路复用)启动:端口=%d\n", PORT);

    // select事件循环
    while (1) {
        // 复制事件集合
        tmp_read = read_fds;
        tmp_write = write_fds;
        tmp_except = except_fds;
        
        // 等待事件发生
        ret = select(max_fd + 1, &tmp_read, &tmp_write, &tmp_except, NULL);
        if (ret < 0) { perror("select fail"); continue; }

        // 处理读事件
        for (i = 0; i <= max_fd; i++) {
            if (!FD_ISSET(i, &tmp_read)) continue;

            if (i == listen_fd) {
                // 新客户端连接
                client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len);
                if (client_fd < 0) { perror("accept fail"); continue; }
                if (client_fd >= MAX_FD) {
                    printf("客户端超出select最大限制(%d),拒绝连接\n", MAX_FD);
                    close(client_fd);
                    continue;
                }

                // 记录客户端信息
                char client_ip[INET_ADDRSTRLEN];
                inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
                int client_port = ntohs(client_addr.sin_port);
                init_client(client_fd, client_ip, client_port);

                // 添加到事件集合
                FD_SET(client_fd, &read_fds);
                FD_SET(client_fd, &except_fds);
                if (client_fd > max_fd) max_fd = client_fd;

                // 新连接提示
                printf("新客户端连接:%s:%d\n", client_ip, client_port);
            } else {
                // 客户端发送数据
                if (!clients[i].in_use) continue;

                memset(recv_buf, 0, BUF_SIZE);
                ret = recv(i, recv_buf, BUF_SIZE - 1, 0);
                if (ret <= 0) {
                    // 正常断开提示
                    printf("客户端:%s:%d正常断开\n", 
                           clients[i].ip, clients[i].port);
                    // 清理资源
                    FD_CLR(i, &read_fds);
                    FD_CLR(i, &write_fds);
                    FD_CLR(i, &except_fds);
                    cleanup_client(i);
                    close(i);
                    continue;
                }

                // 接收数据提示
                printf("接收:%s(%d字节)→ 暂存待发送\n", recv_buf, ret);
                strncpy(clients[i].send_buf, recv_buf, ret);
                clients[i].send_len = ret;
                FD_SET(i, &write_fds);
            }
        }

        // 处理写事件
        for (i = 0; i <= max_fd; i++) {
            if (!clients[i].in_use || !FD_ISSET(i, &tmp_write) || clients[i].send_len <= 0) {
                continue;
            }

            ret = send(i, clients[i].send_buf, clients[i].send_len, 0);
            if (ret < 0) {
                perror("send fail");
                FD_CLR(i, &read_fds);
                FD_CLR(i, &write_fds);
                FD_CLR(i, &except_fds);
                cleanup_client(i);
                close(i);
            } else {
                // 发送完成提示
                printf("发送:%s(%d字节)→ 发送完成\n", 
                       clients[i].send_buf, clients[i].send_len);
                clients[i].send_len = 0;
                FD_CLR(i, &write_fds);
            }
        }

        // 处理异常事件
        for (i = 0; i <= max_fd; i++) {
            if (!FD_ISSET(i, &tmp_except) || i == listen_fd || !clients[i].in_use) {
                continue;
            }

            // 异常断开提示
            printf("客户端:%s:%d异常断开\n", 
                   clients[i].ip, clients[i].port);
            // 清理资源
            FD_CLR(i, &read_fds);
            FD_CLR(i, &write_fds);
            FD_CLR(i, &except_fds);
            cleanup_client(i);
            close(i);

            // 更新最大FD
            if (i == max_fd) {
                while (max_fd > 0 && !FD_ISSET(max_fd, &read_fds) && !FD_ISSET(max_fd, &write_fds)) {
                    max_fd--;
                }
            }
        }
    }

    close(listen_fd);
    return 0;
}

编译运行gcc -o server_select server_select.c && ./server_select

poll 实现:基于 “结构体数组” 的监听

poll 是对 select 的优化实现,核心通过 struct pollfd 结构体数组 管理待监听的套接字(FD),解决了 select 中 “位集合(fd_set)的 FD 数量硬限制” 问题,同时保留了 “内核批量检查 + 用户轮询确认” 的核心逻辑,适配中等规模并发场景(如数千连接)。

核心原理:“结构体数组管控 + 内核批量检查 + 用户轮询确认” 的优化流程

管理方式:用 struct pollfd 精准描述监听需求

poll 摒弃了 select 的位集合,改用动态数组存储监听信息,单个 struct pollfd 元素包含三部分核心字段,实现 “一对一” 的 FD 与监听规则映射:

struct pollfd {
    int    fd;      // 待监听的文件描述符(Socket FD,-1 表示该元素无效)
    short  events;  // 期望监听的事件(如 POLLIN=读事件、POLLOUT=写事件,可组合)
    short  revents; // 实际发生的事件(内核填充,用于判断 FD 是否就绪)
};

其中,events 和 revents 支持的常见事件类型需明确:

  • POLLIN:读事件(如客户端发送数据到 Socket、监听套接字有新连接请求、Socket 接收缓冲区有残留数据);
  • POLLOUT:写事件(如 Socket 发送缓冲区有空闲空间,可写入数据发送给客户端);
  • POLLERR:异常事件(如客户端连接异常断开、Socket 发生错误);
  • POLLPRI:紧急数据事件(如 TCP 紧急指针指向的数据到达,较少见)。

例如,若需同时监听某个客户端 FD 的 “读事件” 和 “异常事件”,只需设置 events = POLLIN | POLLERR,内核会同时检查这两类事件。

并且,其基于数组的标准操作流程更灵活,无需依赖宏函数,逻辑更直观:

  • 初始化:创建 struct pollfd 数组,按预期并发规模设置长度(如 struct pollfd fds[5000] 支持 5000 个 FD),若后续连接数超出初始长度,可通过 realloc 动态扩容;
  • 加监听:对需要监听的每个 FD,在数组中找到“无效位置”(fd=-1 的元素),赋值 fd (目标 FD)和 events (监听事件,如 POLLIN | POLLERR,监听读事件和异常事件),无效元素设 fd=-1 即可,内核会自动跳过该元素。
  • 调用 poll :传入数组地址、数组长度、超时时间,内核批量检查所有有效 FD 的事件状态(将struct pollfd数组从用户空间拷贝至内核空间,遍历fd≠-1的元素检查events是否就绪(就绪则写入revents,未就绪则revents为 0);待有 FD 就绪或超时,将数组拷贝回用户空间,唤醒进程并返回就绪 FD 数量);
  • 查结果:poll 返回后,遍历数组检查 revents,当 revents & POLLIN 为真时,表示该 FD 有读事件就绪(比如监听 FD 有新连接到来或客户端 FD 有数据可读取),可执行 accept () 或 recv () 操作;当 revents & POLLOUT为真时,表示该 FD 有写事件就绪(通常是发送缓冲区处于空闲状态),可执行 send () 操作;当 revents & POLLERR 为真时,表示该 FD 发生了异常,此时需要关闭该 FD 并将其标记为 fd=-1。

效率特性:突破硬限制,但仍存轮询开销:poll 相比 select 的核心改进是 解决 FD 数量硬限制,但未完全消除效率瓶颈:

  • 突破数量约束:数组大小仅受系统内存和 FD 总数(ulimit -n)限制,无需修改内核即可支持上万 FD 监听;
  • 减少冗余操作:无需像 select 那样每次调用前重置监听集合(events 字段不被内核修改),也无需跟踪 “最大 FD”(poll 直接用数组长度确定检查范围);
  • 仍有轮询成本:内核仅返回 “就绪 FD 的数量”,不明确具体 FD 列表,用户进程需遍历整个数组确认就绪 FD,时间复杂度仍为 O (n)(n = 数组长度);
  • 拷贝开销优化有限struct pollfd 数组仍需从用户态拷贝到内核态,数组越大(FD 越多),拷贝耗时越长。
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <poll.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <stdbool.h>  // 用于bool类型

#define PORT 5150
#define BUF_SIZE 1024
#define MAX_CONN 2048  // 最大支持连接数
#define IP_LEN 16      // IPv4地址存储长度

// 客户端完整信息结构体(整合连接信息与通信状态)
typedef struct {
    int fd;                  // 客户端文件描述符
    char ip[IP_LEN];         // 客户端IP地址
    int port;                // 客户端端口
    char send_buf[BUF_SIZE]; // 待发送数据缓冲区
    int send_len;            // 待发送数据长度
    bool in_use;             // 标记是否正在使用
} Client;

// 全局管理结构:包含pollfd数组和客户端信息
struct {
    struct pollfd fds[MAX_CONN];
    Client clients[MAX_CONN];
    int count;               // 当前有效连接数
} poll_manager;

// 初始化客户端结构体
void init_client(int idx, int fd, const char* ip, int port) {
    memset(&poll_manager.clients[idx], 0, sizeof(Client));
    poll_manager.clients[idx].fd = fd;
    strncpy(poll_manager.clients[idx].ip, ip, IP_LEN - 1);
    poll_manager.clients[idx].port = port;
    poll_manager.clients[idx].in_use = true;
}

// 添加FD到管理结构
void add_fd(int fd, const char* ip, int port) {
    if (poll_manager.count >= MAX_CONN) {
        printf("连接数已满(%d/%d)\n", poll_manager.count, MAX_CONN);
        close(fd);
        return;
    }
    
    int idx = poll_manager.count;
    poll_manager.fds[idx].fd = fd;
    poll_manager.fds[idx].events = POLLIN;  // 初始关注读事件
    init_client(idx, fd, ip, port);
    poll_manager.count++;
}

// 从管理结构移除FD
void remove_fd(int idx) {
    if (idx < 0 || idx >= poll_manager.count) return;
    
    // 关闭文件描述符
    close(poll_manager.clients[idx].fd);
    
    // 用最后一个元素覆盖当前元素(减少数组移动)
    if (idx != poll_manager.count - 1) {
        poll_manager.fds[idx] = poll_manager.fds[poll_manager.count - 1];
        poll_manager.clients[idx] = poll_manager.clients[poll_manager.count - 1];
    }
    
    poll_manager.count--;  // 有效数量减1
}

int main() {
    int listen_fd, client_fd, ret, i;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    char recv_buf[BUF_SIZE];  // 接收缓冲区

    // 初始化管理结构
    memset(&poll_manager, 0, sizeof(poll_manager));
    poll_manager.count = 0;

    // 创建监听套接字并加入管理结构
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd < 0) { perror("socket fail"); return -1; }
    add_fd(listen_fd, "0.0.0.0", PORT);  // 监听套接字用占位IP和端口

    // 端口复用+绑定+监听
    int reuse = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind fail"); return -1;
    }
    listen(listen_fd, 5);
    printf("poll服务器(IO多路复用)启动:端口=%d,最大连接=%d\n", PORT, MAX_CONN);

    // poll事件循环
    while (1) {
        // 阻塞等待事件(无超时)
        ret = poll(poll_manager.fds, poll_manager.count, -1);
        if (ret < 0) { perror("poll fail"); continue; }

        // 遍历处理就绪事件
        for (i = 0; i < poll_manager.count; i++) {
            // 处理读事件
            if (poll_manager.fds[i].revents & POLLIN) {
                if (poll_manager.clients[i].fd == listen_fd) {
                    // 新客户端连接
                    client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len);
                    if (client_fd < 0) { perror("accept fail"); continue; }
                    
                    // 获取客户端IP和端口
                    char client_ip[INET_ADDRSTRLEN];
                    inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
                    int client_port = ntohs(client_addr.sin_port);
                    
                    // 添加到管理结构
                    add_fd(client_fd, client_ip, client_port);
                    
                    // 新连接提示
                    printf("新客户端连接:%s:%d\n", client_ip, client_port);
                } else {
                    // 客户端发送数据
                    if (!poll_manager.clients[i].in_use) continue;

                    memset(recv_buf, 0, BUF_SIZE);
                    ret = recv(poll_manager.clients[i].fd, recv_buf, BUF_SIZE - 1, 0);
                    if (ret <= 0) {
                        // 正常断开提示
                        printf("客户端:%s:%d正常断开\n", 
                               poll_manager.clients[i].ip, 
                               poll_manager.clients[i].port);
                        remove_fd(i);
                        i--;  // 回退索引避免跳过元素
                        continue;
                    }

                    // 接收数据提示
                    printf("接收:%s(%d字节)→ 暂存待发送\n", recv_buf, ret);
                    // 暂存数据并注册写事件
                    strncpy(poll_manager.clients[i].send_buf, recv_buf, ret);
                    poll_manager.clients[i].send_len = ret;
                    poll_manager.fds[i].events |= POLLOUT;  // 添加写事件
                }
            }
            
            // 处理写事件
            if (poll_manager.fds[i].revents & POLLOUT) {
                Client* client = &poll_manager.clients[i];
                if (!client->in_use || client->send_len <= 0) {
                    poll_manager.fds[i].events &= ~POLLOUT;  // 移除写事件
                    continue;
                }

                // 发送暂存数据
                ret = send(client->fd, client->send_buf, client->send_len, 0);
                if (ret < 0) {
                    perror("send fail");
                    remove_fd(i);
                    i--;
                } else {
                    // 发送完成提示
                    printf("发送:%s(%d字节)→ 发送完成\n", 
                           client->send_buf, client->send_len);
                    client->send_len = 0;
                    poll_manager.fds[i].events &= ~POLLOUT;  // 移除写事件
                }
            }
            
            // 处理异常事件(POLLERR|POLLHUP|POLLNVAL)
            if (poll_manager.fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
                // 异常断开提示
                printf("客户端:%s:%d异常断开\n", 
                       poll_manager.clients[i].ip, 
                       poll_manager.clients[i].port);
                remove_fd(i);
                i--;
            }
        }
    }

    close(listen_fd);
    return 0;
}

编译运行gcc -o server_poll server_poll.c && ./server_poll

epoll 实现:基于 “内核事件表” 的高效监听(含 LT/ET 两种触发模式)

epoll 是 Linux 内核专为高并发场景设计的 IO 多路复用机制,核心优势是  “高效管理大量连接”,相比 select/poll 的全量遍历,epoll 通过内核维护的两个关键数据结构,将 IO 事件检测效率从 O (n) 降至 O (1),是万级以上并发服务的首选方案。

epoll 高效的核心:内核级数据结构设计:epoll 高效处理大量连接的本质是内核封装了两个各司其职的数据结构,由此替代 select/poll 中 “用户态维护 FD 集合” 的低效方式:

  • 红黑树(内核事件表) :用于存储用户主动注册的 “待监听 FD 及对应事件”(如读事件、写事件)。红黑树的特性确保 FD 的添加、删除、查询操作均为 O (log n) 高效执行,解决select/poll中 “修改 FD 集合需全量拷贝” 的开销。
  • 就绪链表:仅存储 “已就绪的 FD”(如 FD 有数据可读、发送缓冲区空闲)。当 FD 状态从 “非就绪” 变为 “就绪” 时,内核会主动将其移入就绪链表;epoll_wait 调用可直接从链表中读取就绪 FD,无需轮询所有 FD,达到“按需返回” 的 O (1) 效率。

epoll 的两种触发模式:核心差异在 “事件通知时机”:epoll 的灵活性体现在支持两种事件触发模式,二者的核心区别是  “内核何时向用户进程通知 FD 就绪”,直接决定了数据处理逻辑的复杂度、容错性和性能,需根据业务场景选择。

  • 水平触发(Level Triggered, LT) :LT 是 epoll 的默认工作模式,只要 FD 处于 “就绪状态”(如可读、可写)时,epoll就会持续向用户进程通知该事件,直到 FD 就绪状态消失。其特点是无需特殊配置(注册事件时不设置 EPOLLET 标志);编码时无需严格保证 “一次性处理完所有数据”,即使“数据未处理完”,epoll 会反复通知,直到数据处理完成;支持阻塞或非阻塞 IO,适配大多数通用场景。
  • 边缘触发(Edge Triggered, ET): ET 模式是 epoll 的高性能模式,仅在 FD 的状态从 “非就绪” 变为 “就绪” 的 “瞬间” 触发一次事件,即使 FD 仍有未处理的数据 / 空间,后续也不会再通知,必须依赖开发者手动确保 “一次性处理完所有就绪数据”。其特点是需通过 EPOLL_IN | EPOLLET 标志启用;由于事件仅通知一次,必须用非阻塞 IO 循环读取 / 写入数据(直到 recv/send 返回 EAGAIN/EWOULDBLOCK,表示 “当前无更多数据 / 空间”),避免阻塞;同一 FD 的同一事件仅触发一次,大幅减少内核空间与用户空间的交互开销,必须循环读写直到返回特定错误码,否则会导致数据丢失,适合对性能要求极高的场景。
水平触发(Level Triggered, LT)
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
#include <stdbool.h>

#define PORT 5150
#define BUF_SIZE 1024
#define MAX_EVENTS 1024
#define MAX_CLIENTS 1024  // 最大客户端数量
#define IP_LEN 16         // IPv4地址存储长度

// 客户端完整信息结构体
typedef struct {
    int fd;                  // 客户端文件描述符
    char ip[IP_LEN];         // 客户端IP地址
    int port;                // 客户端端口
    char send_buf[BUF_SIZE]; // 待发送数据缓冲区
    int send_len;            // 待发送数据长度
    bool in_use;             // 标记是否正在使用
} Client;

// 客户端数组(用FD作为索引)
Client clients[MAX_CLIENTS];

// 设置非阻塞模式
void set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
        perror("fcntl设置非阻塞失败");
        close(fd);
        exit(EXIT_FAILURE);
    }
}

// 初始化客户端结构体
void init_client(int fd, const char* ip, int port) {
    memset(&clients[fd], 0, sizeof(Client));
    clients[fd].fd = fd;
    strncpy(clients[fd].ip, ip, IP_LEN - 1);
    clients[fd].port = port;
    clients[fd].in_use = true;
    clients[fd].send_len = 0;
}

// 清理客户端结构体(通过参数传入epoll_fd,避免全局变量)
void cleanup_client(int epoll_fd, int fd) {
    if (fd < 0 || fd >= MAX_CLIENTS || !clients[fd].in_use) return;
    
    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL);  // 使用参数传入的epoll_fd
    close(fd);
    memset(&clients[fd], 0, sizeof(Client));
    clients[fd].in_use = false;
}

int main() {
    int listen_fd, client_fd, ret, i;
    int epoll_fd;  // epoll实例描述符
    struct epoll_event ev, events[MAX_EVENTS];
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    char recv_buf[BUF_SIZE];

    // 初始化客户端数组
    memset(clients, 0, sizeof(clients));
    for (int i = 0; i < MAX_CLIENTS; i++) {
        clients[i].in_use = false;
    }

    // 创建epoll实例
    if ((epoll_fd = epoll_create1(0)) < 0) {
        perror("epoll_create1失败");
        return -1;
    }

    // 创建监听套接字
    if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket创建失败");
        return -1;
    }
    set_nonblocking(listen_fd);

    // 监听FD加入epoll
    ev.events = EPOLLIN;
    ev.data.fd = listen_fd;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev) < 0) {
        perror("epoll_ctl添加监听FD失败");
        close(listen_fd);
        close(epoll_fd);
        return -1;
    }

    // 端口复用+绑定+监听
    int reuse = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    listen(listen_fd, 5);
    printf("epoll服务器(LT模式)启动:端口=%d,最大连接=%d\n", PORT, MAX_CLIENTS);

    // 事件循环
    while (1) {
        ret = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if (ret < 0) {
            perror("epoll_wait失败");
            continue;
        }

        for (i = 0; i < ret; i++) {
            int fd = events[i].data.fd;

            // 处理读事件
            if (events[i].events & EPOLLIN) {
                if (fd == listen_fd) {
                    // 新客户端连接(LT模式循环处理)
                    while (1) {
                        client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len);
                        if (client_fd < 0) {
                            if (errno != EAGAIN && errno != EWOULDBLOCK) {
                                perror("accept失败");
                            }
                            break;
                        }

                        if (client_fd >= MAX_CLIENTS) {
                            printf("客户端超出最大限制(%d),拒绝连接\n", MAX_CLIENTS);
                            close(client_fd);
                            continue;
                        }

                        // 初始化客户端信息
                        char client_ip[INET_ADDRSTRLEN];
                        inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
                        int client_port = ntohs(client_addr.sin_port);
                        init_client(client_fd, client_ip, client_port);

                        // 设置非阻塞并添加到epoll
                        set_nonblocking(client_fd);
                        ev.events = EPOLLIN;
                        ev.data.fd = client_fd;
                        if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev) < 0) {
                            perror("epoll_ctl添加客户端FD失败");
                            cleanup_client(epoll_fd, client_fd);  // 传入epoll_fd参数
                            continue;
                        }

                        printf("新客户端连接:%s:%d\n", client_ip, client_port);
                    }
                } else {
                    // 客户端发送数据(LT模式循环读取)
                    if (!clients[fd].in_use) continue;

                    while (1) {
                        memset(recv_buf, 0, BUF_SIZE);
                        ret = recv(fd, recv_buf, BUF_SIZE - 1, 0);
                        if (ret < 0) {
                            if (errno != EAGAIN && errno != EWOULDBLOCK) {
                                perror("recv失败");
                                printf("客户端:%s:%d异常断开\n", clients[fd].ip, clients[fd].port);
                                cleanup_client(epoll_fd, fd);  // 传入epoll_fd参数
                            }
                            break;
                        } else if (ret == 0) {
                            // 正常断开
                            printf("客户端:%s:%d正常断开\n", clients[fd].ip, clients[fd].port);
                            cleanup_client(epoll_fd, fd);  // 传入epoll_fd参数
                            break;
                        }

                        // 接收数据处理
                        printf("接收:%s(%d字节)→ 暂存待发送\n", recv_buf, ret);
                        strncpy(clients[fd].send_buf, recv_buf, ret);
                        clients[fd].send_len = ret;
                        // 修改事件,添加写事件
                        ev.events = EPOLLIN | EPOLLOUT;
                        ev.data.fd = fd;
                        epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &ev);
                    }
                }
            }

            // 处理写事件
            if (events[i].events & EPOLLOUT) {
                if (!clients[fd].in_use || clients[fd].send_len <= 0) {
                    // 无数据可发,移除写事件
                    ev.events = EPOLLIN;
                    ev.data.fd = fd;
                    epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &ev);
                    continue;
                }

                // 发送数据
                int sent = send(fd, clients[fd].send_buf, clients[fd].send_len, 0);
                if (sent < 0) {
                    if (errno != EAGAIN && errno != EWOULDBLOCK) {
                        perror("send失败");
                        printf("客户端:%s:%d异常断开\n", clients[fd].ip, clients[fd].port);
                        cleanup_client(epoll_fd, fd);  // 传入epoll_fd参数
                    }
                    continue;
                }

                // 更新发送状态
                clients[fd].send_len -= sent;
                if (clients[fd].send_len == 0) {
                    printf("发送:%s(%d字节)→ 发送完成\n", clients[fd].send_buf, sent);
                    // 移除写事件
                    ev.events = EPOLLIN;
                    ev.data.fd = fd;
                    epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &ev);
                } else {
                    memmove(clients[fd].send_buf, clients[fd].send_buf + sent, clients[fd].send_len);
                }
            }

            // 处理异常事件
            if (events[i].events & (EPOLLERR | EPOLLHUP)) {
                if (clients[fd].in_use) {
                    printf("客户端:%s:%d异常断开\n", clients[fd].ip, clients[fd].port);
                    cleanup_client(epoll_fd, fd);  // 传入epoll_fd参数
                }
            }
        }
    }

    close(listen_fd);
    close(epoll_fd);
    return 0;
}

编译运行gcc -o server_epoll_lt server_epoll_lt.c && ./server_epoll_lt

边缘触发(ET)实现(高性能模式)
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
#include <stdbool.h>

#define PORT 5150
#define BUF_SIZE 1024
#define MAX_EVENTS 1024
#define MAX_CLIENTS 1024  // 最大客户端数量
#define IP_LEN 16         // IPv4地址存储长度

// 客户端完整信息结构体
typedef struct {
    int fd;                  // 客户端文件描述符
    char ip[IP_LEN];         // 客户端IP地址
    int port;                // 客户端端口
    char send_buf[BUF_SIZE]; // 待发送数据缓冲区
    int send_len;            // 待发送数据长度
    bool in_use;             // 标记是否正在使用
} Client;

// 客户端数组(用FD作为索引)
Client clients[MAX_CLIENTS];

// 设置非阻塞模式(ET模式必需)
void set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
        perror("fcntl设置非阻塞失败");
        close(fd);
        exit(EXIT_FAILURE);
    }
}

// 初始化客户端结构体
void init_client(int fd, const char* ip, int port) {
    memset(&clients[fd], 0, sizeof(Client));
    clients[fd].fd = fd;
    strncpy(clients[fd].ip, ip, IP_LEN - 1);
    clients[fd].port = port;
    clients[fd].in_use = true;
    clients[fd].send_len = 0;
}

// 清理客户端结构体
void cleanup_client(int epoll_fd, int fd) {
    if (fd < 0 || fd >= MAX_CLIENTS || !clients[fd].in_use) return;
    
    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL);
    close(fd);
    memset(&clients[fd], 0, sizeof(Client));
    clients[fd].in_use = false;
}

int main() {
    int listen_fd, client_fd, ret, i;
    int epoll_fd;  // epoll实例描述符
    struct epoll_event ev, events[MAX_EVENTS];
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    char recv_buf[BUF_SIZE];

    // 初始化客户端数组
    memset(clients, 0, sizeof(clients));
    for (int i = 0; i < MAX_CLIENTS; i++) {
        clients[i].in_use = false;
    }

    // 创建epoll实例
    if ((epoll_fd = epoll_create1(0)) < 0) {
        perror("epoll_create1失败");
        return -1;
    }

    // 创建监听套接字
    if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket创建失败");
        return -1;
    }
    set_nonblocking(listen_fd);  // ET模式必须设置非阻塞

    // 监听FD加入epoll(ET模式:添加EPOLLET标志)
    ev.events = EPOLLIN | EPOLLET;  // 边缘触发+读事件
    ev.data.fd = listen_fd;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev) < 0) {
        perror("epoll_ctl添加监听FD失败");
        close(listen_fd);
        close(epoll_fd);
        return -1;
    }

    // 端口复用+绑定+监听
    int reuse = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    listen(listen_fd, 5);
    printf("epoll服务器(ET模式)启动:端口=%d,最大连接=%d\n", PORT, MAX_CLIENTS);

    // 事件循环(ET模式核心逻辑)
    while (1) {
        ret = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if (ret < 0) {
            perror("epoll_wait失败");
            continue;
        }

        for (i = 0; i < ret; i++) {
            int fd = events[i].data.fd;
            Client* client = &clients[fd];  // 当前客户端指针

            // 处理读事件(ET模式:一次触发需处理完所有数据)
            if (events[i].events & EPOLLIN) {
                if (fd == listen_fd) {
                    // 新客户端连接(ET模式必须循环accept,避免遗漏)
                    while (1) {
                        client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len);
                        if (client_fd < 0) {
                            // 非阻塞模式下,EAGAIN表示暂时没有新连接
                            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                                break;
                            } else {
                                perror("accept失败");
                                break;
                            }
                        }

                        if (client_fd >= MAX_CLIENTS) {
                            printf("客户端超出最大限制(%d),拒绝连接\n", MAX_CLIENTS);
                            close(client_fd);
                            continue;
                        }

                        // 初始化客户端信息
                        char client_ip[INET_ADDRSTRLEN];
                        inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
                        int client_port = ntohs(client_addr.sin_port);
                        init_client(client_fd, client_ip, client_port);

                        // 设置非阻塞并添加到epoll(ET模式)
                        set_nonblocking(client_fd);
                        ev.events = EPOLLIN | EPOLLET;  // 边缘触发+读事件
                        ev.data.fd = client_fd;
                        if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev) < 0) {
                            perror("epoll_ctl添加客户端FD失败");
                            cleanup_client(epoll_fd, client_fd);
                            continue;
                        }

                        // 新连接提示
                        printf("新客户端连接:%s:%d\n", client_ip, client_port);
                    }
                } else {
                    // 客户端发送数据(ET模式必须循环recv,直到无数据)
                    if (!client->in_use) continue;

                    while (1) {
                        memset(recv_buf, 0, BUF_SIZE);
                        ret = recv(fd, recv_buf, BUF_SIZE - 1, 0);
                        if (ret > 0) {
                            // 接收数据处理
                            printf("接收:%s(%d字节)→ 暂存待发送\n", recv_buf, ret);
                            
                            // 暂存数据到发送缓冲区
                            if (client->send_len + ret <= BUF_SIZE) {
                                memcpy(client->send_buf + client->send_len, recv_buf, ret);
                                client->send_len += ret;
                                
                                // 注册写事件(ET模式:添加EPOLLOUT)
                                ev.events = EPOLLIN | EPOLLOUT | EPOLLET;
                                ev.data.fd = fd;
                                epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &ev);
                            } else {
                                printf("客户端:%s:%d 发送缓冲区已满,丢弃数据\n", client->ip, client->port);
                            }
                        } else if (ret == 0) {
                            // 正常断开
                            printf("客户端:%s:%d正常断开\n", client->ip, client->port);
                            cleanup_client(epoll_fd, fd);
                            break;
                        } else {
                            // 无更多数据(ET模式关键:EAGAIN时退出循环)
                            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                                break;
                            } else {
                                // 异常错误
                                perror("recv失败");
                                printf("客户端:%s:%d异常断开\n", client->ip, client->port);
                                cleanup_client(epoll_fd, fd);
                                break;
                            }
                        }
                    }
                }
            }

            // 处理写事件(ET模式:一次触发需发送完所有数据)
            if (events[i].events & EPOLLOUT) {
                if (!client->in_use || client->send_len <= 0) {
                    // 无数据可发,移除写事件
                    ev.events = EPOLLIN | EPOLLET;
                    ev.data.fd = fd;
                    epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &ev);
                    continue;
                }

                // 循环发送数据直到完成或缓冲区满
                while (client->send_len > 0) {
                    int sent = send(fd, client->send_buf, client->send_len, 0);
                    if (sent < 0) {
                        // 缓冲区满(ET模式关键:EAGAIN时退出循环)
                        if (errno == EAGAIN || errno == EWOULDBLOCK) {
                            break;
                        } else {
                            // 异常错误
                            perror("send失败");
                            printf("客户端:%s:%d异常断开\n", client->ip, client->port);
                            cleanup_client(epoll_fd, fd);
                            break;
                        }
                    }

                    // 更新发送状态
                    client->send_len -= sent;
                    if (client->send_len == 0) {
                        // 发送完成
                        printf("发送:%s(%d字节)→ 发送完成\n", client->send_buf, sent);
                        // 移除写事件
                        ev.events = EPOLLIN | EPOLLET;
                        ev.data.fd = fd;
                        epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &ev);
                        break;
                    } else {
                        // 部分发送:移动剩余数据
                        memmove(client->send_buf, client->send_buf + sent, client->send_len);
                    }
                }
            }

            // 处理异常事件
            if (events[i].events & (EPOLLERR | EPOLLHUP)) {
                if (client->in_use) {
                    // 异常断开
                    printf("客户端:%s:%d异常断开\n", client->ip, client->port);
                    cleanup_client(epoll_fd, fd);
                }
            }
        }
    }

    close(listen_fd);
    close(epoll_fd);
    return 0;
}

编译运行gcc -o server_epoll_et server_epoll_et.c && ./server_epoll_et

信号驱动 IO(Signal-Driven IO)模型:高效的信号通知机制

信号驱动 I/O 模型是衔接 “非阻塞 I/O” 与 “纯异步 I/O” 的过渡方案,通过内核主动发送信号通知 I/O 操作就绪,核心优势在于彻底避免轮询的开销,实现 “数据就绪即通知” 的按需响应模式,适配低频次、高响应要求的场景,(如网络监控工具 tcpdump、嵌入式设备传感器交互),无法支撑高并发服务(如万级在线的游戏服务器)。

核心原理:“信号注册 - 异步等待 - 信号触发 - 数据拷贝” 四步流程:信号驱动 I/O 的核心逻辑围绕 “内核与进程的信号协作” 展开,关键特征是 “等待 I/O 就绪阶段非阻塞,数据拷贝阶段阻塞”,具体流程可拆解为以下四步:

提前注册:约定 I/O 就绪的 “通知规则”:进程需先完成两项核心配置,与内核约定 I/O 就绪的信号通知方式:

  • 注册 SIGIO 信号处理函数:通过系统调用(如 sigaction)向内核注册一个自定义函数,该函数是 “内核检测到 I/O 就绪后,进程需执行的读写逻辑”,且需确保函数仅调用 “信号安全函数”(如 read/write),避免非安全函数(如 printf)导致的进程状态混乱。
  • 绑定文件描述符(FD)与进程:通过文件控制调用(如 fcntl)的 F_SETOWN 命令将目标文件描述符(FD,如网络 Socket、设备文件)与当前进程 ID 绑定,然后用 F_SETFL 命令为 FD 添加 O_ASYNC 标志,开启该 FD 的 “信号驱动模式”;同时,将 FD 设为非阻塞模式,避免后续数据拷贝时阻塞信号处理流程,导致其他 I/O 事件无法响应。

异步等待:进程自由执行其他任务:完成注册后,进程无需像 “阻塞 I/O” 那样卡在 read()/write() 调用上等待,也无需像 “非阻塞 I/O + 轮询” 那样反复检查 FD 状态。相反,进程可以立即返回,继续执行其他业务逻辑(比如处理已就绪的任务、进行数据计算、或管理其他资源);监听 FD 状态的工作完全交给内核,内核会持续监控该 FD 是否有数据到达(如 Socket 接收缓冲区有新数据),进程与内核在此阶段是 “异步并行” 的,CPU 资源不会因 “等待 I/O” 而闲置。

信号触发:内核主动 “通知” I/O 就绪:当内核检测到目标 FD 的 I/O 操作就绪(例如客户端向 Socket 发送了数据、设备缓冲区有数据可读),会立即向绑定的进程发送 SIGIO 信号,进程原本在执行的任务会被暂停,自动跳转到之前注册的 “信号处理函数”,标志着 “I/O 就绪” 的消息已成功送达。

数据拷贝:阻塞完成 “数据搬运”:在信号处理函数中,进程需要调用 read()/write() 等 I/O 函数,完成数据在 “内核缓冲区” 与 “用户缓冲区” 之间的拷贝:

  • 读操作:将内核空间中的数据(如客户端发送的网络数据)拷贝到用户空间;
  • 写操作:将用户空间中的数据(如服务器的响应数据)拷贝到内核空间。 在此阶段进程会处于阻塞状态,直到数据拷贝完成,read()/write() 才会返回,进程才能继续执行被暂停的任务。这是信号驱动 I/O 与 “纯异步 I/O”(内核全程完成拷贝,进程无阻塞)的核心区别。
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <stdarg.h>
#include <stdbool.h>

// 配置参数
#define PORT 5150          // 服务器监听端口
#define BUF_SIZE 1024      // 数据缓冲区大小
#define MAX_CLIENTS 10     // 最大客户端数量

// 客户端结构体:管理连接信息
typedef struct {
    int fd;                  // 客户端文件描述符(-1表示空闲)
    char send_buf[BUF_SIZE]; // 待发送数据缓冲区
    int send_len;            // 待发送数据长度
    char ip[INET_ADDRSTRLEN];// 客户端IP地址
    int port;                // 客户端端口
    bool in_use;             // 连接状态标记
} Client;

// 全局客户端列表
Client clients[MAX_CLIENTS];
int client_count = 0;        // 当前活跃客户端数

// 函数声明
void safe_printf(const char *fmt, ...);
void set_nonblocking(int fd);
void init_client(Client *cli);
int find_free_client_slot(void);
void remove_client(int epoll_fd, int fd);
void handle_send(Client *cli);
void sigio_handler(int signo);
void setup_sigio(int fd, pid_t pid);

// 信号安全的日志打印
void safe_printf(const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);

    // 屏蔽SIGIO避免打印中断
    sigset_t mask, old_mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGIO);
    sigprocmask(SIG_BLOCK, &mask, &old_mask);

    vprintf(fmt, args);

    // 恢复信号掩码
    sigprocmask(SIG_SETMASK, &old_mask, NULL);

    va_end(args);
}

// 设置套接字为非阻塞模式
void set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
        perror("fcntl设置非阻塞失败");
        close(fd);
        exit(EXIT_FAILURE);
    }
}

// 初始化客户端结构体
void init_client(Client *cli) {
    cli->fd = -1;
    cli->send_len = 0;
    memset(cli->send_buf, 0, BUF_SIZE);
    memset(cli->ip, 0, INET_ADDRSTRLEN);
    cli->port = 0;
    cli->in_use = false;
}

// 查找空闲客户端槽位
int find_free_client_slot() {
    for (int i = 0; i < MAX_CLIENTS; i++) {
        if (!clients[i].in_use) {
            return i;
        }
    }
    return -1;
}

// 移除客户端连接
void remove_client(int epoll_fd, int fd) {
    for (int i = 0; i < MAX_CLIENTS; i++) {
        if (clients[i].fd == fd && clients[i].in_use) {
            safe_printf("客户端:%s:%d正常断开\n", clients[i].ip, clients[i].port);
            close(fd);
            init_client(&clients[i]);
            client_count--;
            break;
        }
    }
}

// 处理发送缓冲区数据
void handle_send(Client *cli) {
    if (cli->send_len <= 0 || !cli->in_use) {
        return;
    }

    // 循环发送数据
    while (cli->send_len > 0) {
        ssize_t sent = send(cli->fd, cli->send_buf, cli->send_len, 0);
        if (sent < 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                break; // 缓冲区满,等待下次触发
            } else {
                perror("send错误");
                safe_printf("客户端:%s:%d异常断开\n", cli->ip, cli->port);
                remove_client(-1, cli->fd);
                break;
            }
        } else if (sent == 0) {
            safe_printf("客户端:%s:%d正常断开\n", cli->ip, cli->port);
            remove_client(-1, cli->fd);
            break;
        } else {
            cli->send_len -= (int)sent;
            if (cli->send_len > 0) {
                memmove(cli->send_buf, cli->send_buf + sent, cli->send_len);
                safe_printf("客户端:%s:%d 部分发送:%d字节,剩余%d字节\n", 
                       cli->ip, cli->port, (int)sent, cli->send_len);
            } else {
                safe_printf("发送:%s(%d字节)→ 发送完成\n", cli->send_buf, (int)sent);
                memset(cli->send_buf, 0, BUF_SIZE);
            }
        }
    }
}

// SIGIO信号处理函数
void sigio_handler(int signo) {
    if (signo != SIGIO) {
        return;
    }

    // 遍历客户端处理IO事件
    for (int i = 0; i < MAX_CLIENTS; i++) {
        Client *cli = &clients[i];
        if (!cli->in_use) {
            continue;
        }

        // 处理读事件
        int has_data = 0;
        char recv_buf[BUF_SIZE];
        do {
            memset(recv_buf, 0, BUF_SIZE);
            ssize_t recv_len = recv(cli->fd, recv_buf, BUF_SIZE - 1, 0);

            if (recv_len > 0) {
                has_data = 1;
                safe_printf("接收:%s(%d字节)→ 暂存待发送\n", recv_buf, (int)recv_len);

                if (cli->send_len + recv_len <= BUF_SIZE) {
                    memcpy(cli->send_buf + cli->send_len, recv_buf, recv_len);
                    cli->send_len += (int)recv_len;
                } else {
                    safe_printf("客户端:%s:%d 发送缓冲区已满,丢弃数据\n", 
                           cli->ip, cli->port);
                }
            } else if (recv_len == 0) {
                safe_printf("客户端:%s:%d正常断开\n", cli->ip, cli->port);
                remove_client(-1, cli->fd);
                break;
            } else {
                if (errno != EAGAIN && errno != EWOULDBLOCK) {
                    perror("recv错误");
                    safe_printf("客户端:%s:%d异常断开\n", cli->ip, cli->port);
                    remove_client(-1, cli->fd);
                }
                break;
            }
        } while (1);

        // 处理写事件
        if (has_data || cli->send_len > 0) {
            handle_send(cli);
        }
    }
}

// 配置信号驱动IO
void setup_sigio(int fd, pid_t pid) {
    // 设置FD所有者
    if (fcntl(fd, F_SETOWN, pid) < 0) {
        perror("fcntl设置FD所有者失败");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 启用异步IO标志
    int flags = fcntl(fd, F_GETFL, 0);
    if (fcntl(fd, F_SETFL, flags | O_ASYNC) < 0) {
        perror("fcntl启用异步IO失败");
        close(fd);
        exit(EXIT_FAILURE);
    }

    set_nonblocking(fd);
}

// 主函数:初始化服务器并处理连接
int main() {
    int listen_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    struct sigaction sa;
    pid_t pid = getpid();

    // 初始化客户端列表
    for (int i = 0; i < MAX_CLIENTS; i++) {
        init_client(&clients[i]);
    }

    // 创建监听套接字
    if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("创建监听套接字失败");
        return -1;
    }

    // 设置端口复用
    int reuse = 1;
    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
        perror("设置端口复用失败");
        close(listen_fd);
        return -1;
    }

    // 绑定地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("绑定地址失败");
        close(listen_fd);
        return -1;
    }

    // 开始监听
    if (listen(listen_fd, 5) < 0) {
        perror("监听失败");
        close(listen_fd);
        return -1;
    }

    // 设置非阻塞
    set_nonblocking(listen_fd);

    // 配置SIGIO信号处理
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = sigio_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    if (sigaction(SIGIO, &sa, NULL) < 0) {
        perror("注册SIGIO信号处理失败");
        close(listen_fd);
        return -1;
    }

    // 启动信息
    safe_printf("信号驱动IO服务器启动:端口=%d,最大连接=%d\n", PORT, MAX_CLIENTS);

    // 主循环接受连接
    while (1) {
        client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len);
        if (client_fd > 0) {
            int slot = find_free_client_slot();
            if (slot == -1) {
                safe_printf("客户端超出最大限制(%d),拒绝连接\n", MAX_CLIENTS);
                close(client_fd);
                continue;
            }

            setup_sigio(client_fd, pid);

            Client *new_cli = &clients[slot];
            new_cli->fd = client_fd;
            inet_ntop(AF_INET, &client_addr.sin_addr, new_cli->ip, sizeof(new_cli->ip));
            new_cli->port = ntohs(client_addr.sin_port);
            new_cli->in_use = true;
            client_count++;

            safe_printf("新客户端连接:%s:%d\n", new_cli->ip, new_cli->port);
        } else {
            if (errno != EAGAIN && errno != EWOULDBLOCK) {
                perror("accept错误");
                usleep(10000);
            }
        }
    }

    close(listen_fd);
    return 0;
}

编译运行gcc -o server_sigio server_sigio.c && ./server_sigio

异步 IO(Asynchronous IO)模型:完全非阻塞

异步 I/O 模型是唯一实现  “等待数据就绪” 与 “数据拷贝” 两阶段全程非阻塞 的模型,通过 “进程发起请求后彻底脱离 I/O 流程,内核全程处理并在完成后通知” 的机制,将 CPU 资源从 “等待 I/O” 中完全解放,适配对性能有极致需求的场景(如百万级并发网关、高性能数据库),但需承担更高的开发复杂度与内核兼容性成本。

核心原理:“进程发起请求→内核全程处理→完成后通知” 三步流程:异步 I/O 的核心逻辑围绕 “进程与内核的‘委托 - 通知’关系” 展开,全程无任何阻塞环节,关键特征是  “仅在‘发起请求’和‘处理结果’时占用进程,中间流程完全交给内核” ,具体流程可拆解为以下三步:

进程发起异步 I/O 请求,立即返回: 进程通过专门的异步 I/O 接口向内核 “委托” I/O 任务,无需等待任何结果,直接回归业务逻辑,核心操作包括:

  • 指定 I/O 任务参数:明确 I/O 类型(读 / 写)、目标文件描述符(FD,如 Socket、磁盘文件)、用户空间数据缓冲区地址、数据长度等,让内核清楚 “要处理什么 I/O”;
  • 设置完成通知方式:告知内核 “I/O 全程完成后,如何通知我”,常见两种方式,信号通知(如 POSIX 标准 aio_read/aio_write 绑定 SIGIO 信号);完成队列通知(如 Linux 现代方案 io_uring 的 CQ 队列,内核将结果写入队列,进程轮询队列即可,无信号竞态问题);
  • 非阻塞返回:请求提交后,内核仅记录任务信息(如缓冲区地址、FD 状态),进程无需等待 “是否就绪” 或 “是否拷贝”,立即返回执行其他业务(如处理已完成的 I/O 结果、计算任务)。

内核全程处理 I/O 流程,进程无感知:进程发起请求后,I/O 的两个核心阶段完全由内核接管,进程无需任何干预,实现 “CPU 与 I/O 并行”:

  • 阶段 1:内核等待 I/O 就绪:内核持续监控目标 FD 的状态(如 Socket 接收缓冲区是否有数据、磁盘是否准备好读写),此阶段与进程完全并行,无需进程轮询或阻塞;
  • 阶段 2:内核完成数据拷贝:当 FD 就绪后,内核直接将数据在 “内核空间” 与 “用户空间” 之间搬运(读操作:内核→用户;写操作:用户→内核),整个拷贝过程无需进程参与,也不会阻塞进程,内核可通过硬件 DMA(直接内存访问)或高效内存拷贝机制完成,避免占用进程 CPU(区别于信号驱动 I/O 的核心,信号驱动 I/O 仅让内核通知 “就绪”,拷贝仍需进程阻塞处理,而异步 I/O 让内核全程完成 “就绪等待 + 数据拷贝”,进程全程无感知)。

内核通知进程 I/O 完成,进程处理结果:当内核完成 “等待就绪 + 数据拷贝” 全程后,通过进程预设的方式发送 “完成通知”,进程仅需处理结果,无需参与 I/O 执行:

  • 触发通知:若为 “信号通知”,内核发送预设信号(如 SIGIO);若为 “完成队列通知”,内核将 I/O 结果(成功 / 失败、处理字节数)写入队列(如 io_uring 的 CQ 队列);
  • 进程处理结果:进程收到通知后,无需执行任何 I/O 操作,仅需读取 “完成结果” 并处理业务逻辑:读操作完成:直接使用用户缓冲区中已就绪的数据(如数据库将加载的磁盘数据返回给客户端); 写操作完成:确认数据已发送(如网关记录 “请求已成功转发” 日志);失败处理:针对错误码(如 FD 关闭、磁盘空间不足)执行重试或告警逻辑。此阶段仍为非阻塞 —— 即使大量 I/O 同时完成,进程也可批量处理结果(如轮询完成队列),无需逐个等待。
基于 POSIX 标准 aio_* 接口:传统异步 I/O
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <aio.h>
#include <signal.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <stdarg.h>
#include <stdbool.h>

// 配置参数
#define PORT 5150
#define BUF_SIZE 1024
#define MAX_CLIENTS 1024  // 最大客户端数量

// 客户端数据结构体
typedef struct {
    int fd;                  // 客户端FD(-1表示空闲)
    struct aiocb read_aio;   // 读操作aio控制块
    struct aiocb write_aio;  // 写操作aio控制块
    char read_buf[BUF_SIZE]; // 读缓冲区
    char write_buf[BUF_SIZE];// 写缓冲区
    struct sockaddr_in addr; // 客户端地址(日志用)
    bool in_use;             // 连接状态标记(统一风格)
} Client;
Client client_data[MAX_CLIENTS]; // 管理每个客户端
int client_count = 0;            // 当前活跃客户端数量

// 函数声明
void safe_printf(const char *fmt, ...);
void init_client(Client *cli);
int find_free_client();
void set_nonblocking(int fd);
void get_client_addr(Client *cli, char *buf, int len);
void aio_completion_handler(int signo, siginfo_t *info, void *context);

// 信号安全的日志打印
void safe_printf(const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);

    // 屏蔽SIGIO避免打印中断
    sigset_t mask, old_mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGIO);
    sigprocmask(SIG_BLOCK, &mask, &old_mask);

    vprintf(fmt, args);

    // 恢复信号掩码
    sigprocmask(SIG_SETMASK, &old_mask, NULL);

    va_end(args);
}

// 初始化客户端结构体(标记为空闲状态)
void init_client(Client *cli) {
    cli->fd = -1;
    cli->in_use = false;       // 初始化标记为未使用
    memset(&cli->read_aio, 0, sizeof(struct aiocb));
    memset(&cli->write_aio, 0, sizeof(struct aiocb));
    memset(cli->read_buf, 0, BUF_SIZE);
    memset(cli->write_buf, 0, BUF_SIZE);
    memset(&cli->addr, 0, sizeof(struct sockaddr_in));
}

// 查找空闲客户端槽位
int find_free_client() {
    for (int i = 0; i < MAX_CLIENTS; i++) {
        if (!client_data[i].in_use) {  
            return i;
        }
    }
    return -1; // 无空闲槽位
}

// 设置套接字为非阻塞模式
void set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
        perror("fcntl设置非阻塞失败");
        close(fd);
        exit(EXIT_FAILURE);
    }
}

// 从客户端地址获取IP:端口字符串
void get_client_addr(Client *cli, char *buf, int len) {
    inet_ntop(AF_INET, &cli->addr.sin_addr, buf, len);
    int port = ntohs(cli->addr.sin_port);
    snprintf(buf + strlen(buf), len - strlen(buf), ":%d", port);
}

// aio完成信号处理函数(内核完成I/O后触发)
void aio_completion_handler(int signo, siginfo_t *info, void *context) {
    if (signo != SIGIO || info->si_signo != SIGIO) {
        return; // 忽略非SIGIO信号
    }

    // 遍历客户端,匹配触发信号的aio控制块
    for (int i = 0; i < MAX_CLIENTS; i++) {
        Client *cli = &client_data[i];
        if (!cli->in_use) {  // 使用in_use判断连接状态
            continue;
        }

        char client_addr[INET_ADDRSTRLEN + 6];
        get_client_addr(cli, client_addr, sizeof(client_addr));

        // 处理读操作完成
        if (&cli->read_aio == info->si_value.sival_ptr) {
            ssize_t ret = aio_return(&cli->read_aio);
            if (ret <= 0) {
                if (ret == 0) {
                    safe_printf("客户端:%s正常断开\n", client_addr);
                } else {
                    const char *err_msg = strerror(-ret);
                    safe_printf("客户端:%s异常断开(%s)\n", client_addr, err_msg);
                }
                close(cli->fd);
                cli->in_use = false;  // 标记为空闲
                init_client(cli);
                client_count--;
                return;
            }

            safe_printf("接收:%s(%d字节)→ 暂存待发送\n", cli->read_buf, (int)ret);
            memcpy(cli->write_buf, cli->read_buf, ret);

            // 初始化写操作aio控制块
            memset(&cli->write_aio, 0, sizeof(struct aiocb));
            cli->write_aio.aio_fildes = cli->fd;
            cli->write_aio.aio_buf = cli->write_buf;
            cli->write_aio.aio_nbytes = ret;
            cli->write_aio.aio_offset = 0;
            cli->write_aio.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
            cli->write_aio.aio_sigevent.sigev_signo = SIGIO;
            cli->write_aio.aio_sigevent.sigev_value.sival_ptr = &cli->write_aio;

            if (aio_write(&cli->write_aio) < 0) {
                perror("aio_write错误");
                safe_printf("客户端:%s异常断开\n", client_addr);
                close(cli->fd);
                cli->in_use = false;  // 标记为空闲
                init_client(cli);
                client_count--;
            }
            return;
        }

        // 处理写操作完成
        if (&cli->write_aio == info->si_value.sival_ptr) {
            ssize_t ret = aio_return(&cli->write_aio);
            if (ret <= 0) {
                const char *err_msg = strerror(-ret);
                if (-ret == EPIPE || -ret == ECONNRESET || -ret == ENOTCONN) {
                    safe_printf("客户端:%s异常断开(发送失败:%s)\n", client_addr, err_msg);
                } else {
                    perror("aio_write错误");
                    safe_printf("客户端:%s异常断开\n", client_addr);
                }
                close(cli->fd);
                cli->in_use = false;  // 标记为空闲
                init_client(cli);
                client_count--;
                return;
            }

            if (ret < (ssize_t)cli->read_buf[0]) {  // 修正为实际已读长度判断
                safe_printf("客户端:%s 部分发送:%d字节,剩余%d字节\n", 
                       client_addr, (int)ret, (int)(strlen(cli->write_buf) - ret));
            } else {
                safe_printf("发送:%s(%d字节)→ 发送完成\n", cli->write_buf, (int)ret);
            }

            // 重新发起异步读
            memset(&cli->read_aio, 0, sizeof(struct aiocb));
            cli->read_aio.aio_fildes = cli->fd;
            cli->read_aio.aio_buf = cli->read_buf;
            cli->read_aio.aio_nbytes = BUF_SIZE - 1;
            cli->read_aio.aio_offset = 0;
            cli->read_aio.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
            cli->read_aio.aio_sigevent.sigev_signo = SIGIO;
            cli->read_aio.aio_sigevent.sigev_value.sival_ptr = &cli->read_aio;

            if (aio_read(&cli->read_aio) < 0) {
                perror("aio_read错误");
                safe_printf("客户端:%s异常断开\n", client_addr);
                close(cli->fd);
                cli->in_use = false;  // 标记为空闲
                init_client(cli);
                client_count--;
            }
            return;
        }
    }
}

int main() {
    int listen_fd, client_fd, slot;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    struct sigaction sa;
    pid_t pid = getpid();

    // 初始化所有客户端数据
    for (int i = 0; i < MAX_CLIENTS; i++) {
        init_client(&client_data[i]);
    }

    // 创建监听套接字并设为非阻塞
    if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("创建监听套接字失败");
        return -1;
    }
    set_nonblocking(listen_fd);

    // 配置aio完成信号处理
    memset(&sa, 0, sizeof(sa));
    sa.sa_sigaction = aio_completion_handler;
    sa.sa_flags = SA_SIGINFO;
    sigemptyset(&sa.sa_mask);
    if (sigaction(SIGIO, &sa, NULL) < 0) {
        perror("注册SIGIO信号处理失败");
        close(listen_fd);
        return -1;
    }

    // 端口复用+绑定+监听
    int reuse = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("绑定地址失败");
        close(listen_fd);
        return -1;
    }
    listen(listen_fd, 5);
    safe_printf("异步I/O服务器(POSIX aio_*)启动:端口=%d,最大连接=%d\n", PORT, MAX_CLIENTS);

    // 主循环:非阻塞accept新连接
    while (1) {
        client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len);
        if (client_fd > 0) {
            slot = find_free_client();
            if (slot == -1) {
                char client_ip[INET_ADDRSTRLEN];
                inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
                safe_printf("客户端:%s:%d 超出最大限制(%d),拒绝连接\n", 
                       client_ip, ntohs(client_addr.sin_port), MAX_CLIENTS);
                close(client_fd);
                continue;
            }

            // 初始化新客户端数据
            Client *new_cli = &client_data[slot];
            new_cli->fd = client_fd;
            new_cli->addr = client_addr;
            new_cli->in_use = true;  // 标记为活跃连接
            client_count++;

            // 发起第一个异步读请求
            memset(&new_cli->read_aio, 0, sizeof(struct aiocb));
            new_cli->read_aio.aio_fildes = client_fd;
            new_cli->read_aio.aio_buf = new_cli->read_buf;
            new_cli->read_aio.aio_nbytes = BUF_SIZE - 1;
            new_cli->read_aio.aio_offset = 0;
            new_cli->read_aio.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
            new_cli->read_aio.aio_sigevent.sigev_signo = SIGIO;
            new_cli->read_aio.aio_sigevent.sigev_value.sival_ptr = &new_cli->read_aio;

            if (aio_read(&new_cli->read_aio) < 0) {
                perror("aio_read发起失败");
                close(client_fd);
                new_cli->in_use = false;  // 标记为空闲
                init_client(new_cli);
                client_count--;
                continue;
            }

            // 打印新连接日志
            char client_ip[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
            safe_printf("新客户端连接:%s:%d\n", client_ip, ntohs(client_addr.sin_port));
        } else if (errno != EAGAIN && errno != EWOULDBLOCK) {
            perror("accept错误");
            usleep(10000);
        }
    }

    close(listen_fd);
    return 0;
}

编译运行gcc -o server_aio server_aio.c -lrt && ./server_aio

基于 Linux 现代 io_uring 接口:高性能异步 I/O
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <liburing.h> // 新版 liburing 头文件(≥2.0)
#include <stdarg.h>
#include <signal.h>
#include <stdbool.h>

#define PORT 5150
#define BUF_SIZE 1024
#define MAX_CLIENTS 1024  // 最大客户端数量
#define RING_ENTRIES 256  // io_uring 队列大小(SQ/CQ 最大条目数)

// 客户端数据结构体
typedef struct {
    int fd;                  // 客户端 FD
    char read_buf[BUF_SIZE]; // 读缓冲区
    char write_buf[BUF_SIZE];// 写缓冲区
    int read_len;            // 已读数据长度
    struct sockaddr_in addr; // 客户端地址(日志打印用)
    bool in_use;             // 连接状态标记(统一风格)
} Client;

// 全局变量:客户端数据管理、io_uring 实例
Client client_data[MAX_CLIENTS];
struct io_uring ring;


// 信号安全的日志打印
void safe_printf(const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);

    // 屏蔽SIGIO避免打印中断
    sigset_t mask, old_mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGIO);
    sigprocmask(SIG_BLOCK, &mask, &old_mask);

    vprintf(fmt, args);

    // 恢复信号掩码
    sigprocmask(SIG_SETMASK, &old_mask, NULL);

    va_end(args);
}


// 从客户端地址获取IP:端口字符串
void get_client_addr(Client *cli, char *buf, int len) {
    inet_ntop(AF_INET, &cli->addr.sin_addr, buf, len);
    int port = ntohs(cli->addr.sin_port);
    snprintf(buf + strlen(buf), len - strlen(buf), ":%d", port);
}


// 客户端初始化
void init_client(Client *cli) {
    cli->fd = -1;
    cli->read_len = 0;
    cli->in_use = false;     // 初始化标记为未使用
    memset(cli->read_buf, 0, BUF_SIZE);
    memset(cli->write_buf, 0, BUF_SIZE);
    memset(&cli->addr, 0, sizeof(struct sockaddr_in));
}


// 查找空闲客户端槽位
int find_free_client() {
    for (int i = 0; i < MAX_CLIENTS; i++) {
        if (!client_data[i].in_use) {  
            return i;
        }
    }
    return -1;
}


// 设置套接字为非阻塞
void set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
        perror("fcntl设置非阻塞失败");
        close(fd);
        exit(EXIT_FAILURE);
    }
}


// 提交 accept 请求到 io_uring
void submit_accept_request(int listen_fd) {
    struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
    if (!sqe) {
        perror("io_uring_get_sqe失败(accept请求)");
        return;
    }

    io_uring_prep_accept(
        sqe,                // 空闲 SQ 条目
        listen_fd,          // 监听 FD
        NULL, NULL,         // 客户端地址(后续补充)
        0                   // 标志
    );

    sqe->user_data = (uint64_t)-1; // 标记为 accept 请求
}


// 提交 read 请求到 io_uring
void submit_read_request(int client_fd, int client_idx) {
    struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
    if (!sqe) {
        perror("io_uring_get_sqe失败(read请求)");
        return;
    }

    Client *cli = &client_data[client_idx];
    io_uring_prep_read(
        sqe,                // 空闲 SQ 条目
        client_fd,          // 客户端 FD
        cli->read_buf,      // 读缓冲区
        BUF_SIZE - 1,       // 读数据长度
        0                   // 偏移量
    );

    sqe->user_data = (uint64_t)client_idx; // 标记为 read 请求
}


// 提交 write 请求到 io_uring
void submit_write_request(int client_fd, int client_idx) {
    struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
    if (!sqe) {
        perror("io_uring_get_sqe失败(write请求)");
        return;
    }

    Client *cli = &client_data[client_idx];
    io_uring_prep_write(
        sqe,                // 空闲 SQ 条目
        client_fd,          // 客户端 FD
        cli->write_buf,     // 写缓冲区
        cli->read_len,      // 写数据长度
        0                   // 偏移量
    );

    // 标记为 write 请求(高位标记)
    sqe->user_data = (uint64_t)client_idx | (1ULL << 30);
}


// 核心函数:处理 io_uring 完成队列(CQ)
void process_completion_queue(int listen_fd) {
    struct io_uring_cqe *cqe; // 完成队列条目
    int ret;                  // I/O 操作结果

    while (io_uring_peek_cqe(&ring, &cqe) == 0) {
        // 处理 accept 请求完成
        if (cqe->user_data == (uint64_t)-1) {
            ret = cqe->res;
            if (ret < 0) {
                printf("accept失败:%s(errno=%d)\n", strerror(-ret), -ret);
                io_uring_cqe_seen(&ring, cqe);
                submit_accept_request(listen_fd);
                continue;
            }
            int client_fd = ret;

            int slot = find_free_client();
            if (slot == -1) {
                struct sockaddr_in client_addr;
                socklen_t addr_len = sizeof(client_addr);
                getsockname(client_fd, (struct sockaddr*)&client_addr, &addr_len);
                char client_ip[INET_ADDRSTRLEN];
                inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
                safe_printf("客户端:%s:%d 超出最大限制(%d),拒绝连接\n", 
                       client_ip, ntohs(client_addr.sin_port), MAX_CLIENTS);
                close(client_fd);
                io_uring_cqe_seen(&ring, cqe);
                submit_accept_request(listen_fd);
                continue;
            }

            Client *new_cli = &client_data[slot];
            new_cli->fd = client_fd;
            new_cli->in_use = true;  // 标记为活跃连接
            struct sockaddr_in client_addr;
            socklen_t addr_len = sizeof(client_addr);
            getsockname(client_fd, (struct sockaddr*)&client_addr, &addr_len);
            new_cli->addr = client_addr;

            submit_read_request(client_fd, slot);

            char client_ip[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
            safe_printf("新客户端连接:%s:%d\n", 
                   client_ip, ntohs(client_addr.sin_port));

            submit_accept_request(listen_fd);
        }

        // 处理 read 请求完成(无高位标记)
        else if (!(cqe->user_data & (1ULL << 30))) {
            int client_idx = (int)cqe->user_data;
            Client *cli = &client_data[client_idx];

            if (!cli->in_use) {  // 使用in_use判断连接状态
                io_uring_cqe_seen(&ring, cqe);
                continue;
            }

            char client_addr[INET_ADDRSTRLEN + 6];
            get_client_addr(cli, client_addr, sizeof(client_addr));

            ret = cqe->res;
            if (ret <= 0) {
                // 区分正常断开和异常断开
                if (ret == 0) {
                    safe_printf("客户端:%s正常断开\n", client_addr);
                } else {
                    safe_printf("客户端:%s异常断开(%s)\n", client_addr, strerror(-ret));
                }
                close(cli->fd);
                cli->in_use = false;  // 标记为空闲
                init_client(cli);
                io_uring_cqe_seen(&ring, cqe);
                continue;
            }

            cli->read_len = ret;
            memcpy(cli->write_buf, cli->read_buf, ret);
            safe_printf("接收:%s(%d字节)→ 暂存待发送\n", cli->read_buf, ret);
            submit_write_request(cli->fd, client_idx);

        }

        // 处理 write 请求完成(有高位标记)
        else {
            int client_idx = (int)(cqe->user_data & ~(1ULL << 30));
            Client *cli = &client_data[client_idx];

            if (!cli->in_use) {  
                io_uring_cqe_seen(&ring, cqe);
                continue;
            }

            char client_addr[INET_ADDRSTRLEN + 6];
            get_client_addr(cli, client_addr, sizeof(client_addr));

            ret = cqe->res;
            if (ret < 0) {
                safe_printf("客户端:%s异常断开(发送失败:%s)\n", client_addr, strerror(-ret));
                close(cli->fd);
                cli->in_use = false;  // 标记为空闲
                init_client(cli);
                io_uring_cqe_seen(&ring, cqe);
                continue;
            }

            if (ret < cli->read_len) {
                safe_printf("客户端:%s 部分发送:%d字节,剩余%d字节\n", 
                       client_addr, ret, cli->read_len - ret);
                // 调整缓冲区,准备发送剩余数据
                memmove(cli->write_buf, cli->write_buf + ret, cli->read_len - ret);
                cli->read_len -= ret;
                submit_write_request(cli->fd, client_idx);
            } else {
                safe_printf("发送:%s(%d字节)→ 发送完成\n", cli->write_buf, ret);
                submit_read_request(cli->fd, client_idx);
            }
        }

        io_uring_cqe_seen(&ring, cqe);
    }
}

int main() {
    int listen_fd;
    struct sockaddr_in server_addr;

    // 初始化所有客户端为未使用状态
    for (int i = 0; i < MAX_CLIENTS; i++) {
        init_client(&client_data[i]);
    }

    if (io_uring_queue_init(RING_ENTRIES, &ring, 0) < 0) {
        perror("io_uring_queue_init失败");
        return -1;
    }

    if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket创建失败");
        io_uring_queue_exit(&ring);
        return -1;
    }
    set_nonblocking(listen_fd);

    int reuse = 1;
    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
        perror("setsockopt端口复用失败");
        close(listen_fd);
        io_uring_queue_exit(&ring);
        return -1;
    }

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind失败");
        close(listen_fd);
        io_uring_queue_exit(&ring);
        return -1;
    }

    if (listen(listen_fd, 5) < 0) {
        perror("listen失败");
        close(listen_fd);
        io_uring_queue_exit(&ring);
        return -1;
    }

    safe_printf("异步I/O服务器(Linux io_uring)启动:端口=%d,最大连接=%d,队列大小=%d\n", 
           PORT, MAX_CLIENTS, RING_ENTRIES);

    submit_accept_request(listen_fd);

    while (1) {
        io_uring_submit(&ring);
        process_completion_queue(listen_fd);
        usleep(1000);
    }

    close(listen_fd);
    io_uring_queue_exit(&ring);
    return 0;
}

编译运行(源码安装的 liburing 头文件在 /usr/local/include,库文件在 /usr/local/lib,编译时需显式指定路径): gcc -o server_io_uring server_io_uring.c -I/usr/local/include -L/usr/local/lib -luring && ./server_io_uring

编译与运行步骤:基于CentOS 8

确保已安装 liburing :若此前未安装或安装失败,根据以下命令源码安装(兼容所有 CentOS 8 版本):

# 1. 安装编译依赖(gcc、make、wget)
dnf install -y gcc make wget

# 2. 下载 liburing 2.5 源码(稳定版,兼容代码)
cd /tmp
wget https://github.com/axboe/liburing/archive/refs/tags/liburing-2.5.tar.gz

# 3. 解压并编译安装
tar -zxvf liburing-2.5.tar.gz
cd liburing-liburing-2.5
./configure
make -j4  # 4线程编译,可根据 CPU 核心数调整(如 -j8)
make install  # 安装到 /usr/local/include 和 /usr/local/lib
CentOS 8 内核升级:5.1+

io_uring 是 Linux 内核 5.1 版本才正式引入 的特性,而 CentOS 8 默认内核版本是 4.18.x,完全不支持 io_uring!即使编译成功,运行时也会报 “设备不支持” 或 “无效参数” 错误。至此需先升级 CentOS 8 内核到 5.1+,具体步骤如下

更新仓库缓存,搜索可用的内核版本

# 1. 清理仓库缓存,避免旧数据干扰
yum clean all && yum makecache

# 2. 搜索 ELRepo 仓库中的主线内核(kernel-ml,ml=mainline)
yum --enablerepo=elrepo-kernel search kernel-ml

安装最新的主线内核

yum --enablerepo=elrepo-kernel install -y kernel-ml

设置新内核为默认启动项(与之前一致)

# 1. 查看所有内核启动项,找到新安装的 kernel-ml(通常在最前面,序号为 0)
awk -F\' '$1=="menuentry " {print i++ " : " $2}' /etc/grub2.cfg

# 2. 设置默认启动项为新内核(序号 0,根据实际输出调整)
grub2-set-default 0

# 3. 生成新的 grub 配置
grub2-mkconfig -o /boot/grub2/grub.cfg

# 4. 重启服务器(必须重启才能加载新内核)
reboot

重启后确认内核版本(需 ≥5.1)

uname -r # 输出示例:5.15.102-elrepo.x86_64(确认是 5.1+ 版本)

总结:Linux Socket 五大 I/O 模型核心要点

Linux Socket 五大 I/O 模型(阻塞 I/O、非阻塞 I/O、I/O 多路复用、信号驱动 I/O、异步 I/O)的本质差异,均源于对 “等待数据就绪” 和 “数据拷贝” 两个核心阶段的阻塞策略与通知机制设计不同。

阻塞 I/O 因两阶段均阻塞,仅适用于单人测试服等低并发场景,代码最简单但无并发能力;非阻塞 I/O 等待阶段不阻塞却需轮询,CPU 消耗高,仅适合 10-20 人小规模高频微操作测试;I/O 多路复用(select/poll/epoll)通过单线程监听多 FD 突破并发瓶颈,其中 select 兼容旧系统但 FD 限 100,poll 无 FD 限但效率一般,epoll 依托红黑树与就绪链表实现 O (1) 事件处理,支持万人级并发,是游戏正式服(如 MMO 跨服战场)的主流选择,LT 模式简单不易出错,ET 模式效率更高但需配合非阻塞 FD 处理数据完整性;信号驱动 I/O 靠 SIGIO 信号通知,避免轮询却有信号风暴风险,游戏开发中极少用;异步 I/O 两阶段均不阻塞,资源利用率最高但兼容性差、调试难,仅适合极致性能需求且有成熟运维支持的场景。