Windows IOCP 示例
2024-12-18 20:03:48+08:00

Windows IOCP 示例

这个示将接收到的数据原样返回给客户端:

#include <WinSock2.h>
#include <WS2tcpip.h>
#include <MSWSock.h>
#include <cstdio>

#define BUFFER_SIZE 1024

enum class IocpType
{
    ACCEPT,
    RECV,
    SEND
};

struct IocpContext : public WSAOVERLAPPED
{
    IocpContext() : 
        WSAOVERLAPPED{}
    {

    }

    WSABUF wsaBuf{BUFFER_SIZE, buffer};
    char buffer[BUFFER_SIZE];
    SOCKET sock;
    IocpType type;
};

// 提交一个异步的 accept 操作
bool postAccpet(SOCKET server)
{
    // 创建上下文
    IocpContext* ctx = new IocpContext;
    ctx->type = IocpType::ACCEPT;

    // 创建接收连接的socket
    ctx->sock = WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, nullptr, 0, WSA_FLAG_OVERLAPPED);
    if (ctx->sock == SOCKET_ERROR)
    {
        fprintf(stderr, "WSASocketW: %s\n", strerror(WSAGetLastError()));
        closesocket(ctx->sock);
        return false;
    }

    // 加载 AcceptEX 函数
    LPFN_ACCEPTEX lpfnAcceptEx = NULL;
    GUID GuidAcceptEx = WSAID_ACCEPTEX;
    DWORD dwBytes;
    {
        int ret = WSAIoctl(server, SIO_GET_EXTENSION_FUNCTION_POINTER,
                            &GuidAcceptEx, sizeof (GuidAcceptEx), 
                            &lpfnAcceptEx, sizeof (lpfnAcceptEx), 
                            &dwBytes, NULL, NULL);
        if (ret == SOCKET_ERROR)
        {
            fprintf(stderr, "WSAIoctl: %s\n", strerror(WSAGetLastError()));
            closesocket(ctx->sock);
            return false;
        }
    }
    
    // 通过 AcceptEx 发起异步的 accept 操作
    {
        DWORD addrlen = sizeof(struct sockaddr_in);
        DWORD recvlen;
        BOOL ret = lpfnAcceptEx(server, 
                                ctx->sock, 
                                ctx->buffer, 
                                BUFFER_SIZE - 2*(addrlen+16), 
                                addrlen + 16, 
                                addrlen + 16, 
                                &recvlen, 
                                ctx);
        if (!ret && WSAGetLastError() != ERROR_IO_PENDING)
        {
            fprintf(stderr, "AcceptEx: %s\n", strerror(WSAGetLastError()));
            closesocket(ctx->sock);
            return false;
        }
    }

    return true;
}

// 提交一个异步的 RECV 操作
bool postRecv(SOCKET sock)
{
    IocpContext* ctx = new IocpContext;
    ctx->sock = sock;
    ctx->type = IocpType::RECV;
    DWORD nBytes = BUFFER_SIZE;
    DWORD flags = 0;
    int ret = WSARecv(sock, &(ctx->wsaBuf), 1, &nBytes, &flags, ctx, nullptr);
    if (ret == SOCKET_ERROR && WSAGetLastError() != ERROR_IO_PENDING)
    {
        fprintf(stderr, "WSARecv: %s\n", strerror(WSAGetLastError()));
        return false;
    }

    return true;
}

// 提交一个异步的 SEND 操作
bool postSend(SOCKET sock, const char* data, DWORD size)
{
    IocpContext* ctx = new IocpContext;
    ctx->sock = sock;
    ctx->type = IocpType::SEND;
    memcpy(ctx->buffer, data, size);
    DWORD nBytes = size;
    ctx->wsaBuf.len = size;
    DWORD flags = 0;
    int ret = WSASend(sock, &(ctx->wsaBuf), 1, &nBytes, flags, ctx, nullptr);
    if (ret == SOCKET_ERROR && WSAGetLastError() != ERROR_IO_PENDING)
    {
        fprintf(stderr, "WSASend: %s\n", strerror(WSAGetLastError()));
        return false;
    }

    return true;
}

int main()
{
    WSAData wsa;
    if (WSAStartup(0x202, &wsa) != NO_ERROR)
    {
        fprintf(stderr, "WSAStartup failed\n");
        return EXIT_FAILURE;
    }

    // 创建服务socket
    SOCKET server = WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, nullptr, 0, WSA_FLAG_OVERLAPPED);
    if (server == INVALID_SOCKET)
    {
        fprintf(stderr, "WSASocketW: %s\n", strerror(WSAGetLastError()));
        WSACleanup();
        return EXIT_FAILURE;
    }
    
    // 设为非阻塞
    unsigned long value = 1;
    if (ioctlsocket(server, FIONBIO, &value) == SOCKET_ERROR)
    {
        fprintf(stderr, "ioctlsocket: %s\n", strerror(WSAGetLastError()));
        closesocket(server);
        WSACleanup();
        return EXIT_FAILURE;
    }

    // 绑定端口
    struct sockaddr_in address {};
    address.sin_family = AF_INET;
    address.sin_port = htons(8080);
    inet_pton(AF_INET, "127.0.0.1", &address.sin_addr);
    if (bind(server, (const sockaddr*)(&address), sizeof(address)) == SOCKET_ERROR)
    {
        fprintf(stderr, "bind: %s\n", strerror(WSAGetLastError()));
        closesocket(server);
        WSACleanup();
        return EXIT_FAILURE;
    }

    // 监听
    if (listen(server, SOMAXCONN) == SOCKET_ERROR)
    {
        fprintf(stderr, "listen: %s\n", strerror(WSAGetLastError()));
        closesocket(server);
        WSACleanup();
        return EXIT_FAILURE;
    }

    // 创建 IOCP handle
    HANDLE handle = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 0);
    if (handle == INVALID_HANDLE_VALUE)
    {
        fprintf(stderr, "CreateIoCompletionPort: %s\n", strerror(WSAGetLastError()));
        closesocket(server);
        WSACleanup();
        return EXIT_FAILURE;
    }

    // 将 server 绑定到 IOCP 上
    if (CreateIoCompletionPort(reinterpret_cast<HANDLE>(server), handle, 0, 0) == nullptr)
    {
        fprintf(stderr, "CreateIoCompletionPort: %s\n", strerror(WSAGetLastError()));
        closesocket(server);
        WSACleanup();
        return EXIT_FAILURE;
    }

    // 提交一个异步的 accept 操作
    if (postAccpet(server) == false)
    {
        closesocket(server);
        WSACleanup();
        return EXIT_FAILURE;
    }

    while (true)
    {
        // 等待操作完成
        IocpContext* ctx = nullptr;
        DWORD lpNumberOfBytesTransferred = 0;
        void* lpCompletionKey = nullptr;
        {
            BOOL ret = GetQueuedCompletionStatus(
                        handle,
                        &lpNumberOfBytesTransferred,
                        (PULONG_PTR) &lpCompletionKey,
                        (LPOVERLAPPED *) &ctx,
                        INFINITE);
            if (!ret)
                continue;
        }
        
        // 处理 ACCEPT 操作
        if (ctx->type == IocpType::ACCEPT)
        {   
            // 重新发起一个异步的 accept 操作,接收下一个连接
            postAccpet(server);

            // 将连接设为非阻塞
            unsigned long value = 1;
            if (ioctlsocket(ctx->sock, FIONBIO, &value) == SOCKET_ERROR)
            {
                fprintf(stderr, "ioctlsocket: %s\n", strerror(WSAGetLastError()));
                closesocket(ctx->sock);
                delete ctx;
                continue;
            }

            // 将连接绑定到 IOCP 上
            if (CreateIoCompletionPort(reinterpret_cast<HANDLE>(ctx->sock), handle, 0, 0) == nullptr)
            {
                fprintf(stderr, "CreateIoCompletionPort: %s\n", strerror(WSAGetLastError()));
                closesocket(ctx->sock);
                delete ctx;
                continue;
            }

            // 发起一个异步的 send 操作,NOTE: AcceptEx 会读取第一帧数据
            if (lpNumberOfBytesTransferred > 0)
                postSend(ctx->sock, ctx->buffer, lpNumberOfBytesTransferred);

            // 发起一个异步的 recv 操作,接受后续数据
            if (postRecv(ctx->sock) == false)
            {
                closesocket(ctx->sock);
                delete ctx;
                continue;
            }

            delete ctx;
            continue;
        }

        // 处理 RECV 操作
        if (ctx->type == IocpType::RECV)
        {
            // 连接出错或断开
            if (lpNumberOfBytesTransferred <= 0)
            {
                closesocket(ctx->sock);
                delete ctx;
                continue;
            }

            // 发起一个异步的 send 操作
            postSend(ctx->sock, ctx->buffer, lpNumberOfBytesTransferred);

            // 发起一个异步的 recv 操作,接收后续数据
            if (postRecv(ctx->sock) == false)
            {
                closesocket(ctx->sock);
                delete ctx;
                continue;
            }

            delete ctx;
            continue;
        }

        // 处理 SEND 操作
        if (ctx->type == IocpType::SEND)
        {
            delete ctx;
            continue;
        }
    }
}

使用 Apache HTTP server benchmarking tool 进行测试,结果如下:

Server Software:
Server Hostname:        localhost
Server Port:            8080

Document Path:          /
Document Length:        0 bytes

Concurrency Level:      10000
Time taken for tests:   207.340 seconds
Complete requests:      10000000
Failed requests:        0
Non-2xx responses:      10000000
Keep-Alive requests:    10000000
Total transferred:      1060000000 bytes
HTML transferred:       0 bytes
Requests per second:    48229.98 [#/sec] (mean)
Time per request:       207.340 [ms] (mean)
Time per request:       0.021 [ms] (mean, across all concurrent requests)
Transfer rate:          4992.56 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0      16
Processing:   114  205  19.3    204    1214
Waiting:        0  205  19.3    204    1214
Total:        114  205  19.3    204    1214

Percentage of the requests served within a certain time (ms)
  50%    204
  66%    205
  75%    206
  80%    206
  90%    208
  95%    210
  98%    212
  99%    213
 100%   1214 (longest request)
Windows 程序删除 CMD 窗口
2024-12-18 20:03:48+08:00

Windows 程序删除 CMD 窗口

需要将项目类型设为桌面程序。此时入口函数默认为 WinMainCRTStartup,它会在初始化后调用 WinMain 而不是 main。 可以将入口函数修改为 mainCRTStartup 来调研 main

VS 的链接选项:

:/SUBSYSTEM:windows;/ENTRY:mainCRTStartup

MinGW 的链接选项:

-mwindows -lmingw32