windows完成端口(六)

系列目录 windows完成端口(一) windows完成端口(二) windows完成端口(三) windows完成端口(四) windows完成端口(五) windows完成端口(六)

//  最后释放掉所有资源  
void CIOCPModel::_DeInitialize()  
{  
    // 删除客户端列表的互斥量  
    DeleteCriticalSection(&m_csContextList);  

    // 关闭系统退出事件句柄  
    RELEASE_HANDLE(m_hShutdownEvent);  

    // 释放工作者线程句柄指针  
    for( int i=0;i<m_nThreads;i++ )  
    {  
        RELEASE_HANDLE(m_phWorkerThreads[i]);  
    }  

    RELEASE(m_phWorkerThreads);  

    // 关闭IOCP句柄  
    RELEASE_HANDLE(m_hIOCompletionPort);  

    // 关闭监听Socket  
    RELEASE(m_pListenContext);  

    this->_ShowMessage(_T("释放资源完毕.\n"));  
}  


//====================================================================================  
//  
//                  投递完成端口请求  
//  
//====================================================================================  


//////////////////////////////////////////////////////////////////  
// 投递Accept请求  
bool CIOCPModel::_PostAccept( PER_IO_CONTEXT* pAcceptIoContext )  
{  
    ASSERT( INVALID_SOCKET!=m_pListenContext->m_Socket );  

    // 准备参数  
    DWORD dwBytes = 0;    
    pAcceptIoContext->m_OpType = ACCEPT_POSTED;    
    WSABUF *p_wbuf   = &pAcceptIoContext->m_wsaBuf;  
    OVERLAPPED *p_ol = &pAcceptIoContext->m_Overlapped;  

    // 为以后新连入的客户端先准备好Socket( 这个是与传统accept最大的区别 )   
    pAcceptIoContext->m_sockAccept  = WSASocket(AF_INET,
                                                SOCK_STREAM,
                                                IPPROTO_TCP,
                                                NULL, 0, 
                                                WSA_FLAG_OVERLAPPED);    
    if( INVALID_SOCKET==pAcceptIoContext->m_sockAccept )    
    {    
        _ShowMessage(_T("创建用于Accept的Socket失败!错误代码: %d"),
                         WSAGetLastError());  
        return false;    
    }   

    // 投递AcceptEx  
    if(FALSE == m_lpfnAcceptEx( m_pListenContext->m_Socket,
                                pAcceptIoContext->m_sockAccept,
                                p_wbuf->buf,
                                p_wbuf->len - ((sizeof(SOCKADDR_IN)+16)*2),     
                                sizeof(SOCKADDR_IN)+16,                                                 
                                sizeof(SOCKADDR_IN)+16, 
                                &dwBytes, p_ol))    
    {    
        if(WSA_IO_PENDING != WSAGetLastError())    
        {    
            _ShowMessage(_T("投递 AcceptEx 请求失败,错误代码: %d"),
                            WSAGetLastError());  
            return false;    
        }    
    }   

    return true;  
}  

////////////////////////////////////////////////////////////  
// 在有客户端连入的时候,进行处理  
// 流程有点复杂,你要是看不懂的话,就看配套的文档吧....  
// 如果能理解这里的话,完成端口的机制你就消化了一大半了  

// 总之你要知道,传入的是ListenSocket的Context
//我们需要复制一份出来给新连入的Socket用  
// 原来的Context还是要在上面继续投递下一个Accept请求  
//  
bool CIOCPModel::_DoAccpet( PER_SOCKET_CONTEXT* pSocketContext,
                            PER_IO_CONTEXT* pIoContext )  
{      
    SOCKADDR_IN* ClientAddr = NULL;  
    SOCKADDR_IN* LocalAddr = NULL;    
    int remoteLen = sizeof(SOCKADDR_IN), localLen = sizeof(SOCKADDR_IN);    

    ///////////////////////////////////////////////////////////////////////////  
    // 1. 首先取得连入客户端的地址信息  
    // 这个 m_lpfnGetAcceptExSockAddrs 不得了啊~~~~~~  
    // 不但可以取得客户端和本地端的地址信息,还能顺便取出客户端发来的第一组数据,老强大了...  
    this->m_lpfnGetAcceptExSockAddrs(pIoContext->m_wsaBuf.buf,
                                     pIoContext->m_wsaBuf.len - ((sizeof(SOCKADDR_IN)+16)*2),    
                                     sizeof(SOCKADDR_IN)+16,  
                                     sizeof(SOCKADDR_IN)+16,
                                     (LPSOCKADDR*)&LocalAddr,
                                     &localLen, 
                                     (LPSOCKADDR*)&ClientAddr, 
                                     &remoteLen);    

    this->_ShowMessage( _T("客户端 %s:%d 连入."), 
                       inet_ntoa(ClientAddr->sin_addr), 
                       ntohs(ClientAddr->sin_port) );  
    this->_ShowMessage( _T("客户额 %s:%d 信息:%s."),
                       inet_ntoa(ClientAddr->sin_addr), 
                       ntohs(ClientAddr->sin_port),
                       pIoContext->m_wsaBuf.buf );  


    //////////////////////////////////////////////////////////////////////////////////////////////////////  
    // 2. 这里需要注意,这里传入的这个是ListenSocket上的Context,这个Context我们还需要用于监听下一个连接  
    // 所以我还得要将ListenSocket上的Context复制出来一份为新连入的Socket新建一个SocketContext  

    PER_SOCKET_CONTEXT* pNewSocketContext = new PER_SOCKET_CONTEXT;  
    pNewSocketContext->m_Socket           = pIoContext->m_sockAccept;  
    memcpy(&(pNewSocketContext->m_ClientAddr), 
             ClientAddr, sizeof(SOCKADDR_IN));  

    // 参数设置完毕,将这个Socket和完成端口绑定(这也是一个关键步骤)  
    if( false==this->_AssociateWithIOCP( pNewSocketContext ) )  
    {  
        RELEASE( pNewSocketContext );  
        return false;  
    }    


    ///////////////////////////////////////////////////////////////////////////////////////////////////  
    // 3. 继续,建立其下的IoContext,用于在这个Socket上投递第一个Recv数据请求  
    PER_IO_CONTEXT* pNewIoContext = pNewSocketContext->GetNewIoContext();  
    pNewIoContext->m_OpType       = RECV_POSTED;  
    pNewIoContext->m_sockAccept   = pNewSocketContext->m_Socket;  
    // 如果Buffer需要保留,就自己拷贝一份出来  
    //memcpy( pNewIoContext->m_szBuffer,pIoContext->m_szBuffer,MAX_BUFFER_LEN );  

    // 绑定完毕之后,就可以开始在这个Socket上投递完成请求了  
    if( false==this->_PostRecv( pNewIoContext) )  
    {  
        pNewSocketContext->RemoveContext( pNewIoContext );  
        return false;  
    }  

    /////////////////////////////////////////////////////////////////////////////////////////////////  
    // 4. 如果投递成功,那么就把这个有效的客户端信息,加入到ContextList中去(需要统一管理,方便释放资源)  
    this->_AddToContextList( pNewSocketContext );  

    ////////////////////////////////////////////////////////////////////////////////////////////////  
    // 5. 使用完毕之后,把Listen Socket的那个IoContext重置,然后准备投递新的AcceptEx  
    pIoContext->ResetBuffer();  
    return this->_PostAccept( pIoContext );    
}  

////////////////////////////////////////////////////////////////////  
// 投递接收数据请求  
bool CIOCPModel::_PostRecv( PER_IO_CONTEXT* pIoContext )  
{  
    // 初始化变量  
    DWORD dwFlags = 0;  
    DWORD dwBytes = 0;  
    WSABUF *p_wbuf   = &pIoContext->m_wsaBuf;  
    OVERLAPPED *p_ol = &pIoContext->m_Overlapped;  

    pIoContext->ResetBuffer();  
    pIoContext->m_OpType = RECV_POSTED;  

    // 初始化完成后,,投递WSARecv请求  
    int nBytesRecv = WSARecv( pIoContext->m_sockAccept,
                              p_wbuf, 1,
                              &dwBytes, &dwFlags,
                              p_ol, NULL );  

    // 如果返回值错误,并且错误的代码并非是Pending的话,那就说明这个重叠请求失败了  
    if ((SOCKET_ERROR == nBytesRecv) && (WSA_IO_PENDING != WSAGetLastError()))  
    {  
        this->_ShowMessage(_T("投递第一个WSARecv失败!"));  
        return false;  
    }  
    return true;  
}  

/////////////////////////////////////////////////////////////////  
// 在有接收的数据到达的时候,进行处理  
bool CIOCPModel::_DoRecv( PER_SOCKET_CONTEXT* pSocketContext,
                          PER_IO_CONTEXT* pIoContext )  
{  
    // 先把上一次的数据显示出现,然后就重置状态,发出下一个Recv请求  
    SOCKADDR_IN* ClientAddr = &pSocketContext->m_ClientAddr;  
    this->_ShowMessage( _T("收到  %s:%d 信息:%s"),
                            inet_ntoa(ClientAddr->sin_addr), 
                            ntohs(ClientAddr->sin_port),
                            pIoContext->m_wsaBuf.buf );  

    // 然后开始投递下一个WSARecv请求  
    return _PostRecv( pIoContext );  
}  



/////////////////////////////////////////////////////  
// 将句柄(Socket)绑定到完成端口中  
bool CIOCPModel::_AssociateWithIOCP( PER_SOCKET_CONTEXT *pContext )  
{  
    // 将用于和客户端通信的SOCKET绑定到完成端口中  
    HANDLE hTemp = CreateIoCompletionPort((HANDLE)pContext->m_Socket,
                                           m_hIOCompletionPort,
                                           (DWORD)pContext, 0);  

    if (NULL == hTemp)  
    {  
        this->_ShowMessage(_T("执行CreateIoCompletionPort()出现错误.错误代码:%d"),
                              GetLastError());  
        return false;  
    }  

    return true;  
}  




//====================================================================================  
//  
//                  ContextList 相关操作  
//  
//====================================================================================  


//////////////////////////////////////////////////////////////  
// 将客户端的相关信息存储到数组中  
void CIOCPModel::_AddToContextList( PER_SOCKET_CONTEXT *pHandleData )  
{  
    EnterCriticalSection(&m_csContextList);  

    m_arrayClientContext.Add(pHandleData);    

    LeaveCriticalSection(&m_csContextList);  
}  

////////////////////////////////////////////////////////////////  
//  移除某个特定的Context  
void CIOCPModel::_RemoveContext( PER_SOCKET_CONTEXT *pSocketContext )  
{  
    EnterCriticalSection(&m_csContextList);  

    for( int i=0;i<m_arrayClientContext.GetCount();i++ )  
    {  
        if( pSocketContext==m_arrayClientContext.GetAt(i) )  
        {  
            RELEASE( pSocketContext );            
            m_arrayClientContext.RemoveAt(i);             
            break;  
        }  
    }  

    LeaveCriticalSection(&m_csContextList);  
}  

////////////////////////////////////////////////////////////////  
// 清空客户端信息  
void CIOCPModel::_ClearContextList()  
{  
    EnterCriticalSection(&m_csContextList);  

    for( int i=0;i<m_arrayClientContext.GetCount();i++ )  
    {  
        delete m_arrayClientContext.GetAt(i);  
    }  

    m_arrayClientContext.RemoveAll();  

    LeaveCriticalSection(&m_csContextList);  
}  



//====================================================================================  
//  
//                     其他辅助函数定义  
//  
//====================================================================================  



////////////////////////////////////////////////////////////////////  
// 获得本机的IP地址  
CString CIOCPModel::GetLocalIP()  
{  
    // 获得本机主机名  
    char hostname[MAX_PATH] = {0};  
    gethostname(hostname,MAX_PATH);                  
    struct hostent FAR* lpHostEnt = gethostbyname(hostname);  
    if(lpHostEnt == NULL)  
    {  
        return DEFAULT_IP;  
    }  

    // 取得IP地址列表中的第一个为返回的IP(因为一台主机可能会绑定多个IP)  
    LPSTR lpAddr = lpHostEnt->h_addr_list[0];        

    // 将IP地址转化成字符串形式  
    struct in_addr inAddr;  
    memmove(&inAddr,lpAddr,4);  
    m_strIP = CString( inet_ntoa(inAddr) );          

    return m_strIP;  
}  

///////////////////////////////////////////////////////////////////  
// 获得本机中处理器的数量  
int CIOCPModel::_GetNoOfProcessors()  
{  
    SYSTEM_INFO si;  

    GetSystemInfo(&si);  

    return si.dwNumberOfProcessors;  
}  

/////////////////////////////////////////////////////////////////////  
// 在主界面中显示提示信息  
void CIOCPModel::_ShowMessage(const CString szFormat,...) const  
{  
    // 根据传入的参数格式化字符串  
    CString   strMessage;  
    va_list   arglist;  

    // 处理变长参数  
    va_start(arglist, szFormat);  
    strMessage.FormatV(szFormat,arglist);  
    va_end(arglist);  

    // 在主界面中显示  
    CMainDlg* pMain = (CMainDlg*)m_pMain;  
    if( m_pMain!=NULL )  
    {  
        pMain->AddInformation(strMessage);  
        TRACE( strMessage+_T("\n") );  
    }     
}  

/////////////////////////////////////////////////////////////////////  
// 判断客户端Socket是否已经断开,否则在一个无效的Socket上投递WSARecv操作会出现异常  
// 使用的方法是尝试向这个socket发送数据,判断这个socket调用的返回值  
// 因为如果客户端网络异常断开(例如客户端崩溃或者拔掉网线等)的时候
//服务器端是无法收到客户端断开的通知的  

bool CIOCPModel::_IsSocketAlive(SOCKET s)  
{  
    int nByteSent=send(s,"",0,0);  
    if (-1 == nByteSent) return false;  
    return true;  
}  

///////////////////////////////////////////////////////////////////  
// 显示并处理完成端口上的错误  
bool CIOCPModel::HandleError( PER_SOCKET_CONTEXT *pContext,const DWORD& dwErr )  
{  
    // 如果是超时了,就再继续等吧    
    if(WAIT_TIMEOUT == dwErr)    
    {     
        // 确认客户端是否还活着...  
        if( !_IsSocketAlive( pContext->m_Socket) )  
        {  
            this->_ShowMessage( _T("检测到客户端异常退出!") );  
            this->_RemoveContext( pContext );  
            return true;  
        }  
        else  
        {  
            this->_ShowMessage( _T("网络操作超时!重试中...") );  
            return true;  
        }  
    }    

    // 可能是客户端异常退出了  
    else if( ERROR_NETNAME_DELETED==dwErr )  
    {  
        this->_ShowMessage( _T("检测到客户端异常退出!") );  
        this->_RemoveContext( pContext );  
        return true;  
    }  

    else  
    {  
        this->_ShowMessage( _T("完成端口操作出现错误,线程退出。错误代码:%d"),dwErr );  
        return false;  
    }  
} 

全系列完。

参考链接:http://blog.csdn.net/piggyxp/article/details/6922277

系列目录 windows完成端口(一) windows完成端口(二) windows完成端口(三) windows完成端口(四) windows完成端口(五) windows完成端口(六)

原文发布于微信公众号 - 高性能服务器开发(easyserverdev)

原文发表时间:2018-04-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Urahara Blog

Windows上传并执行恶意代码的N种姿势

5452
来自专栏恰童鞋骚年

.NET Core微服务之基于IdentityServer建立授权与验证服务(续)

上一篇我们基于IdentityServer4建立了一个AuthorizationServer,并且继承了QuickStartUI,能够成功获取Token了。这一...

2955
来自专栏FreeBuf

远程RPC溢出EXP编写实战之MS06-040

0x01 前言 MS06-040算是个比较老的洞了,在当年影响十分之广,基本上Microsoft大部分操作系统都受到了影响,威力不亚于17年爆出的”永恒之蓝”漏...

30810
来自专栏QQ音乐技术团队的专栏

Android点九图总结以及在聊天气泡中的使用

点九图的本质实际上是在图片的四周各增加了1px的像素,并使用纯黑的线进行标记,其它的与原图没有任何区别。

8923
来自专栏晓晨的专栏

VS2015 搭建 Asp.net core 开发环境

3025
来自专栏進无尽的文章

实践-小细节Ⅳ

网上有很多方法,尝试起来都很麻烦,我这个方法还是比较简单的。 解决方法: 我们在手机用数据线连接到电脑时,会弹出很多手机里面的图片,你的隐私尽收眼底(好尴尬...

1241
来自专栏Golang语言社区

写在学习golang一个月后

连接池。由于PHP没有连接池,当高并发时就会有大量的数据库连接直接冲击到MySQL上,最终导致数据库挂掉。虽然Swoole有连接池,但是Swoole只是PHP的...

2762
来自专栏数据和云

监控工具:Oracle 12c Cluster Health Monitor 详解

? 戴明明(Dave) Oracle ACE-A,ACOUG核心成员,宝存科技数据库方案架构师 Dave也是CSDN 认证专家,超过7年的DBA经验,擅长O...

4259
来自专栏葡萄城控件技术团队

Silverlight4控件纯客户端注册验证

本文实现了一个实验性的Silverlight控件纯客户端注册验证机制。希望做过这方面的朋友多给些指导性意见。 先给大家介绍一下Silverlight客户端...

2155
来自专栏JAVA技术站

JFinal整合WebSocket开发 原

说明以tomcat容器为例,tomcat7以上版本开始支持websocket,JFinal集成的jetty服务器不支持websocket

1323

扫码关注云+社区

领取腾讯云代金券