• 分享我写的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。

  • 相关阅读:
    微信 token ticket jsapi_ticket access_token 获取 getAccessToken get_jsapi_ticket方法
    PHP 日志 记录 函数 支持 数组 对象 新浪 sae 环境 去掉 空格 换行 格式化 输出 数组转字符串
    原生 原始 PHP连接MySQL 代码 参考mysqli pdo
    PHP 数字金额转换成中文大写金额的函数 数字转中文
    使用PHPMailer发送带附件并支持HTML内容的邮件
    设置输出编码格式 header 重定向 执行时间 set_time_limit 错误 报告 级别 error_reporting
    html5 bootstrap pannel table 协议 公告 声明 文书 模板
    指向指针的指针
    二级指针
    c语言:当指针成为参数后
  • 原文地址:https://www.cnblogs.com/cdh49/p/3657610.html
Copyright © 2020-2023  润新知