分享我写的IOCP:源码+思路(转载)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分享我写的IOCP:源码+思路(转载)相关的知识,希望对你有一定的参考价值。

首先说明,下面的代码仅是一个IOCP的demo,很多地方的设计非常差,当然也有一些设计还算可以:)。此篇仅供对IOCP有些了解但又不深入的、需要一个稍微完整示例的、对网络编程感兴趣的同学参考。点击这里下载代码

整个程序的流程如下:

技术分享

流程完全是无阻塞的,主线程里,将收到的消息全都一次性取出后,然后派发。所有欲发送的消息都缓存起来,等到更新的时候一起发送。有些地方代码没有完善,比如断开连接后,socket、内存等资源的关闭回收。要注意MAXRECEIVEDBUFFLENGTH这个宏,它是定义每个socket消息收发时缓冲区的大小。如果很大,会非常吃内存的。我在这里也没有做粘包、分包的情况处理。工程还带了一个C#写的测试客户端。在我的机器上,能有12W的连接。

技术分享

关键代码如下:

技术分享

void MyIOCP::Execute() 
{ 
    PerHandleData* pPerHandleData = nullptr; 
    PerIOData* pPerIOData = nullptr; 
    LPOVERLAPPED lpOverLapped = nullptr; 
    DWORD byteTransferred; 
    BOOL bRet = FALSE;

    while (true) 
    { 
        bRet = GetQueuedCompletionStatus(mCompletionPort, 
            &byteTransferred, 
            (PULONG_PTR)&pPerHandleData, 
            /*(LPOVERLAPPED*)&pPerIOData*/&lpOverLapped, 
            INFINITE); 
        pPerIOData = (PerIOData*)CONTAINING_RECORD(lpOverLapped, PerIOData, overlapped); 
        if (bRet == FALSE) 
        { 
            if (pPerIOData == nullptr) 
            { 
                // 退出线程。服务器要关了。。 
                break; 
            } 
            // 对方主动断开了

            continue; 
        }

        // 处理成功的完成端口请求 
        switch (pPerIOData->operationType) 
        { 
        case CDH::E_INVALID: 
            { 
            } 
            break; 
        case CDH::E_ACCEPT: 
            { 
                /************************************************************************/ 
                /* 
                inet_ntoa(ClientAddr->sin_addr) 是客户端IP地址

                ntohs(ClientAddr->sin_port) 是客户端连入的端口

                inet_ntoa(LocalAddr ->sin_addr) 是本地IP地址

                ntohs(LocalAddr -AZ>sin_port) 是本地通讯的端口

                pIoContext->m_wsaBuf.buf 是存储客户端发来第一组数据的缓冲区 
                */ 
                /************************************************************************/ 
                SOCKADDR_IN* ClientAddr = NULL; 
                SOCKADDR_IN* LocalAddr = NULL; 
                int remoteLen = sizeof(SOCKADDR_IN), localLen = sizeof(SOCKADDR_IN); 
                mlpfnGetAcceptExSockAddrs(pPerIOData->buffer, 0,  sizeof(SOCKADDR_IN)+16, sizeof(SOCKADDR_IN)+16, (LPSOCKADDR*)&LocalAddr, &localLen, (LPSOCKADDR*)&ClientAddr, &remoteLen);

                WrapSocket* connectedSocket = GetSocketAndEraseFromTheMap(pPerIOData->remoteSocket, WRAPESOCKETTYPE::E_READYTOBECONNECTED); 
                if (connectedSocket == nullptr) 
                { 
                    closesocket(pPerIOData->remoteSocket); 
                    continue; 
                } 
                if (!AssociateDeviceWithCompletionPort(connectedSocket)) 
                { 
                    closesocket(pPerIOData->remoteSocket); 
                    continue; 
                } 
                // 添加到已经连接列表 
                InsertWrapSocketToMap(connectedSocket, WRAPESOCKETTYPE::E_CONNECTED);

                PostReceive(connectedSocket);

                // 这次消耗了一个准备好了的socket, 现在再生成一个socket待连接。 
                PostAcceptEx(connectedSocket->GetPerHandleData().hasListenSocket); 
            } 
            break; 
        case CDH::E_RECV: 
            { 
                int socket = pPerHandleData->selfSocket; 
                WrapSocket* wrapSocket = nullptr; 
                GetSocketForSendOrRecvData(socket, wrapSocket);

                if (wrapSocket != nullptr) 
                { 
                    wrapSocket->GetPerIODataReceive().bufferLen = byteTransferred; 
                    Receive(wrapSocket); 
                } 
            } 
            break; 
        case CDH::E_SEND: 
            { 
                int socket = pPerHandleData->selfSocket; 
                WrapSocket* wrapSocket = nullptr; 
                GetSocketForSendOrRecvData(socket, wrapSocket); 
                if (wrapSocket != nullptr) 
                { 
                    wrapSocket->SendingData(false); 
                }

            } 
            break; 
        case CDH::E_CONNECT: 
            break; 
        default: 
            break; 
        }

    } 
}

技术分享

注意CDH::E_RECV,收到消息后,将消息缓存,然后直接又进行投递PostReceive(),这其实是非常不好的。应该分情况来选择是否立即投递。

以上,整个代码,基本是用C++的格式写C代码。希望以后能有机会与大家共同分享一个比较完整的、面向对象风格的IOCP。

以上是关于分享我写的IOCP:源码+思路(转载)的主要内容,如果未能解决你的问题,请参考以下文章

Libevent源码分析--- IOCP

DELPHI中完成端口(IOCP)的简单分析

如何将CKeditor编辑器的上传和thinkphp结合

Nginx源码分析 - Nginx启动以及IOCP模型

C语言项目:矿井逃生游戏(密室)!详细思路+源码分享

教你如何打造网页爬虫工具(实现思路及源码下载)