IOCP实现异步IO

        IOCP是管理多套接字,并异步进行IO动作执行,且配备了线程池支持的网络通信模型。当IO通知时,线程池中的工作线程作为回调任务,这比epoll 只是通知多个套接字IO状态完善的多,epoll没有线程池支持,只有事件通知。

        使用步骤:

1:初始化winsock库,创建套接字绑定地址,

inline void InitWinsock()
{
    // 初始化WINSOCK
    WSADATA wsd;
    if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
    {
        OutErr("WSAStartup()");
    }
}

2:获取CPU核心数确定工作线程数量。CPU*2

SYSTEM_INFO SystemInfo;
GetSystemInfo(&SystemInfo);
int thNum = SystemInfo.dwNumberOfProcessors;

3: 创建完成端口。完成端口用来管理线程池和客户端套接字

HANDLE CompletionPort = 
CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, thNum);

3 :创建工作线程,在工作线程内循环获取已经排队的完成端口状态,这些状态包括:传输了多少字节,套接字句柄附带数据(也叫套接字重叠结构),每次IO操作的附带数据(IO重叠结构)。当获取了这些状态后,可以将IO数据拷贝走(这块内存还要以后接收数据呢),继续进行异步读写操作。(标注了?的部分,经测试,都是性能拉跨的地方。标准输出一定关掉)

//工作线程数目=系统进程数目的两倍.
for (DWORD i = 0; i < SystemInfo.dwNumberOfProcessors; i++)
{
    HANDLE hProcessIO = CreateThread(NULL, 0, ProcessIO, 
                                      CompletionPort, 0, NULL);
    if (hProcessIO)
    {
        CloseHandle(hProcessIO);
    }   
}

//工作线程 CPU * 2
DWORD WINAPI ProcessIO(LPVOID lpParam)
{
    HANDLE CompletionPort = (HANDLE)lpParam;
    DWORD BytesTransferred;
    LPPER_HANDLE_DATA PerHandleData  = nullptr;
    LPPER_IO_OPERATION_DATA PerIoData = nullptr;

    while (true)
    {
        //?
        //完成端口;已传输字节;套接字标识数据块;io标识的重叠结构;超时时间;没有完成的IO就阻塞自己;
        //等待数据的事件很长
        if (0 == GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, 
            (PULONG_PTR)&PerHandleData, (LPOVERLAPPED*)&PerIoData, INFINITE))
        {
            if ((GetLastError() == WAIT_TIMEOUT) || (GetLastError() == ERROR_NETNAME_DELETED))
            {
                //?
                //cout << "closingsocket:" << PerHandleData->Socket << endl;
                closesocket(PerHandleData->Socket);

                delete PerIoData;
                delete PerHandleData;
                continue;
            }
            else
            {//?
                //OutErr("GetQueuedCompletionStatus failed!");
            }
            return 0;
        }

        // 说明客户端已经退出
        if (BytesTransferred == 0)
        {
            //?
            //cout << "closing socket " << PerHandleData->Socket << endl;
            closesocket(PerHandleData->Socket);
            delete PerIoData;
            delete PerHandleData;
            continue;
        }

        string data(PerIoData->Buffer, BytesTransferred);//裸指针字符串是没有长度的,没有\0的话,调试时可能会显示无效内存,建议用string类
        // 继续向 socket 投递WSARecv操作
        DWORD Flags = 0;
        DWORD dwRecv = 0;
        ZeroMemory(PerIoData, sizeof(PER_IO_OPERATION_DATA));
        PerIoData->DataBuf.buf = PerIoData->Buffer;
        PerIoData->DataBuf.len = DATA_BUFSIZE;
        WSARecv(PerHandleData->Socket, &PerIoData->DataBuf, 1, &dwRecv, &Flags, &PerIoData->Overlapped, NULL);//异步IO投递花费时间少
        //数据处理
        per_read(data);
    }

    return 0;
}

4:循环接收客户端套接字,将客户端套接字关联到完成端口,并传递一个套接字重叠结构,然后,创建一个客户端套接字进行IO操作的重叠结构,附带IO操作的说明信息或者缓冲区信息,传递给 IO操作函数。如此循环往复,每次接收一个套接字就发送一个读数据、写数据。

    while (true)
    {
        sClient = accept(sListen, 0, 0);
        clCnt++;
        

        pPerHandleData = new PER_HANDLE_DATA();
        pPerHandleData->Socket = sClient;

        // 将接入的客户端和完成端口联系起来,最后一个参数表示使用和核心一样的并发线程数, 
        // 注意:DWORD表示32位值,而PerHandleData可能是64位指针值,用DWORD接收64位指针可能会导致,接收到阉割的低4位地址,即指针截断
        //但是32位没有问题,因为32位指针正好能被DWORD存下。不被阉割,因此,最好使用超大号值存储指针,或者不要用数值复制指针的值,否则,会内存访问权限
        //问题。
        DWORD pvalue = (DWORD)pPerHandleData;
        CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (ULONG_PTR)pPerHandleData, 0);

        // 建立一个Overlapped,并使用这个Overlapped结构对socket投递操作
        pPerIoData = new PER_IO_OPERATION_DATA();

        ZeroMemory(pPerIoData, sizeof(PER_IO_OPERATION_DATA));
        pPerIoData->DataBuf.buf = pPerIoData->Buffer; memset(pPerIoData->DataBuf.buf, 2, DATA_BUFSIZE);
        pPerIoData->DataBuf.len = DATA_BUFSIZE;

        // 投递一个WSARecv操作
        DWORD Flags = 0;
        DWORD dwRecv = 0;
        WSARecv(sClient, &pPerIoData->DataBuf, 1, &dwRecv, &Flags, &pPerIoData->Overlapped, NULL);
    }

5:当发生异常,或者通信结束,投递一个完成退出通知,用 PostQueuedCompletionStatus

//退出消息
PostQueuedCompletionStatus(CompletionPort, dwByteTrans, 0, 0);

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值