前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >windows完成端口(六)

windows完成端口(六)

作者头像
范蠡
发布2018-04-24 15:12:31
1.5K0
发布2018-04-24 15:12:31
举报

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

代码语言:javascript
复制
//  最后释放掉所有资源  
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完成端口(六)

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-04-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 高性能服务器开发 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档