一、多线程模型的瓶颈

1. 线程资源消耗

  • 内存开销:每个线程默认占用1MB栈空间(Windows),数千连接时内存压力大。
  • 切换开销:频繁线程上下文切换(Context Switch)导致CPU利用率下降。

2. 并发限制

  • 硬性上限:即使使用线程池,万级并发仍难以实现(如实时游戏服务器)。

二、异步IO模型概述

1. 同步 vs 异步

  • 同步IO:send()/recv()阻塞当前线程,直到操作完成。
  • 异步IO:操作提交后立即返回,通过回调或事件通知处理结果。

2. 事件驱动模型

  • 核心思想:单线程(或少量线程)通过事件循环处理多个连接。
  • 实现方式
    a) ​ select():跨平台,但性能较低(O(n)遍历)。
    ​b) IOCP(完成端口):Windows专属,高性能(基于内核队列)。
    ​c) epoll(Linux)/ kqueue(macOS)​:类Unix系统的高效方案。

三、使用 select() 实现事件驱动服务器

1. select() 原理

  • 功能:监视一组Socket的可读/可写/错误状态。
  • 流程
1. 创建Socket并设为非阻塞模式。
2. 将Socket加入fd_set集合。
3. 调用select()等待事件。
4. 遍历集合处理就绪的Socket。

2. 完整代码示例

#include <iostream>
#include <winsock2.h>
#include <vector>
#pragma comment(lib, "ws2_32.lib")

int main() {
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    sockaddr_in serverAddr{};
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddr.sin_port = htons(8080);

    bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr));
    listen(serverSocket, SOMAXCONN);

    // 设置非阻塞模式
    u_long mode = 1;
    ioctlsocket(serverSocket, FIONBIO, &mode);

    std::vector<SOCKET> clients;
    fd_set readSet;

    std::cout << "select() server running..." << std::endl;

    while (true) {
        FD_ZERO(&readSet);
        FD_SET(serverSocket, &readSet);
        for (SOCKET s : clients) FD_SET(s, &readSet);

        // 等待事件(设置超时为1秒)
        timeval timeout{ 1, 0 };
        int count = select(0, &readSet, nullptr, nullptr, &timeout);
        if (count == SOCKET_ERROR) {
            std::cerr << "select failed: " << WSAGetLastError() << std::endl;
            break;
        }

        if (count == 0) continue;  // 超时

        // 处理新连接
        if (FD_ISSET(serverSocket, &readSet)) {
            sockaddr_in clientAddr{};
            int addrLen = sizeof(clientAddr);
            SOCKET clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, &addrLen);
            if (clientSocket != INVALID_SOCKET) {
                ioctlsocket(clientSocket, FIONBIO, &mode);  // 非阻塞
                clients.push_back(clientSocket);
                std::cout << "New client connected: " << clients.size() << std::endl;
            }
        }

        // 处理客户端数据
        for (auto it = clients.begin(); it != clients.end();) {
            SOCKET s = *it;
            if (FD_ISSET(s, &readSet)) {
                char buffer[1024];
                int bytesRead = recv(s, buffer, sizeof(buffer), 0);
                if (bytesRead > 0) {
                    std::cout << " received " << bytesRead << " bytes." << std::endl;
                    send(s, buffer, bytesRead, 0);  // Echo回传
                }
                else {
                    closesocket(s);
                    it = clients.erase(it);
                    std::cout << "Client disconnected. Remaining: " << clients.size() << std::endl;
                    continue;
                }
            }
            ++it;
        }
    }

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

3. 测试结果

在这里插入图片描述


四、Windows完成端口(IOCP)​

1. IOCP核心机制

  • 完成队列:内核维护操作队列,线程通过GetQueuedCompletionStatus获取完成事件。
  • 线程池:多个工作线程并行处理事件,无需频繁切换上下文。

2. IOCP服务器框架

#include <iostream>
#include <winsock2.h>
#include <windows.h>
#include <vector>
#pragma comment(lib, "ws2_32.lib")

#define MAX_THREADS 4

// I/O操作上下文结构
struct PerIoData {
    WSAOVERLAPPED overlapped;
    SOCKET socket;
    WSABUF wsaBuf;
    char buffer[1024];
    bool isSend;
};

// 单例IOCP管理器
class IOCPManager {
public:
    static HANDLE GetInstance() {
        static HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
        return hCompletionPort;
    }
};

DWORD WINAPI WorkerThread(LPVOID lpParam) {
    HANDLE hCompletionPort = IOCPManager::GetInstance();
    DWORD bytesTransferred;
    ULONG_PTR completionKey;
    LPOVERLAPPED overlapped;

    while (true) {
        BOOL result = GetQueuedCompletionStatus(
            hCompletionPort, &bytesTransferred, &completionKey, &overlapped, INFINITE);

        SOCKET clientSocket = (SOCKET)completionKey;
        if (!result || (bytesTransferred == 0 && overlapped == NULL)) {
            std::cerr << "Client disconnected." << std::endl;
            closesocket(clientSocket);
            continue;
        }

        if (overlapped) {
            PerIoData* ioData = CONTAINING_RECORD(overlapped, PerIoData, overlapped);

            if (ioData->isSend) {
                // 发送完成,释放资源
                delete ioData;
                continue;
            }

            // 打印接收数据
            std::cout << "Received from client (" << bytesTransferred << " bytes): ";
            std::cout.write(ioData->buffer, bytesTransferred);
            std::cout << std::endl;

            // 准备回显数据
            PerIoData* sendData = new PerIoData;
            ZeroMemory(&sendData->overlapped, sizeof(WSAOVERLAPPED));
            sendData->socket = clientSocket;
            sendData->wsaBuf.buf = ioData->buffer;
            sendData->wsaBuf.len = bytesTransferred;
            sendData->isSend = true;

            // 异步发送
            if (WSASend(clientSocket, &sendData->wsaBuf, 1, NULL, 0, &sendData->overlapped, NULL) == SOCKET_ERROR) {
                if (WSAGetLastError() != WSA_IO_PENDING) {
                    std::cerr << "WSASend failed: " << WSAGetLastError() << std::endl;
                    delete sendData;
                }
            }

            // 重新投递接收请求
            PerIoData* newRecv = new PerIoData;
            ZeroMemory(&newRecv->overlapped, sizeof(WSAOVERLAPPED));
            newRecv->socket = clientSocket;
            newRecv->wsaBuf.buf = newRecv->buffer;
            newRecv->wsaBuf.len = sizeof(newRecv->buffer);
            newRecv->isSend = false;
            DWORD flags = 0;
            if (WSARecv(clientSocket, &newRecv->wsaBuf, 1, NULL, &flags, &newRecv->overlapped, NULL) == SOCKET_ERROR) {
                if (WSAGetLastError() != WSA_IO_PENDING) {
                    std::cerr << "WSARecv failed: " << WSAGetLastError() << std::endl;
                    delete newRecv;
                }
            }

            delete ioData;
        }
    }
    return 0;
}

int main() {
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    // 创建工作线程
    std::vector<HANDLE> threads;
    for (int i = 0; i < MAX_THREADS; ++i) {
        threads.push_back(CreateThread(NULL, 0, WorkerThread, NULL, 0, NULL));
    }

    // 创建服务器Socket
    SOCKET serverSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    sockaddr_in serverAddr{};
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddr.sin_port = htons(8080);

    bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr));
    listen(serverSocket, SOMAXCONN);

    // 关联完成端口
    HANDLE hCompletionPort = IOCPManager::GetInstance();
    CreateIoCompletionPort((HANDLE)serverSocket, hCompletionPort, (ULONG_PTR)serverSocket, 0);

    std::cout << "Server started on port 8080..." << std::endl;

    while (true) {
        SOCKET clientSocket = accept(serverSocket, NULL, NULL);
        CreateIoCompletionPort((HANDLE)clientSocket, hCompletionPort, (ULONG_PTR)clientSocket, 0);

        // 初始化接收请求
        PerIoData* recvData = new PerIoData;
        ZeroMemory(&recvData->overlapped, sizeof(WSAOVERLAPPED));
        recvData->socket = clientSocket;
        recvData->wsaBuf.buf = recvData->buffer;
        recvData->wsaBuf.len = sizeof(recvData->buffer);
        recvData->isSend = false;
        DWORD flags = 0;
        // 投递异步接收
        if (WSARecv(clientSocket, &recvData->wsaBuf, 1, NULL, &flags, &recvData->overlapped, NULL) == SOCKET_ERROR) {
            if (WSAGetLastError() != WSA_IO_PENDING) {
                std::cerr << "WSARecv failed: " << WSAGetLastError() << std::endl;
                delete recvData;
            }
        }
    }

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

3. 测试结果

在这里插入图片描述


五、关键对比与适用场景

​模型 ​优点 ​缺点​ 适用场景
多线程 逻辑简单,易于调试 资源消耗大,并发上限低 低频短连接(如HTTP API)
select() 跨平台,代码简单 性能差(O(n)遍历),仅支持1024连接 低并发测试
IOCP 高性能,支持超大规模并发 Windows专属,代码复杂 实时游戏、高频交易
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐