前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >关于windows完成端口(IOCP)的一些理解(二)

关于windows完成端口(IOCP)的一些理解(二)

作者头像
范蠡
发布2018-04-13 15:25:25
1.5K0
发布2018-04-13 15:25:25
举报

1

不知道你是否记得前面中说过每消耗一个预先准备客户端的socket,就要补上一个。这个代码现在看来就应该放在连接成功事件里面了:

代码语言:javascript
复制
DWORD ThreadFunction()  
{  
    OVERLAPPED           *pOverlapped = NULL;  
    PER_SOCKET_CONTEXT   *pSocketContext = NULL;  
    DWORD                dwBytesTransfered = 0;  

    BOOL bReturn = GetQueuedCompletionStatus(m_hIOCompletionPort, 
                                             &dwBytesTransfered, 
                                             (PULONG_PTR)&pSocketContext,
                                             &pOverlapped, INFINITE);  

    if (((SOME_STRUCT*)pSocketContext)->s == 侦听socket句柄)  
    {  
        /*连接成功后可以做以下事情:  
         1. 获取对端和本端的ip地址和端口号,即AcceptEx的
             第三个参数lpOutputBuffer中拿(这一步,不是必须)  
         2. 如果对端连接成功后会发数据过来,则可以从初始化时
             调用AcceptEx准备的缓冲区里面拿到,即AcceptEx的
              第三个参数lpOutputBuffer中拿(这一步不是必须)  

         3. 再次调用AcceptEx补充一个sAcceptSocket
           (这一步是必须的)*/  
    }  
    //普通客户端socket收发数据  
    else  
    {  
        //通过pOverlapped结构得到pIOContext  
        PER_IO_CONTEXT* pIOContext = (PER_IO_CONTEXT*)pOverlapped;    
        if (pIOContext->Type == 收)  
        {  
            //做一些操作2,比如解析数据  
        }  
        else if (pIOContext->Type == 发)  
        {  
            //做一些操作3,比如显示一条数据发送成功信息  
        }  
    }  

}  

上面连接成功后的伪码,第1步和第2步不是必须的,而第3步是必须的,如果不及时补充的话,等连接数多于准备的socket,可能就会发生故障了。

因为两端的地址信息和对端发过来的第一组数据都在同一个缓冲区里面,再次看下AcceptEx函数签名吧:

代码语言:javascript
复制
BOOL AcceptEx(  
  _In_  SOCKET       sListenSocket,  
  _In_  SOCKET       sAcceptSocket,  
  _In_  PVOID        lpOutputBuffer,  
  _In_  DWORD        dwReceiveDataLength,  
  _In_  DWORD        dwLocalAddressLength,  
  _In_  DWORD        dwRemoteAddressLength,  
  _Out_ LPDWORD      lpdwBytesReceived,  
  _In_  LPOVERLAPPED lpOverlapped  
);  

虽然可以根据dwReceiveDataLength、dwLocalAddressLength、dwRemoteAddressLength、lpdwBytesReceived这几个参数计算出来,但是微软提供了一个函数来帮我们做这个解析动作: GetAcceptExSockaddrs 同理,这个函数最好也要通过WSAIoctl函数来动态获取:

代码语言:javascript
复制
// 获取GetAcceptExSockAddrs函数指针,也是同理  
if(SOCKET_ERROR == WSAIoctl(  
    m_pListenContext->m_Socket,   
    SIO_GET_EXTENSION_FUNCTION_POINTER,   
    &GuidGetAcceptExSockAddrs,  
    sizeof(GuidGetAcceptExSockAddrs),   
    &m_lpfnGetAcceptExSockAddrs,   
    sizeof(m_lpfnGetAcceptExSockAddrs),     
    &dwBytes,   
    NULL,   
    NULL))    
{    
    this->_ShowMessage(_T("WSAIoctl 未能获取GuidGetAcceptExSockAddrs函数指针。错误代码: %d\n"), 
                          WSAGetLastError());  
    this->_DeInitialize();  
    return false;   
}  

然后使用返回的函数指针来使用函数GetAcceptExSockaddrs。解析地址信息和第一组数据的代码如下:

代码语言:javascript
复制
<pre code_snippet_id="2472609" 
snippet_file_name="blog_20170706_24_1671088" 
name="code"
class="cpp">SOCKADDR_IN* ClientAddr = NULL;  
SOCKADDR_IN* LocalAddr = NULL;    
int remoteLen = sizeof(SOCKADDR_IN);
int 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 );
  </pre><br><br>  

2

以上介绍的是接收新连接成功后的处理,那收数据和发数据的准备工作在哪里做呢?(收取第一组数据可以在调用AcceptEx的地方做)。这个就仁者见仁,智者见智了。比如可以在新连接接收成功之后,立即准备给对端发数据;或者在收到对端数据的时候准备给对端发数据;在发送数据完成后准备收对端数据。伪码如下:

代码语言:javascript
复制
DWORD ThreadFunction()  
{  
    OVERLAPPED           *pOverlapped = NULL;  
    PER_SOCKET_CONTEXT   *pSocketContext = NULL;  
    DWORD                dwBytesTransfered = 0;  

    BOOL bReturn = GetQueuedCompletionStatus(m_hIOCompletionPort, 
                                             &dwBytesTransfered,
                                             (PULONG_PTR)&pSocketContext,
                                             &pOverlapped, INFINITE);  

    if (((SOME_STRUCT*)pSocketContext)->s == 侦听socket句柄)  
    {  
        /*连接成功后可以做以下事情:  
        1. 获取对端和本端的ip地址和端口号,
           即AcceptEx的第三个参数lpOutputBuffer中拿(这一步,不是必须)  
        2. 如果对端连接成功后会发数据过来,则可以从初始化时
           调用AcceptEx准备的缓冲区里面拿到,即AcceptEx的
           第三个参数lpOutputBuffer中拿(这一步不是必须)  

        3. 再次调用AcceptEx补充一个sAcceptSocket
          (这一步是必须的)  

        4. 调用WSASend准备发送数据工作或调用WSARecv准备接收数据工作
           (这一步,不是必须)*/  
    }  
    //普通客户端socket收发数据  
    else  
    {  
        //通过pOverlapped结构得到pIOContext  
        PER_IO_CONTEXT* pIOContext = (PER_IO_CONTEXT*)pOverlapped;    
        if (pIOContext->Type == 收)  
        {             
            //解析收到的数据
            //(这一步,不是必须)  
            //调用WSASend准备发送数据工作
            //(比如应答客户端)(这一步,不是必须)  
            //继续调用WSARecv准备收取数据工作
            //(这一步,不是必须)  
        }  
        else if (pIOContext->Type == 发)  
        {  
            //调用WSARecv准备收取数据工作(这一步,不是必须)  
        }  
    }  

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

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

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

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

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