分享我寫的IOCP:源碼+思路


首先說明,下面的代碼僅是一個IOCP的demo,很多地方的設計非常差,當然也有一些設計還算可以:)。此篇僅供對IOCP有些了解但又不深入的、需要一個稍微完整示例的、對網絡編程感興趣的同學參考。點擊這里下載代碼

整個程序的流程如下:

image

流程完全是無阻塞的,主線程里,將收到的消息全都一次性取出后,然后派發。所有欲發送的消息都緩存起來,等到更新的時候一起發送。有些地方代碼沒有完善,比如斷開連接后,socket、內存等資源的關閉回收。要注意MAXRECEIVEDBUFFLENGTH這個宏,它是定義每個socket消息收發時緩沖區的大小。如果很大,會非常吃內存的。我在這里也沒有做粘包、分包的情況處理。工程還帶了一個C#寫的測試客戶端。在我的機器上,能有12W的連接。

E0~BUD3__C7HGP@%Y7BHW9A

關鍵代碼如下:

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。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM