深入拆解 Windows Socket 五种 I/O 模型:核心机制、Linux 差异与场景适配

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

引言:I/O 模型对 Windows 游戏服务性能的核心影响

网络 I/O 模型的本质是解决 "如何协调数据传输的'等待就绪'与'数据拷贝'两大核心阶段",以平衡效率与资源消耗。与 Linux 不同,Windows 作为闭源操作系统,虽遵循 I/O 传输的通用逻辑,却未采用 Linux 依赖的 POSIX 标准,而是基于自身内核的底层特性(如内核级线程池调度、用户态 - 内核态内存直接映射机制),设计了 IOCP(I/O 完成端口)、重叠 I/O 等专有 I/O 实现,形成了与 Linux 截然不同的 I/O 架构。

核心差异基础:Windows 与 Linux I/O 模型的底层分野

Windows 与 Linux I/O 模型的差异并非表层接口差异,而是源于系统调用、事件通知、异步实现、并发管理四大底层机制的根源性不同,直接决定了两种系统的 I/O 设计逻辑、性能上限与适用场景。

系统调用接口:POSIX 通用兼容 vs 内核贴合的专有设计:系统调用是用户态与内核交互的桥接,直接影响 I/O 操作的效率与适配范围::

  • Windows :用 WSARecv/WSASend/WSARecvFrom 等 Winsock 专有接口,接口参数(如 WSAOVERLAPPED 结构体、WSABUF 内存描述符)直接贴合 Windows 内核机制。比如 WSABUF 结构体可实现 "用户态 - 内核态内存零拷贝"(内核直接读写用户态内存,不用额外拷贝);WSAOVERLAPPED 结构体原生承载异步状态(事件、结果),不用用户态再封装,调用效率更高。
  • Linux :严格遵循 POSIX 标准,通过 read/write/recvfrom 等通用接口实现 I/O 操作。接口设计以 "跨平台兼容" 为核心,需适配嵌入式设备、服务器、桌面终端等多场景,因此未深度绑定某一内核特性,调用逻辑相对通用,牺牲了部分效率(比如无法直接零拷贝)。

事件通知机制:用户态主动检查 vs 内核级主动触发:事件通知机制解决 "如何让程序知道 I/O 事件已发生" 的问题,其效率直接影响高并发场景下的资源利用率:

  • Windows :内核主动触发。比如 WSAEventSelect 模型中 Socket 绑定内核事件(WSAEVENT),当 Socket 触发指定事件(如数据可读、新连接到达)时,内核直接激活该事件对象,无需用户态轮询;IOCP 模型更直接,将已完成的 I/O 事件(含操作结果、关联 Socket、用户数据)放入 "完成端口队列",工作线程通过 GetQueuedCompletionStatus 直接获取,无需任何轮询或遍历开销)。
  • Linux :用户态主动检查。比如 多路复用(如 epoll/select/poll 模型需用户态调用 epoll_wait/select 等函数阻塞等待事件,事件就绪后需遍历 FD 集合处理;信号驱动 I/O 模型则通过信号通知 I/O 事件,虽能避免轮询,但也存在 "信号风暴"(大量事件同时触发导致进程频繁中断)、信号丢失等风险。

异步 I/O 实现:用户态库模拟 vs 内核原生支持:异步 I/O 的核心是 "发起 I/O 操作后无需阻塞等待,可继续处理其他任务,待操作完成后再处理结果",其实现深度(内核参与范围)决定异步流程的效率:

  • Windows :内核原生支持,以 "重叠 I/O 为基础、IOCP 为高并发框架" 实现端到端异步。重叠 I/O 是底层支撑,通过 WSAOVERLAPPED 结构体发起的异步操作,从 "等待数据就绪"(内核监听网卡或磁盘缓冲区)到 "数据拷贝完成"(内核完成数据传输),全流程由内核自主调度,用户态发起操作后可立即返回处理其他任务;IOCP 则为升级版,基于重叠 I/O 整合了内核线程池,内核根据 I/O 负载动态分配线程处理完成事件,无需用户态维护线程池或异步队列,异步调度深度远超 Linux);
  • Linux :无内核原生端到端支持,核心依赖用户态主导实现,用户态需手动维护异步操作队列,管理待执行、已完成的异步任务,内核仅负责执行基础 I/O 操作,内核完成 I/O 操作通过信号或回调向用户态发送通知,不参与异步流程的调度逻辑,核心调度(队列排序、线程分配、结果关联)均需用户态实现,效率受限于用户态与内核态协同(如队列延迟拖累性能),其网络异步需通过 "非阻塞 I/O + epoll + 线程池" 组合模拟。

多并发管理:用户态手动配置 vs 内核动态适配:多并发管理解决 "如何分配线程资源处理大量连接" 的问题,调度精度直接影响线程切换开销与资源利用率,两者的管理逻辑差异明显:

  • Linux :"进程 / 线程 + 多路复用" 的用户态管理模式处理多并发,线程资源配置依赖开发者经验:需通过 pthread_create 等接口手动创建线程池,线程数量需开发者根据场景估算(如按 "CPU 核心数 ×2" 配置);以至并发资源利用率易受参数适配影响(线程数量过多,会导致 CPU 频繁切换线程上下文(保存 / 恢复线程栈、寄存器数据);线程数量过少,会引发 I/O 事件排队阻塞,无法及时处理业务)。
  • Windows:"内核线程池 + IOCP" 的动态管理模式处理多并发,线程资源由内核自动适配负载,内核线程池的线程数量默认与 CPU 核心数匹配(通常为 "核心数 + 1",根源上避免线程数量与 CPU 能力不匹配的问题);内核会根据 I/O 完成事件的密度实时调整线程状态(事件密集时(如峰值时段连接请求集中)激活更多线程并行处理,事件稀疏时(如凌晨连接活跃度低)挂起部分线程减少切换开销),无需用户态手动创建线程、估算数量或处理线程启停,内核托管所有线程资源,开发者专注于业务逻辑实现。

Windows Socket I/O 模型详解:机制拆解、Linux 对比与逻辑实现

Windows 网络 I/O 模型可归纳为 5 种(阻塞 I/O非阻塞 I/OI/O 多路复用 (select/WSAEventSelect)、WSAAsyncSelect异步 I/O(重叠 I/O + IOCP)),核心差异体现在 "等待数据就绪""数据拷贝" 阶段的阻塞规则、事件通知方式上,部分与 Linux 模型功能对应但实现逻辑不同,需结合游戏场景(如单人工具、副本服务器、MMO 主服)选择适配模型。

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

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

scss 复制代码
#include <winsock2.h>   // Windows Socket 核心头文件
#include <ws2tcpip.h>   // 包含 inet_pton 等函数(IP 地址转换)
#include <stdio.h>
#include <string.h>

// 链接 Windows Socket 静态库(必须添加,否则编译报错)
#pragma comment(lib, "ws2_32.lib")

// 配置参数(与原 Linux 代码保持一致)
#define SERVER_IP "127.0.0.1"  // 服务器 IP(本地测试用 127.0.0.1)
#define PORT 5150              // 服务器端口
#define BUF_SIZE 1024          // 数据缓冲区大小

int main() {
    // 1. 初始化 Windows Socket 环境(Windows 特有步骤,Linux 无需此操作)
    WSADATA wsaData;  // 存储 Socket 初始化信息
    // 初始化 2.2 版本 Socket(主流兼容版本)
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        // 初始化失败:打印错误码(WSAGetLastError 是 Windows 特有错误获取函数)
        printf("WSAStartup 初始化失败!错误码:%d\n", WSAGetLastError());
        return 1;
    }

    // 2. 创建 TCP 套接字(Windows 中套接字类型为 SOCKET,而非 Linux 的 int)
    SOCKET client_fd;  // Windows 套接字句柄(unsigned int 类型)
    // 参数:AF_INET(IPv4)、SOCK_STREAM(TCP 流式协议)、IPPROTO_TCP(TCP 协议)
    client_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (client_fd == INVALID_SOCKET) {  // Windows 无效套接字判断(而非 Linux 的 <0)
        printf("创建套接字失败!错误码:%d\n", WSAGetLastError());
        WSACleanup();  // 失败时需释放 Socket 资源(Windows 特有)
        return 1;
    }

    // 3. 初始化服务器地址结构(与 Linux 逻辑一致,参数含义相同)
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));  // 清空结构体
    server_addr.sin_family = AF_INET;              // IPv4 地址族
    server_addr.sin_port = htons(PORT);            // 端口转换为网络字节序(大端序)
    // 将字符串 IP 转换为二进制格式(inet_pton 跨平台,但 Windows 需 ws2tcpip.h 头文件)
    if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
        printf("IP 地址转换失败!错误码:%d\n", WSAGetLastError());
        closesocket(client_fd);  // 关闭套接字(Windows 用 closesocket,Linux 用 close)
        WSACleanup();            // 释放 Socket 环境
        return 1;
    }

    // 4. 连接服务器(逻辑与 Linux 一致,错误判断为 SOCKET_ERROR)
    if (connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {
        printf("连接服务器失败!错误码:%d\n", WSAGetLastError());
        closesocket(client_fd);
        WSACleanup();
        return 1;
    }
    printf("已连接服务器,输入数据发送(输入'quit'退出):\n");

    // 5. 发送-接收循环(核心逻辑与 Linux 完全一致)
    char buf[BUF_SIZE];
    int ret;  // 存储系统调用返回值
    while (1) {
        // 读取用户输入
        printf("客户端发送:");
        fgets(buf, BUF_SIZE, stdin);  // 读取控制台输入

        // 去掉输入末尾的换行符(fgets 会把回车符 '\n' 也读入缓冲区)
        int len = strlen(buf);
        if (buf[len - 1] == '\n') {
            buf[--len] = '\0';
        }

        // 输入 "quit" 则退出循环
        if (strcmp(buf, "quit") == 0) {
            break;
        }

        // 发送数据到服务器(参数与 Linux 一致,错误判断为 SOCKET_ERROR)
        ret = send(client_fd, buf, len, 0);
        if (ret == SOCKET_ERROR) {
            printf("发送数据失败!错误码:%d\n", WSAGetLastError());
            break;
        }
        printf("发送字节数:%d\n", ret);

        // 接收服务器反射的响应数据
        memset(buf, 0, BUF_SIZE);  // 清空缓冲区,避免残留旧数据
        ret = recv(client_fd, buf, BUF_SIZE, 0);
        if (ret == SOCKET_ERROR) {
            printf("接收数据失败!错误码:%d\n", WSAGetLastError());
            break;
        }
        else if (ret == 0) {
            // ret == 0 表示服务器主动关闭连接(与 Linux 逻辑一致)
            printf("服务器已断开连接!\n");
            break;
        }
        printf("服务器返回:%s(接收字节数:%d)\n\n", buf, ret);
    }

    // 6. 资源清理(Windows 特有步骤:先关套接字,再释放 Socket 环境)
    closesocket(client_fd);  // 关闭套接字句柄(替代 Linux 的 close)
    WSACleanup();            // 释放 Windows Socket 环境资源
    printf("已断开连接,资源已清理\n");

    return 0;
}

阻塞 I/O(Blocking I/O):低并发场景的基础方案

核心机制 :"等待数据就绪" 与 "数据拷贝" 均阻塞调用线程,在回声服务器中,主线程通过 accept 阻塞等待客户端连接,连接成功后创建子线程,子线程通过 recv 阻塞等待客户端发送数据,接收完成后用 send 阻塞返回回声,整个过程中子线程完全挂起,直至操作完成。

Windows 特有细节:Socket 默认阻塞,若需切换非阻塞,需通过 ioctlsocket 函数显式配置,传入 FIONBIO 命令(参数设为非零值);而 Linux 中,可通过 fcntl 函数设置 O_NONBLOCK 标志实现非阻塞切换。

与 Linux 对比及适用场景对比:逻辑与 Linux 阻塞 I/O 完全一致,阻塞线程均会被内核调度器挂起(不占用 CPU 资源),避免无效消耗;差异仅在于 Socket 模式切换的接口(Windows 用 ioctlsocket,Linux 用 fcntl);适用低并发、低交互场景,如游戏 GM 工具的单客户端连接等。

scss 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <winsock2.h>
#include <windows.h>
#include <process.h>
#include <ws2tcpip.h>  // 用于 INET_ADDRSTRLEN 和 inet_ntop

// 链接 Winsock 库(Windows 特有)
#pragma comment(lib, "ws2_32.lib")

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

typedef struct {
    SOCKET client_fd;          // Windows 用 SOCKET 类型(对应 Linux 的 int)
    char client_ip[IP_LEN];    // 客户端 IP 地址
    int client_port;           // 客户端端口(随机分配)
} ClientParam;

//接收客户端数据→回显→错误处理
unsigned int __stdcall handle_client(void* arg) {
    ClientParam* param = (ClientParam*)arg;
    SOCKET 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);  // Windows recv 用法与 Linux 兼容

        //接收结果判断
        if (ret == 0) {
            // 正常断开:客户端主动关闭连接
            printf("客户端:%s:%d正常断开\n", client_ip, client_port);
            break;
        }
        else if (ret < 0) {
            // 异常断开:排除 "系统中断" 错误(Windows 对应 WSAEINTR)
            if (WSAGetLastError() != WSAEINTR) {  // Windows 用 WSAGetLastError()
                printf("客户端:%s:%d异常断开\n", client_ip, client_port);
                break;
            }
            continue;  // 若为中断错误,重试 recv
        }

        // 接收成功:打印数据并回显(流程与 Linux 完全一致)
        printf("接收:%s(%d字节)\n", buf, ret);
        ret = send(client_fd, buf, ret, 0);  // Windows send 用法
        if (ret <= 0) {
            printf("send fail: %d\n", WSAGetLastError());  
            break;
        }
    }

    // 释放资源:关闭客户端套接字(Windows 用 closesocket)
    closesocket(client_fd);
    _endthreadex(0);  // Windows 线程结束(确保资源释放)
    return 0;
}

int main() {
    // 变量定义
    SOCKET listen_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;  // 地址结构体
    int client_addr_len = sizeof(client_addr);    
    HANDLE hThread;                               

    // 初始化网络库(Windows 特有,流程前置)
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        printf("WSAStartup fail: %d\n", WSAGetLastError());  
        return -1;
    }

    // 创建监听套接字
    listen_fd = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, 0);  
    if (listen_fd == INVALID_SOCKET) {  // Windows 用 INVALID_SOCKET 替代 -1
        printf("socket fail: %d\n", WSAGetLastError());
        WSACleanup();  // Windows 需清理 Winsock 资源
        return -1;
    }

    // 端口复用 + 绑定地址
    int reuse = 1;
    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) == SOCKET_ERROR) {
        printf("setsockopt fail: %d\n", WSAGetLastError());
        closesocket(listen_fd);  // 替代 Linux 的 close()
        WSACleanup();
        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);  // 监听所有网卡

    // 绑定(API 适配:Windows 用 SOCKET_ERROR 替代 -1)
    if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {
        printf("bind fail: %d\n", WSAGetLastError());
        closesocket(listen_fd);
        WSACleanup();
        return -1;
    }

    // 开始监听
    if (listen(listen_fd, 5) == SOCKET_ERROR) {  // 监听队列大小 5
        printf("listen fail: %d\n", WSAGetLastError());
        closesocket(listen_fd);
        WSACleanup();
        return -1;
    }
    printf("阻塞IO服务器启动:端口=%d\n", PORT);  

    // 循环接受客户端连接
    while (1) {
       
        client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len);
        if (client_fd == INVALID_SOCKET) {
            printf("accept fail: %d\n", WSAGetLastError());
            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);  

        // 封装线程参数
        ClientParam* param = (ClientParam*)malloc(sizeof(ClientParam));
        if (param == NULL) {
            printf("malloc fail\n");  
            closesocket(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;

        // 创建子线程处理客户端
        hThread = (HANDLE)_beginthreadex(NULL, 0, handle_client, param, 0, NULL);
        if (hThread == NULL) {  // 线程创建失败
            printf("pthread_create fail\n");  
            closesocket(client_fd);
            free(param);  // 释放参数内存
        }
        CloseHandle(hThread);  // Windows 释放线程句柄
    }

    // 资源清理(理论上不执行)
    closesocket(listen_fd);
    WSACleanup();  // Windows 特有:清理 Winsock 库
    return 0;
}

非阻塞 I/O(Non-Blocking I/O):高频小数据的轮询方案

核心机制 :"等待数据就绪" 不阻塞线程,"数据拷贝" 仍阻塞。在回声服务器中,acceptrecvsend 均设为非阻塞:若客户端未发送数据,recv 会立即返回 WSAEWOULDBLOCK 错误,服务器需通过 while 循环轮询(搭配 Sleep(10) 减少 CPU 占用),直至数据就绪后执行回声发送。

与 Linux 对比:均需用户态轮询检查数据状态,轮询过程消耗 CPU 资源,需搭配线程池(如 Windows 用 CreateThreadPool,Linux 用 pthread_create)分散轮询压力,避免单线程轮询占用过多资源;差异在于"数据未就绪" 的错误标识不同(Windows 通过 WSAGetLastError() 获取 WSAEWOULDBLOCK 错误码,Linux 则是 EAGAIN/EWOULDBLOCK 错误码),此外接口略有差异(Windows 非阻塞 Socket 需通过 ioctlsocket 配置,Linux 可通过 fcntl 或 ioctl 配置)。

适用场景:适用场景 高频、小数据量的交互场景,如游戏实时心跳检测、玩家短消息发送(单次数据量 < 1KB),不适合高并发场景(如 1000 人战场会因轮询导致 CPU 利用率飙升)。

ini 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <winsock2.h>
#include <windows.h>
#include <process.h>
#include <ws2tcpip.h>  // 用于 INET_ADDRSTRLEN 和 inet_ntop

#pragma comment(lib, "ws2_32.lib")

#define PORT 5150
#define BUF_SIZE 1024
#define MAX_CLIENTS 10
#define IP_LEN 16

// 客户端参数结构体
typedef struct {
    SOCKET client_fd;
    char client_ip[IP_LEN];
    int client_port;
} ClientParam;

// 设置非阻塞模式
void set_nonblocking(SOCKET fd) {
    u_long mode = 1;
    ioctlsocket(fd, FIONBIO, &mode);
}

// 处理客户端(非阻塞轮询)
unsigned int __stdcall handle_client(void* arg) {
    ClientParam* param = (ClientParam*)arg;
    SOCKET client_fd = param->client_fd;
    char client_ip[IP_LEN];
    int client_port = param->client_port;

    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) {
        // 非阻塞接收
        ret = recv(client_fd, buf, BUF_SIZE, 0);
        if (ret > 0) {
            buf[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 {
            int err = WSAGetLastError();
            if (err != WSAEWOULDBLOCK && err != WSAEINPROGRESS) {
                printf("客户端:%s:%d异常断开(错误码:%d)\n",
                    client_ip, client_port, err);
                break;
            }
            // 无数据时休眠
            Sleep(10);
        }
    }

    closesocket(client_fd);
    return 0;
}

int main() {
    WSADATA wsaData;
    SOCKET listen_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    int client_addr_len = sizeof(client_addr);
    HANDLE hThread;

    WSAStartup(MAKEWORD(2, 2), &wsaData);

    // 创建监听套接字并设置非阻塞
    listen_fd = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, 0);
    set_nonblocking(listen_fd);

    // 端口复用
    int reuse = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&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("非阻塞I/O服务器启动:端口=%d\n", PORT);

    // 非阻塞接受连接
    while (1) {
        client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len);
        if (client_fd != INVALID_SOCKET) {
            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);

            ClientParam* param = (ClientParam*)malloc(sizeof(ClientParam));
            param->client_fd = client_fd;
            strncpy(param->client_ip, client_ip, IP_LEN - 1);
            param->client_port = client_port;

            hThread = (HANDLE)_beginthreadex(NULL, 0, handle_client, param, 0, NULL);
            if (hThread == NULL) {
                closesocket(client_fd);
                free(param);
            }
            CloseHandle(hThread);
        }
        else {
            int err = WSAGetLastError();
            if (err != WSAEWOULDBLOCK && err != WSAEINPROGRESS) {
                printf("accept错误:%d\n", err);
            }
            Sleep(10);
        }
    }

    closesocket(listen_fd);
    WSACleanup();
    return 0;
}

I/O 多路复用(I/O Multiplexing):中低并发的高效监听

Windows 提供两种主流多路复用模型,功能上对应 Linux 的 select/poll,但基于内核事件对象优化,更适配中低并发场景。

select 模型:兼容旧系统的基础多路复用

核心机制 :单线程通过 "读 / 写 / 异常" 三个 FD 集合监听多 Socket。在回声服务器中,select 阻塞等待任一客户端触发事件(如 FD_READ 表示有数据待接收),事件就绪后遍历 FD 集合,找到对应客户端执行 recv 接收数据、send 返回回声。

与 Linux 对比: 共性是默认支持 64 个 Socket(可通过宏扩展,但效率下降)、每次调用需重新初始化 FD 集合(因内核会修改集合),效率随 Socket 数量增加而下降;而差异是 Windows select 的首参(nfds)无效(传 0),Linux select 需传入 "最大文件描述符 + 1",以致内核可确定遍历范围;且Windows FD 集合仅支持 Socket,Linux 支持所有文件描述符(如文件、管道)。

适用场景:兼容 WinXP 等旧系统,或连接数 < 100 的小型游戏房间(如 20 人斗地主房间,仅需低频次的聊天、出牌数据交互)。

scss 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <stdbool.h>

#pragma comment(lib, "ws2_32.lib")

// 全局宏定义(与 Linux 版本保持一致)
#define PORT 5150
#define BUF_SIZE 1024
#define MAX_FD 1024   // select 支持的最大套接字数量
#define IP_LEN 16     // IPv4 地址存储长度

// 客户端信息结构体(与 Linux 版本保持一致)
typedef struct {
    bool in_use;           // 标记是否正在使用
    char ip[IP_LEN];       // 客户端 IP 地址
    int port;              // 客户端端口
    char send_buf[BUF_SIZE];// 待发送数据缓冲区
    int send_len;          // 待发送数据长度
} Client;

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

// 初始化客户端结构体
void init_client(SOCKET 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(SOCKET fd) {
    memset(&clients[fd], 0, sizeof(Client));
    clients[fd].in_use = false;
}

int main() {
    WSADATA wsaData;
    SOCKET listen_fd, client_fd;
    SOCKET max_fd;  // 改为 main 函数局部变量(与 Linux 版本统一)
    struct sockaddr_in server_addr, client_addr;
    int 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;

    // 初始化 Winsock 库
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        printf("WSAStartup 失败: %d\n", WSAGetLastError());
        return -1;
    }

    // 初始化客户端数组
    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 = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, 0);
    if (listen_fd == INVALID_SOCKET) {
        printf("创建套接字失败: %d\n", WSAGetLastError());
        WSACleanup();
        return -1;
    }
    FD_SET(listen_fd, &read_fds);
    FD_SET(listen_fd, &except_fds);
    max_fd = listen_fd;  // 局部变量初始化

    // 端口复用+绑定+监听
    int reuse = 1;
    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) == SOCKET_ERROR) {
        printf("setsockopt 失败: %d\n", WSAGetLastError());
        closesocket(listen_fd);
        WSACleanup();
        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)) == SOCKET_ERROR) {
        printf("绑定失败: %d\n", WSAGetLastError());
        closesocket(listen_fd);
        WSACleanup();
        return -1;
    }

    if (listen(listen_fd, 5) == SOCKET_ERROR) {
        printf("监听失败: %d\n", WSAGetLastError());
        closesocket(listen_fd);
        WSACleanup();
        return -1;
    }

    printf("select服务器(IO多路复用)启动:端口=%d\n", PORT);

    // select 事件循环
    while (1) {
        // 复制事件集合
        tmp_read = read_fds;
        tmp_write = write_fds;
        tmp_except = except_fds;

        // 等待事件发生(Windows select 第一个参数忽略,传 0)
        ret = select(0, &tmp_read, &tmp_write, &tmp_except, NULL);
        if (ret == SOCKET_ERROR) {
            printf("select 失败: %d\n", WSAGetLastError());
            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 == INVALID_SOCKET) {
                    printf("accept 失败: %d\n", WSAGetLastError());
                    continue;
                }
                if (client_fd >= MAX_FD) {
                    printf("客户端超出select最大限制(%d),拒绝连接\n", MAX_FD);
                    closesocket(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);

                // 添加到事件集合并更新 max_fd
                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);
                    closesocket(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 == SOCKET_ERROR) {
                printf("send 失败: %d\n", WSAGetLastError());
                FD_CLR(i, &read_fds);
                FD_CLR(i, &write_fds);
                FD_CLR(i, &except_fds);
                cleanup_client(i);
                closesocket(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);
            closesocket(i);

            // 更新最大套接字句柄(局部变量操作)
            if (i == max_fd) {
                while (max_fd > 0 && !FD_ISSET(max_fd, &read_fds) && !FD_ISSET(max_fd, &write_fds)) {
                    max_fd--;
                }
            }
        }
    }

    // 清理资源(理论上不会执行)
    closesocket(listen_fd);
    WSACleanup();
    return 0;
}
WSAEventSelect 模型:中并发的事件驱动优化

核心机制 :通过内核事件对象(WSAEVENT)绑定 每个 Socket 事件,监听 FD_READ/FD_ACCEPT/FD_CLOSE 等事件(如 FD_READ 表示数据可读、FD_ACCEPT 表示有新连接、FD_CLOSE 表示连接关闭),在回声服务器中,服务器通过 WSAWaitForMultipleEvents 阻塞等待事件,事件就绪后通过 WSAEnumNetworkEvents 直接获取事件类型(无需遍历),找到对应客户端执行回声逻辑。

与 Linux 对比 :类似 Linux 的 poll,但基于内核事件驱动,效率比 select 高 30%~50%,接近 epoll 的水平触发(不支持边缘触发,可能重复通知)。

适用场景:中并发场景,如 100 人团队副本服务器(需处理技能释放、伤害计算等中等频次数据)、中小型公会聊天服务。

ini 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <stdbool.h>

#pragma comment(lib, "ws2_32.lib")

// 全局宏定义
#define PORT 5150
#define BUF_SIZE 1024
#define MAX_CLIENTS 10
#define MAX_EVENTS 11  // 1个监听事件 + 10个客户端事件
#define IP_LEN 16      // IPv4地址存储长度

// 客户端信息结构体
typedef struct {
    bool in_use;           // 标记是否正在使用
    SOCKET fd;             // 客户端套接字句柄
    char ip[IP_LEN];       // 客户端IP地址
    int port;              // 客户端端口
    char read_buf[BUF_SIZE];  // 读缓冲区
    char send_buf[BUF_SIZE];  // 待发送数据缓冲区
    int send_len;          // 待发送数据总长度
    int send_pos;          // 当前发送位置(解决部分发送问题)
    WSAEVENT event;        // 关联事件对象
} Client;

// 全局数组:管理所有客户端
Client clients[MAX_CLIENTS];
WSAEVENT events[MAX_EVENTS];  // 事件数组
int event_count = 0;          // 当前事件总数

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

// 清理客户端结构体
void cleanup_client(int index) {
    if (clients[index].fd != INVALID_SOCKET) {
        closesocket(clients[index].fd);
        clients[index].fd = INVALID_SOCKET;
    }
    if (clients[index].event != WSA_INVALID_EVENT) {
        WSACloseEvent(clients[index].event);
        clients[index].event = WSA_INVALID_EVENT;
    }
    memset(&clients[index], 0, sizeof(Client));
    clients[index].in_use = false;
}

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

// 移除事件数组中的指定事件
void remove_event(WSAEVENT event) {
    for (int i = 1; i < event_count; i++) {  // 从1开始:0是监听事件
        if (events[i] == event) {
            // 移动数组元素填补空缺
            memmove(&events[i], &events[i + 1], (event_count - i - 1) * sizeof(WSAEVENT));
            event_count--;
            break;
        }
    }
}

// 发送数据函数(处理部分发送情况)
void send_data(int client_idx) {
    Client* cli = &clients[client_idx];
    if (cli->send_len <= 0 || cli->send_pos >= cli->send_len) {
        // 没有数据可发送,恢复事件监听
        WSAEventSelect(cli->fd, cli->event, FD_READ | FD_CLOSE);
        cli->send_len = 0;
        cli->send_pos = 0;
        return;
    }

    // 发送剩余数据
    int sent = send(cli->fd, cli->send_buf + cli->send_pos,
        cli->send_len - cli->send_pos, 0);

    if (sent == SOCKET_ERROR) {
        int err = WSAGetLastError();
        if (err == WSAEWOULDBLOCK) {
            // 暂时无法发送,保留事件监听等待下次机会
            return;
        }

        // 真正的错误,关闭连接
        printf("客户端 %s:%d 发送失败: %d\n", cli->ip, cli->port, err);
        remove_event(cli->event);
        cleanup_client(client_idx);
        return;
    }

    cli->send_pos += sent;

    // 数据发送完成
    if (cli->send_pos >= cli->send_len) {
        printf("发送:%s(%d字节)→ 发送完成\n", cli->send_buf, cli->send_len);
        cli->send_len = 0;
        cli->send_pos = 0;
        // 恢复事件监听,只关注读和关闭事件
        WSAEventSelect(cli->fd, cli->event, FD_READ | FD_CLOSE);
    }
    else {
        // 还有数据未发送,继续监听可写事件
        printf("客户端 %s:%d 部分发送:已发送%d字节,剩余%d字节\n",
            cli->ip, cli->port, sent, cli->send_len - cli->send_pos);
    }
}

int main() {
    WSADATA wsaData;
    SOCKET listen_fd;
    struct sockaddr_in server_addr, client_addr;
    int client_addr_len = sizeof(client_addr);
    DWORD wait_result;
    int ret;

    // 初始化Winsock库
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        printf("WSAStartup 失败: %d\n", WSAGetLastError());
        return -1;
    }

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

    // 创建监听套接字
    listen_fd = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, 0);
    if (listen_fd == INVALID_SOCKET) {
        printf("创建套接字失败: %d\n", WSAGetLastError());
        WSACleanup();
        return -1;
    }

    // 启用端口复用
    int reuse = 1;
    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR,
        (const char*)&reuse, sizeof(reuse)) == SOCKET_ERROR) {
        printf("setsockopt 失败: %d\n", WSAGetLastError());
        closesocket(listen_fd);
        WSACleanup();
        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)) == SOCKET_ERROR) {
        printf("绑定失败: %d\n", WSAGetLastError());
        closesocket(listen_fd);
        WSACleanup();
        return -1;
    }

    // 开始监听
    if (listen(listen_fd, 5) == SOCKET_ERROR) {
        printf("监听失败: %d\n", WSAGetLastError());
        closesocket(listen_fd);
        WSACleanup();
        return -1;
    }

    // 初始化监听事件
    events[0] = WSACreateEvent();
    if (events[0] == WSA_INVALID_EVENT) {
        printf("创建监听事件失败: %d\n", WSAGetLastError());
        closesocket(listen_fd);
        WSACleanup();
        return -1;
    }
    WSAEventSelect(listen_fd, events[0], FD_ACCEPT | FD_CLOSE);
    event_count = 1;

    printf("WSAEventSelect服务器(IO多路复用)启动:端口=%d,最大连接=%d\n", PORT, MAX_CLIENTS);

    // 事件循环
    while (1) {
        // 等待事件触发
        wait_result = WSAWaitForMultipleEvents(event_count, events, FALSE, WSA_INFINITE, FALSE);
        if (wait_result == WSA_WAIT_FAILED) {
            printf("WSAWaitForMultipleEvents 失败: %d\n", WSAGetLastError());
            break;
        }

        int event_index = wait_result - WSA_WAIT_EVENT_0;
        // 遍历处理所有触发的事件
        for (int i = event_index; i < event_count; i++) {
            if (WSAWaitForMultipleEvents(1, &events[i], TRUE, 0, FALSE) != WSA_WAIT_EVENT_0) {
                continue;
            }

            // 重置事件状态
            WSAResetEvent(events[i]);

            // 处理监听事件(新连接)
            if (i == 0) {
                WSANETWORKEVENTS network_events;
                ret = WSAEnumNetworkEvents(listen_fd, events[0], &network_events);
                if (ret == SOCKET_ERROR) {
                    printf("WSAEnumNetworkEvents 失败: %d\n", WSAGetLastError());
                    continue;
                }

                // 处理新连接事件
                if (network_events.lNetworkEvents & FD_ACCEPT) {
                    if (network_events.iErrorCode[FD_ACCEPT_BIT] != 0) {
                        printf("FD_ACCEPT 失败: %d\n", network_events.iErrorCode[FD_ACCEPT_BIT]);
                        continue;
                    }

                    // 接受新连接
                    SOCKET client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len);
                    if (client_fd == INVALID_SOCKET) {
                        printf("accept 失败: %d\n", WSAGetLastError());
                        continue;
                    }

                    // 检查连接限制
                    int free_slot = find_free_client();
                    if (free_slot == -1 || event_count >= MAX_EVENTS) {
                        char client_ip[INET_ADDRSTRLEN];
                        inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
                        printf("客户端:%s:%d 超出最大限制,拒绝连接\n",
                            client_ip, ntohs(client_addr.sin_port));
                        closesocket(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);

                    // 创建客户端事件
                    WSAEVENT client_event = WSACreateEvent();
                    if (client_event == WSA_INVALID_EVENT) {
                        printf("创建客户端事件失败: %d\n", WSAGetLastError());
                        closesocket(client_fd);
                        continue;
                    }

                    // 初始化客户端结构体
                    init_client(free_slot, client_fd, client_ip, client_port);
                    clients[free_slot].event = client_event;

                    // 绑定事件与套接字
                    WSAEventSelect(client_fd, client_event, FD_READ | FD_CLOSE);

                    // 添加事件到数组
                    events[event_count] = client_event;
                    event_count++;

                    printf("新客户端连接:%s:%d\n", client_ip, client_port);
                }
            }

            // 处理客户端事件
            else {
                // 查找对应的客户端
                int client_idx = -1;
                for (int j = 0; j < MAX_CLIENTS; j++) {
                    if (clients[j].in_use && clients[j].event == events[i]) {
                        client_idx = j;
                        break;
                    }
                }
                if (client_idx == -1) continue;

                Client* cli = &clients[client_idx];
                WSANETWORKEVENTS network_events;
                ret = WSAEnumNetworkEvents(cli->fd, cli->event, &network_events);
                if (ret == SOCKET_ERROR) {
                    printf("客户端 %s:%d WSAEnumNetworkEvents 失败: %d\n",
                        cli->ip, cli->port, WSAGetLastError());
                    continue;
                }

                // 处理可读事件
                if (network_events.lNetworkEvents & FD_READ) {
                    if (network_events.iErrorCode[FD_READ_BIT] != 0) {
                        printf("客户端 %s:%d FD_READ 失败: %d\n",
                            cli->ip, cli->port, network_events.iErrorCode[FD_READ_BIT]);
                        continue;
                    }

                    // 读取数据
                    int valread = recv(cli->fd, cli->read_buf, BUF_SIZE - 1, 0);
                    if (valread <= 0) {
                        printf("客户端 %s:%d 异常断开\n", cli->ip, cli->port);
                        remove_event(cli->event);
                        cleanup_client(client_idx);
                        continue;
                    }

                    // 准备回显数据
                    cli->read_buf[valread] = '\0';
                    memcpy(cli->send_buf, cli->read_buf, valread);
                    cli->send_len = valread;
                    cli->send_pos = 0;  // 重置发送位置
                    printf("接收:%s(%d字节)→ 暂存待发送\n", cli->read_buf, valread);

                    // 注册写事件,准备发送数据
                    WSAEventSelect(cli->fd, cli->event, FD_READ | FD_WRITE | FD_CLOSE);

                    // 立即尝试发送数据
                    send_data(client_idx);
                }

                // 处理可写事件(仅在有数据时处理)
                if (network_events.lNetworkEvents & FD_WRITE && cli->send_len > 0) {
                    if (network_events.iErrorCode[FD_WRITE_BIT] != 0) {
                        printf("客户端 %s:%d FD_WRITE 失败: %d\n",
                            cli->ip, cli->port, network_events.iErrorCode[FD_WRITE_BIT]);
                        continue;
                    }

                    send_data(client_idx);
                }

                // 处理关闭事件
                if (network_events.lNetworkEvents & FD_CLOSE) {
                    printf("客户端 %s:%d 正常断开\n", cli->ip, cli->port);
                    remove_event(cli->event);
                    cleanup_client(client_idx);
                }
            }
        }
    }

    // 清理资源
    for (int i = 0; i < event_count; i++) {
        WSACloseEvent(events[i]);
    }
    closesocket(listen_fd);
    WSACleanup();
    return 0;
}

异步选择模型(WSAAsyncSelect):GUI 程序的消息驱动方案

核心机制 :基于 Windows 窗口消息传递 Socket 事件。在回声服务器中,需先创建窗口(即使隐藏),将 Socket 事件与自定义消息(如 WM_SOCKET)绑定;当客户端发送数据(触发 FD_READ),内核会向窗口消息队列发送 WM_SOCKET,窗口过程(WndProc)接收消息后,调用 recv/send 执行回声逻辑,不阻塞 GUI 线程。

与 Linux 对比:Linux 无窗口消息机制,无直接对应模型,功能类似 "信号驱动 I/O",但通过消息队列可缓存事件,不会频繁中断进程(避免 "信号风暴"),但 WSAAsyncSelect 依赖窗口环境,必须在有窗口的进程中使用(纯服务端无法使用),而 Linux 信号驱动 I/O 可在无 GUI 的服务端使用,但信号处理优先级低,易丢失事件。

适用场景:GUI 程序的网络交互,如处理登录验证、简单文字聊天等低频网络交互,不适合纯服务端高并发场景。

ini 复制代码
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS  // 新增:禁用Winsock过时API警告
#include <stdio.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <stdbool.h>

#pragma comment(lib, "ws2_32.lib")


#define PORT 5150
#define BUF_SIZE 1024
#define MAX_CLIENTS 10
#define IP_LEN 16
#define WM_SOCKET (WM_USER + 1)

typedef struct {
    bool in_use;
    SOCKET fd;
    char ip[IP_LEN];
    int port;
    char send_buf[BUF_SIZE];
    int send_len;
} Client;

Client clients[MAX_CLIENTS];
SOCKET listen_fd;
HWND hWnd;

void init_client(Client* cli, SOCKET fd, const char* ip, int port) {
    memset(cli, 0, sizeof(Client));
    cli->in_use = true;
    cli->fd = fd;
    strncpy(cli->ip, ip, IP_LEN - 1);
    cli->port = port;
    cli->send_len = 0;
}

void cleanup_client(Client* cli) {
    if (cli->fd != INVALID_SOCKET) {
        closesocket(cli->fd);
        cli->fd = INVALID_SOCKET;
    }
    memset(cli, 0, sizeof(Client));
    cli->in_use = false;
}

int find_free_client() {
    for (int i = 0; i < MAX_CLIENTS; i++) {
        if (!clients[i].in_use) {
            return i;
        }
    }
    return -1;
}

int find_client_by_fd(SOCKET fd) {
    for (int i = 0; i < MAX_CLIENTS; i++) {
        if (clients[i].in_use && clients[i].fd == fd) {
            return i;
        }
    }
    return -1;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    SOCKET sock = (SOCKET)wParam;
    int event = WSAGETSELECTEVENT(lParam);
    int error = WSAGETSELECTERROR(lParam);
    int ret, slot;
    char recv_buf[BUF_SIZE];
    struct sockaddr_in client_addr;
    int client_addr_len = sizeof(client_addr);

    switch (msg) {
    case WM_SOCKET:
        if (error != 0) {
            printf("套接字事件错误: %d\n", error);
            if (sock != listen_fd) {
                slot = find_client_by_fd(sock);
                if (slot != -1) {
                    printf("客户端:%s:%d异常断开\n", clients[slot].ip, clients[slot].port);
                    cleanup_client(&clients[slot]);
                }
            }
            return 0;
        }

        if (sock == listen_fd) {
            if (event == FD_ACCEPT) {
                SOCKET client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len);
                if (client_fd == INVALID_SOCKET) {
                    printf("accept 失败: %d\n", WSAGetLastError());
                    return 0;
                }

                slot = find_free_client();
                if (slot == -1) {
                    char ip_str[INET_ADDRSTRLEN];
                    inet_ntop(AF_INET, &client_addr.sin_addr, ip_str, sizeof(ip_str));
                    printf("客户端超出最大限制(%d),拒绝连接\n", MAX_CLIENTS);
                    closesocket(client_fd);
                    return 0;
                }

                char client_ip[IP_LEN];
                inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, IP_LEN - 1);
                int client_port = ntohs(client_addr.sin_port);
                init_client(&clients[slot], client_fd, client_ip, client_port);

                WSAAsyncSelect(client_fd, hWnd, WM_SOCKET, FD_READ | FD_CLOSE);
                printf("新客户端连接:%s:%d\n", client_ip, client_port);
            }
            return 0;
        }

        slot = find_client_by_fd(sock);
        if (slot == -1) return 0;
        Client* cli = &clients[slot];

        if (event == FD_CLOSE) {
            printf("客户端:%s:%d正常断开\n", cli->ip, cli->port);
            cleanup_client(cli);
            return 0;
        }

        if (event == FD_READ) {
            memset(recv_buf, 0, BUF_SIZE);
            ret = recv(cli->fd, recv_buf, BUF_SIZE - 1, 0);
            if (ret <= 0) {
                printf("客户端:%s:%d异常断开\n", cli->ip, cli->port);
                cleanup_client(cli);
                return 0;
            }

            printf("接收:%s(%d字节)→ 暂存待发送\n", recv_buf, ret);
            strncpy(cli->send_buf, recv_buf, ret);
            cli->send_len = ret;

            WSAAsyncSelect(cli->fd, hWnd, WM_SOCKET, FD_WRITE | FD_CLOSE);
            return 0;
        }

        if (event == FD_WRITE) {
            if (cli->send_len <= 0) {
                WSAAsyncSelect(cli->fd, hWnd, WM_SOCKET, FD_READ | FD_CLOSE);
                return 0;
            }

            ret = send(cli->fd, cli->send_buf, cli->send_len, 0);
            if (ret == SOCKET_ERROR) {
                printf("send 失败: %d\n", WSAGetLastError());
                cleanup_client(cli);
                return 0;
            }

            printf("发送:%s(%d字节)→ 发送完成\n", cli->send_buf, cli->send_len);
            cli->send_len = 0;
            WSAAsyncSelect(cli->fd, hWnd, WM_SOCKET, FD_READ | FD_CLOSE);
            return 0;
        }
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hWnd, msg, wParam, lParam);
    }
    return 0;
}

bool create_window() {
    WNDCLASSEX wc = { 0 };
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.lpfnWndProc = WndProc;
    wc.hInstance = GetModuleHandle(NULL);
    wc.lpszClassName = "SocketWindowClass";

    if (!RegisterClassEx(&wc)) {
        printf("窗口类注册失败: %d\n", GetLastError());
        return false;
    }

    hWnd = CreateWindowEx(0, "SocketWindowClass", "WSAAsyncSelect服务器",
        0, 0, 0, 0, 0, HWND_MESSAGE, NULL, wc.hInstance, NULL);
    if (!hWnd) {
        printf("窗口创建失败: %d\n", GetLastError());
        UnregisterClass(wc.lpszClassName, wc.hInstance);
        return false;
    }
    return true;
}

int main() {
    WSADATA wsaData;
    struct sockaddr_in server_addr;
    MSG msg;
    int i;

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        printf("WSAStartup 失败: %d\n", WSAGetLastError());
        return -1;
    }

    memset(clients, 0, sizeof(clients));
    for (i = 0; i < MAX_CLIENTS; i++) {
        clients[i].in_use = false;
    }

    if (!create_window()) {
        WSACleanup();
        return -1;
    }

    listen_fd = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, 0);
    if (listen_fd == INVALID_SOCKET) {
        printf("创建套接字失败: %d\n", WSAGetLastError());
        DestroyWindow(hWnd);
        WSACleanup();
        return -1;
    }

    int reuse = 1;
    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) == SOCKET_ERROR) {
        printf("setsockopt 失败: %d\n", WSAGetLastError());
        closesocket(listen_fd);
        DestroyWindow(hWnd);
        WSACleanup();
        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)) == SOCKET_ERROR) {
        printf("绑定失败: %d\n", WSAGetLastError());
        closesocket(listen_fd);
        DestroyWindow(hWnd);
        WSACleanup();
        return -1;
    }

    if (listen(listen_fd, 5) == SOCKET_ERROR) {
        printf("监听失败: %d\n", WSAGetLastError());
        closesocket(listen_fd);
        DestroyWindow(hWnd);
        WSACleanup();
        return -1;
    }

    if (WSAAsyncSelect(listen_fd, hWnd, WM_SOCKET, FD_ACCEPT) == SOCKET_ERROR) {
        printf("WSAAsyncSelect 初始化失败: %d\n", WSAGetLastError());
        closesocket(listen_fd);
        DestroyWindow(hWnd);
        WSACleanup();
        return -1;
    }

    printf("WSAAsyncSelect服务器(消息驱动)启动:端口=%d\n", PORT);

    while (GetMessage(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    closesocket(listen_fd);
    for (i = 0; i < MAX_CLIENTS; i++) {
        if (clients[i].in_use) {
            cleanup_client(&clients[i]);
        }
    }
    UnregisterClass("SocketWindowClass", GetModuleHandle(NULL));
    WSACleanup();
    return 0;
}

异步 I/O:中高至超高并发的核心方案

作为 Windows 应对中高至超高并发的关键,异步 I/O 突破线程阻塞限制,实现 "发起即返回、内核传数据、完成再通知" 的全异步流程。核心是 "重叠 I/O(底层基础,支持零拷贝、管理异步状态)+ IOCP(高并发优化,内核动态调度线程)" 架构,依托 Windows 内核特性,适配游戏万级任务服、十万级 MMO 主服场景。

重叠 I/O(Overlapped I/O):中高并发的异步进阶方案

核心机制 :"等待就绪" 与 "数据拷贝" 均不阻塞线程。在回声服务器中,通过 WSAOVERLAPPED 发起异步 WSARecv(接收客户端数据),线程无需等待即可处理其他任务;当内核完成数据接收与拷贝后,会激活关联的 WSAEVENT;线程通过 WSAWaitForMultipleEvents 捕获事件后,必须调用 WSAGetOverlappedResult 获取异步操作的具体结果(如实际接收 / 发送的字节数、错误码),再基于结果发起异步 WSASend 执行回声逻辑,确保操作状态准确。

与 Linux 对比:类似 Linux 的 "非阻塞 I/O + epoll" 组合,Linux 需用户态维护非阻塞 Socket 与 epoll 事件的关联,手动检查操作结果;Windows 重叠 I/O 由内核托管异步操作,无需用户态维护状态,WSAGetOverlappedResult 可直接获取操作结果,减少用户态与内核态的交互次数,效率比 Linux 同方案高 20%~30%,接近 Linux io_uring 模型(但 io_uring 需用户态维护提交队列,重叠 I/O 无需)。

适用场景:高并发场景,如 1 万人同服的日常任务服务器(需处理任务提交、奖励发放等高频数据)、游戏道具商城(玩家频繁领取道具,需低延迟响应)。

ini 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#include <stdbool.h>
#include <string.h>

#pragma comment(lib, "ws2_32.lib")

#define PORT 5150
#define BUF_SIZE 1024
#define MAX_CLIENTS 10
#define IP_LEN 16

typedef struct {
    SOCKET fd;                  // 客户端套接字
    bool in_use;                // 连接状态
    char ip[IP_LEN];            // 客户端IP
    int port;                   // 客户端端口
    char read_buf[BUF_SIZE];    // 读缓冲区
    char send_buf[BUF_SIZE];    // 发送缓冲区
    int send_len;               // 待发送长度
    int send_pos;               // 发送位置
    WSAOVERLAPPED overlapped;   // 重叠结构
    WSAEVENT event;             // 独立事件对象(关键修复:使用独立事件而非重叠结构内的事件)
    bool is_sending;            // 操作类型标记
} Client;

Client clients[MAX_CLIENTS];
WSAEVENT events[MAX_CLIENTS + 1];  // 0号为监听事件,1~MAX为客户端事件
int event_count = 0;

// 初始化客户端
void init_client(Client* cli, SOCKET fd, const char* ip, int port) {
    if (cli == NULL) return;

    memset(cli, 0, sizeof(Client));
    cli->fd = fd;
    cli->in_use = true;
    strncpy(cli->ip, ip, IP_LEN - 1);
    cli->port = port;
    cli->send_len = 0;
    cli->send_pos = 0;
    cli->is_sending = false;

    // 创建独立事件对象,与重叠结构分离
    cli->event = WSACreateEvent();
    memset(&cli->overlapped, 0, sizeof(WSAOVERLAPPED));
}

// 清理客户端
void cleanup_client(Client* cli) {
    if (cli == NULL || !cli->in_use) return;

    closesocket(cli->fd);
    WSACloseEvent(cli->event);  // 关闭独立事件
    memset(cli, 0, sizeof(Client));
    cli->in_use = false;
}

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

// 继续发送剩余数据
void continue_sending(Client* cli) {
    if (cli == NULL || !cli->in_use || cli->send_pos >= cli->send_len) {
        return;
    }

    WSABUF wsa_buf;
    DWORD bytes_sent;
    int remaining = cli->send_len - cli->send_pos;

    wsa_buf.buf = cli->send_buf + cli->send_pos;
    wsa_buf.len = remaining;

    // 重置重叠结构,绑定到客户端独立事件
    memset(&cli->overlapped, 0, sizeof(WSAOVERLAPPED));
    cli->overlapped.hEvent = cli->event;  // 绑定到独立事件
    cli->is_sending = true;

    if (WSASend(cli->fd, &wsa_buf, 1, &bytes_sent, 0, &cli->overlapped, NULL) == SOCKET_ERROR) {
        if (WSAGetLastError() != WSA_IO_PENDING) {
            printf("客户端:%s:%d send失败: %d\n", cli->ip, cli->port, WSAGetLastError());
            cleanup_client(cli);
            return;
        }
    }
    else {
        // 数据立即发送完成
        cli->send_pos += bytes_sent;
        if (cli->send_pos >= cli->send_len) {
            printf("发送:%s(%d字节)→ 发送完成\n", cli->send_buf, cli->send_len);
            cli->is_sending = false;
            cli->send_len = 0;
            cli->send_pos = 0;

            // 重新发起读操作(确保读操作正确绑定事件)
            WSABUF recv_buf;
            DWORD bytes_recv, flags = 0;
            recv_buf.buf = cli->read_buf;
            recv_buf.len = BUF_SIZE - 1;

            memset(&cli->overlapped, 0, sizeof(WSAOVERLAPPED));
            cli->overlapped.hEvent = cli->event;  // 绑定到独立事件

            if (WSARecv(cli->fd, &recv_buf, 1, &bytes_recv, &flags, &cli->overlapped, NULL) == SOCKET_ERROR) {
                if (WSAGetLastError() != WSA_IO_PENDING) {
                    printf("客户端:%s:%d recv失败: %d\n", cli->ip, cli->port, WSAGetLastError());
                    cleanup_client(cli);
                    return;
                }
            }
        }
    }
}

// 处理I/O完成事件
void process_overlapped_result(Client* cli) {
    if (cli == NULL || !cli->in_use) return;

    DWORD bytes_transferred, flags = 0;
    // 获取操作结果(使用WSAGetOverlappedResult获取实际传输字节数)
    if (!WSAGetOverlappedResult(cli->fd, &cli->overlapped, &bytes_transferred, FALSE, &flags)) {
        printf("客户端:%s:%d异常断开(错误码:%d)\n", cli->ip, cli->port, WSAGetLastError());
        cleanup_client(cli);
        return;
    }

    // 处理读操作结果
    if (!cli->is_sending) {
        if (bytes_transferred == 0) {
            printf("客户端:%s:%d正常断开\n", cli->ip, cli->port);
            cleanup_client(cli);
            return;
        }

        cli->read_buf[bytes_transferred] = '\0';
        printf("接收:%s(%d字节)→ 暂存待发送\n", cli->read_buf, (int)bytes_transferred);

        // 准备发送数据
        memcpy(cli->send_buf, cli->read_buf, bytes_transferred);
        cli->send_len = (int)bytes_transferred;
        cli->send_pos = 0;

        // 开始发送
        continue_sending(cli);
    }
    // 处理写操作结果
    else {
        if (bytes_transferred == 0) {
            printf("客户端:%s:%d发送失败\n", cli->ip, cli->port);
            cleanup_client(cli);
            return;
        }

        cli->send_pos += bytes_transferred;
        // 检查是否还有剩余数据
        if (cli->send_pos < cli->send_len) {
            continue_sending(cli);  // 继续发送剩余部分
        }
        else {
            printf("发送:%s(%d字节)→ 发送完成\n", cli->send_buf, cli->send_len);
            cli->is_sending = false;
            cli->send_len = 0;
            cli->send_pos = 0;

            // 重新发起读操作(确保每次发送后都能接收新数据)
            WSABUF recv_buf;
            DWORD bytes_recv;
            recv_buf.buf = cli->read_buf;
            recv_buf.len = BUF_SIZE - 1;
            flags = 0;

            memset(&cli->overlapped, 0, sizeof(WSAOVERLAPPED));
            cli->overlapped.hEvent = cli->event;  // 绑定到独立事件

            if (WSARecv(cli->fd, &recv_buf, 1, &bytes_recv, &flags, &cli->overlapped, NULL) == SOCKET_ERROR) {
                if (WSAGetLastError() != WSA_IO_PENDING) {
                    printf("客户端:%s:%d recv失败: %d\n", cli->ip, cli->port, WSAGetLastError());
                    cleanup_client(cli);
                    return;
                }
            }
        }
    }
}

int main() {
    WSADATA wsaData;
    SOCKET listen_fd = INVALID_SOCKET, client_fd = INVALID_SOCKET;
    struct sockaddr_in server_addr, client_addr;
    int client_addr_len = sizeof(client_addr);

    // 初始化Winsock
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        printf("WSAStartup 失败: %d\n", WSAGetLastError());
        return -1;
    }

    // 初始化客户端列表
    for (int i = 0; i < MAX_CLIENTS; i++) {
        clients[i].in_use = false;
        clients[i].event = WSA_INVALID_EVENT;
    }

    // 创建监听套接字
    listen_fd = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    if (listen_fd == INVALID_SOCKET) {
        printf("创建套接字失败: %d\n", WSAGetLastError());
        WSACleanup();
        return -1;
    }

    // 端口复用
    int reuse = 1;
    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) == SOCKET_ERROR) {
        printf("setsockopt 失败: %d\n", WSAGetLastError());
        closesocket(listen_fd);
        WSACleanup();
        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)) == SOCKET_ERROR) {
        printf("绑定失败: %d\n", WSAGetLastError());
        closesocket(listen_fd);
        WSACleanup();
        return -1;
    }

    // 开始监听
    if (listen(listen_fd, 5) == SOCKET_ERROR) {
        printf("监听失败: %d\n", WSAGetLastError());
        closesocket(listen_fd);
        WSACleanup();
        return -1;
    }

    // 初始化监听事件(0号事件)
    events[0] = WSACreateEvent();
    if (events[0] == WSA_INVALID_EVENT) {
        printf("创建监听事件失败: %d\n", WSAGetLastError());
        closesocket(listen_fd);
        WSACleanup();
        return -1;
    }
    event_count = 1;  // 初始事件数:仅监听事件
    WSAEventSelect(listen_fd, events[0], FD_ACCEPT);

    printf("重叠I/O服务器(IO多路异步)启动:端口=%d\n", PORT);

    // 事件循环
    while (1) {
        // 等待事件(正确处理事件索引)
        DWORD wait_result = WSAWaitForMultipleEvents(event_count, events, FALSE, WSA_INFINITE, FALSE);
        if (wait_result == WSA_WAIT_FAILED) {
            printf("WSAWaitForMultipleEvents 失败: %d\n", WSAGetLastError());
            break;
        }

        int event_idx = wait_result - WSA_WAIT_EVENT_0;
        if (event_idx >= event_count) continue;

        // 重置事件(必须在处理前重置)
        if (!WSAResetEvent(events[event_idx])) {
            printf("WSAResetEvent 失败: %d\n", WSAGetLastError());
            continue;
        }

        // 处理新连接(0号事件)
        if (event_idx == 0) {
            WSANETWORKEVENTS net_events;
            if (WSAEnumNetworkEvents(listen_fd, events[0], &net_events) == SOCKET_ERROR) {
                printf("WSAEnumNetworkEvents 失败: %d\n", WSAGetLastError());
                continue;
            }

            if (net_events.lNetworkEvents & FD_ACCEPT) {
                if (net_events.iErrorCode[FD_ACCEPT_BIT] != 0) {
                    printf("FD_ACCEPT错误: %d\n", net_events.iErrorCode[FD_ACCEPT_BIT]);
                    continue;
                }

                client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len);
                if (client_fd == INVALID_SOCKET) {
                    printf("accept 失败: %d\n", WSAGetLastError());
                    continue;
                }

                int 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));
                    printf("客户端:%s:%d 超出最大限制(%d),拒绝连接\n",
                        client_ip, ntohs(client_addr.sin_port), MAX_CLIENTS);
                    closesocket(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(&clients[slot], client_fd, client_ip, client_port);

                // 添加客户端事件到事件数组
                if (event_count < MAX_CLIENTS + 1) {
                    events[event_count] = clients[slot].event;
                    event_count++;
                }

                // 发起首次读操作
                WSABUF wsa_buf;
                DWORD bytes_recv, flags = 0;
                wsa_buf.buf = clients[slot].read_buf;
                wsa_buf.len = BUF_SIZE - 1;

                memset(&clients[slot].overlapped, 0, sizeof(WSAOVERLAPPED));
                clients[slot].overlapped.hEvent = clients[slot].event;  // 绑定事件

                if (WSARecv(client_fd, &wsa_buf, 1, &bytes_recv, &flags,
                    &clients[slot].overlapped, NULL) == SOCKET_ERROR) {
                    if (WSAGetLastError() != WSA_IO_PENDING) {
                        printf("recv 失败: %d\n", WSAGetLastError());
                        cleanup_client(&clients[slot]);
                        event_count--;
                        continue;
                    }
                }

                printf("新客户端连接:%s:%d\n", client_ip, client_port);
            }
        }
        // 处理客户端事件(1~MAX_CLIENTS号事件)
        else {
            // 查找事件对应的客户端(正确匹配事件与客户端)
            int cli_idx = -1;
            for (int i = 0; i < MAX_CLIENTS; i++) {
                if (clients[i].in_use && clients[i].event == events[event_idx]) {
                    cli_idx = i;
                    break;
                }
            }
            if (cli_idx == -1) {
                // 移除无效事件(清理不再使用的事件)
                for (int i = event_idx; i < event_count - 1; i++) {
                    events[i] = events[i + 1];
                }
                event_count--;
                continue;
            }

            // 处理I/O结果
            process_overlapped_result(&clients[cli_idx]);

            // 检查客户端是否已断开,更新事件数组
            if (!clients[cli_idx].in_use) {
                for (int i = event_idx; i < event_count - 1; i++) {
                    events[i] = events[i + 1];
                }
                event_count--;
            }
        }
    }

    // 资源清理
    for (int i = 0; i < event_count; i++) {
        WSACloseEvent(events[i]);
    }
    closesocket(listen_fd);
    WSACleanup();
    return 0;
}
I/O 完成端口(IOCP):超高并发的性能最优解

核心机制:通过 "完成端口对象 + 工作线程池" 实现超高并发。在回声服务器中,先调用 CreateIoCompletionPort 创建完成端口,并将监听 Socket 及新接入的所有客户端 Socket 绑定到该端口;发起异步 WSARecv(传入封装了操作类型、缓冲区的 OverlappedOp 结构体)后,工作线程池中的线程通过 GetQueuedCompletionStatus 阻塞等待;当回声读写完成,线程直接获取事件(含客户端 Socket、传输字节数、操作类型),读完成则暂存数据并发起异步 WSASend,写完成则再次发起 WSARecv,循环执行回声逻辑。

与 Linux 对比:Linux 无直接对应模型,功能上接近 io_uring 但效率更高, epoll 仅负责 "就绪事件通知",需用户态遍历事件列表、手动发起 I/O 操作;且无原生线程池,需自行实现线程管理(如 pthread 线程池),高并发下存在用户态逻辑开销; io_uring 虽支持异步 I/O 与内核队列,减少了部分用户态干预,但生态不成熟(如驱动支持有限、库适配少),且线程调度需用户态干预;IOCP 由 Windows 内核深度整合 "完成队列 + 工作线程池",I/O 操作触发、结果回调、线程调度均由内核优化(如线程数自动匹配 CPU 核心),无需用户态额外处理;在 10 万连接的高并发场景下,指令处理吞吐量比 Linux epoll 高 20%-30%,且稳定性更优。

适用场景:十万级超高并发场景,如 MMO 游戏主服(需支撑海量玩家同时在线、跨服交互)、大型赛事服务器(需低延迟处理战斗数据)。

ini 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <winsock2.h>
#include <windows.h>
#include <process.h>
#include <stdbool.h>
#include <ws2tcpip.h>

#pragma comment(lib, "ws2_32.lib")

#define PORT 5150
#define BUF_SIZE 1024
#define MAX_CLIENTS 1024
#define WORKER_THREAD_COUNT 4
#define IP_LEN 16

// 操作类型枚举
typedef enum {
    OP_READ,
    OP_WRITE
} OpType;

// 重叠操作结构体
typedef struct {
    WSAOVERLAPPED overlapped;
    WSABUF wsa_buf;
    OpType op_type;
    SOCKET fd;
    int client_idx;
} OverlappedOp;

// 客户端结构体
typedef struct {
    SOCKET fd;                  // 客户端套接字
    char ip[IP_LEN];            // 客户端IP地址
    int port;                   // 客户端端口
    char read_buf[BUF_SIZE];    // 读缓冲区
    char send_buf[BUF_SIZE];    // 发送缓冲区
    int send_len;               // 待发送长度
    struct sockaddr_in addr;    // 客户端地址
    bool in_use;                // 连接状态
} Client;

Client clients[MAX_CLIENTS];
HANDLE iocp_handle;            // IOCP句柄

// 初始化客户端
void init_client(Client* cli) {
    cli->fd = INVALID_SOCKET;
    cli->send_len = 0;
    cli->in_use = false;
    memset(cli->ip, 0, IP_LEN);
    cli->port = 0;
    memset(cli->read_buf, 0, BUF_SIZE);
    memset(cli->send_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 (!clients[i].in_use) {
            return i;
        }
    }
    return -1;
}

// 工作线程函数(处理IOCP完成事件)
unsigned int __stdcall worker_thread(void* arg) {
    DWORD bytes_transferred;
    ULONG_PTR completion_key;
    OverlappedOp* op;
    Client* cli;
    int last_error;

    while (1) {
        // 获取完成事件
        if (GetQueuedCompletionStatus(iocp_handle, &bytes_transferred,
            &completion_key, (LPOVERLAPPED*)&op, INFINITE) == 0) {

            last_error = GetLastError();
            // 处理客户端异常断开(错误码64与select的异常断开同步)
            if (last_error == ERROR_NETNAME_DELETED && op != NULL) {
                int client_idx = op->client_idx;
                if (client_idx >= 0 && client_idx < MAX_CLIENTS) {
                    cli = &clients[client_idx];
                    if (cli->in_use && cli->fd == op->fd) {
                        // 与select版本完全一致的异常断开提示
                        printf("客户端:%s:%d异常断开\n", cli->ip, cli->port);
                        closesocket(cli->fd);
                        init_client(cli);
                    }
                }
                free(op);
                continue;
            }
            // 其他错误保持原有提示格式
            printf("GetQueuedCompletionStatus 失败: %d\n", last_error);
            continue;
        }

        // 处理关闭通知
        if (completion_key == 0 && bytes_transferred == 0) {
            break;
        }

        int client_idx = op->client_idx;
        if (client_idx < 0 || client_idx >= MAX_CLIENTS) {
            free(op);
            continue;
        }

        cli = &clients[client_idx];
        if (!cli->in_use || cli->fd != op->fd) {
            free(op);
            continue;
        }

        // 处理读操作完成
        if (op->op_type == OP_READ) {
            if (bytes_transferred == 0) {
                // 正常断开提示(与select一致)
                printf("客户端:%s:%d正常断开\n", cli->ip, cli->port);
                closesocket(cli->fd);
                init_client(cli);
                free(op);
                continue;
            }

            cli->read_buf[bytes_transferred] = '\0';
            cli->send_len = bytes_transferred;
            memcpy(cli->send_buf, cli->read_buf, bytes_transferred);
            printf("接收:%s(%d字节)→ 暂存待发送\n", cli->read_buf, (int)bytes_transferred);

            // 发起写操作
            OverlappedOp* write_op = (OverlappedOp*)malloc(sizeof(OverlappedOp));
            memset(write_op, 0, sizeof(OverlappedOp));
            write_op->op_type = OP_WRITE;
            write_op->fd = cli->fd;
            write_op->client_idx = client_idx;
            write_op->wsa_buf.buf = cli->send_buf;
            write_op->wsa_buf.len = cli->send_len;

            DWORD bytes_sent;
            if (WSASend(cli->fd, &write_op->wsa_buf, 1, &bytes_sent, 0,
                (LPWSAOVERLAPPED)write_op, NULL) == SOCKET_ERROR) {
                if (WSAGetLastError() != WSA_IO_PENDING) {
                    printf("WSASend 失败: %d\n", WSAGetLastError());
                    closesocket(cli->fd);
                    init_client(cli);
                    free(write_op);
                }
            }
        }

        // 处理写操作完成
        else if (op->op_type == OP_WRITE) {
            if (bytes_transferred <= 0) {
                printf("客户端:%s:%d发送失败\n", cli->ip, cli->port);
                closesocket(cli->fd);
                init_client(cli);
                free(op);
                continue;
            }

            printf("发送:%s(%d字节)→ 发送完成\n", cli->send_buf, (int)bytes_transferred);
            cli->send_len = 0;

            // 发起读操作,支持多次通信
            OverlappedOp* read_op = (OverlappedOp*)malloc(sizeof(OverlappedOp));
            memset(read_op, 0, sizeof(OverlappedOp));
            read_op->op_type = OP_READ;
            read_op->fd = cli->fd;
            read_op->client_idx = client_idx;
            read_op->wsa_buf.buf = cli->read_buf;
            read_op->wsa_buf.len = BUF_SIZE - 1;

            DWORD bytes_recv, flags = 0;
            if (WSARecv(cli->fd, &read_op->wsa_buf, 1, &bytes_recv, &flags,
                (LPWSAOVERLAPPED)read_op, NULL) == SOCKET_ERROR) {
                if (WSAGetLastError() != WSA_IO_PENDING) {
                    printf("WSARecv 失败: %d\n", WSAGetLastError());
                    closesocket(cli->fd);
                    init_client(cli);
                    free(read_op);
                }
            }
        }

        free(op);
    }

    return 0;
}

int main() {
    WSADATA wsaData;
    SOCKET listen_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    int client_addr_len = sizeof(client_addr);
    HANDLE worker_threads[WORKER_THREAD_COUNT];

    // 初始化Winsock
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        printf("WSAStartup 失败: %d\n", WSAGetLastError());
        return -1;
    }

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

    // 创建IOCP
    iocp_handle = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
    if (iocp_handle == NULL) {
        printf("CreateIoCompletionPort 失败: %d\n", GetLastError());
        return -1;
    }

    // 创建工作线程
    for (int i = 0; i < WORKER_THREAD_COUNT; i++) {
        worker_threads[i] = (HANDLE)_beginthreadex(NULL, 0, worker_thread, NULL, 0, NULL);
        if (worker_threads[i] == NULL) {
            printf("创建工作线程 失败\n");
            return -1;
        }
    }

    // 创建监听套接字
    listen_fd = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    if (listen_fd == INVALID_SOCKET) {
        printf("创建套接字 失败: %d\n", WSAGetLastError());
        WSACleanup();
        return -1;
    }

    // 端口复用
    int reuse = 1;
    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) == SOCKET_ERROR) {
        printf("setsockopt 失败: %d\n", WSAGetLastError());
        closesocket(listen_fd);
        WSACleanup();
        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)) == SOCKET_ERROR) {
        printf("绑定失败: %d\n", WSAGetLastError());
        closesocket(listen_fd);
        WSACleanup();
        return -1;
    }

    // 开始监听
    if (listen(listen_fd, 5) == SOCKET_ERROR) {
        printf("监听失败: %d\n", WSAGetLastError());
        closesocket(listen_fd);
        WSACleanup();
        return -1;
    }

    // 将监听套接字关联到IOCP
    CreateIoCompletionPort((HANDLE)listen_fd, iocp_handle, (ULONG_PTR)listen_fd, 0);

    // 启动提示
    printf("IOCP服务器(IO完成端口)启动:端口=%d\n", PORT);

    // 接受连接循环
    while (1) {
        client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len);
        if (client_fd == INVALID_SOCKET) {
            printf("accept 失败: %d\n", WSAGetLastError());
            Sleep(10);
            continue;
        }

        int 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));
            printf("客户端:%s:%d 超出最大限制(%d),拒绝连接\n",
                client_ip, ntohs(client_addr.sin_port), MAX_CLIENTS);
            closesocket(client_fd);
            continue;
        }

        // 初始化客户端信息
        Client* new_cli = &clients[slot];
        new_cli->fd = client_fd;
        new_cli->addr = client_addr;
        new_cli->in_use = true;
        inet_ntop(AF_INET, &client_addr.sin_addr, new_cli->ip, IP_LEN - 1);
        new_cli->port = ntohs(client_addr.sin_port);

        // 将客户端套接字关联到IOCP
        if (CreateIoCompletionPort((HANDLE)client_fd, iocp_handle, (ULONG_PTR)client_fd, 0) == NULL) {
            printf("关联IOCP 失败: %d\n", GetLastError());
            closesocket(client_fd);
            init_client(new_cli);
            continue;
        }

        // 发起第一个读操作
        OverlappedOp* read_op = (OverlappedOp*)malloc(sizeof(OverlappedOp));
        memset(read_op, 0, sizeof(OverlappedOp));
        read_op->op_type = OP_READ;
        read_op->fd = client_fd;
        read_op->client_idx = slot;
        read_op->wsa_buf.buf = new_cli->read_buf;
        read_op->wsa_buf.len = BUF_SIZE - 1;

        DWORD bytes_recv, flags = 0;
        if (WSARecv(client_fd, &read_op->wsa_buf, 1, &bytes_recv, &flags,
            (LPWSAOVERLAPPED)read_op, NULL) == SOCKET_ERROR) {
            if (WSAGetLastError() != WSA_IO_PENDING) {
                printf("WSARecv 失败: %d\n", WSAGetLastError());
                closesocket(client_fd);
                init_client(new_cli);
                free(read_op);
                continue;
            }
        }

        // 新连接提示
        printf("新客户端连接:%s:%d\n", new_cli->ip, new_cli->port);
    }

    // 清理资源
    for (int i = 0; i < WORKER_THREAD_COUNT; i++) {
        PostQueuedCompletionStatus(iocp_handle, 0, 0, NULL);
        WaitForSingleObject(worker_threads[i], INFINITE);
        CloseHandle(worker_threads[i]);
    }
    closesocket(listen_fd);
    CloseHandle(iocp_handle);
    WSACleanup();
    return 0;
}

总结:Windows Socket 六种 I/O 模型核心要点

Windows Socket 六种 I/O 模型(阻塞 I/O、非阻塞 I/O、select、WSAEventSelect、WSAAsyncSelect、重叠 I/O、I/O 完成端口)的核心差异,源于对 "等待数据就绪" 与 "数据拷贝" 两阶段的阻塞策略、事件通知方式设计不同(阻塞 I/O 两阶段均阻塞,实现简单;非阻塞 I/O 等待阶段不阻塞却需轮询,CPU 消耗高;select 兼容旧系统但存在 Socket 数量限制;WSAEventSelect 依托内核事件驱动,效率优于 select;WSAAsyncSelect 基于 Windows 窗口消息传递事件,专为 GUI 程序设计;重叠 I/O 实现全异步且由内核托管流程;I/O 完成端口(IOCP)通过 "完成端口对象 + 内核线程池" 深度优化,性能最优)。

相关推荐
小码编匠2 小时前
开箱即用!集成 YOLO+OpenCV+OCR 的 WebAI 平台(支持RTSP/RTMP视频流识别与自训练)
spring boot·后端·opencv
文心快码BaiduComate2 小时前
再获殊荣!文心快码荣膺2025年度优秀软件产品!
前端·后端·代码规范
天天摸鱼的java工程师3 小时前
SpringBoot + RabbitMQ + Redis + MySQL:社交平台私信发送、已读状态同步与历史消息缓存
java·后端
Kiri霧3 小时前
Rust数组与向量
开发语言·后端·rust
特立独行的猫a3 小时前
Rust语言入门难,难在哪?所有权、借用检查器、生命周期和泛型介绍
开发语言·后端·rust
间彧3 小时前
Spring Boot Actuator详解:生产级监控与管理工具
后端
开心猴爷3 小时前
Nginx HTTPS 深入实战 配置、性能与排查全流程(Nginx https
后端
舒一笑3 小时前
TorchV知识库安全解决方案:基于智能环境感知的动态权限控制
后端·安全·掘金技术征文
企鹅虎3 小时前
2024年11月郑房新软考中级系统集成项目管理工程师课程 百度网盘
后端