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

1

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

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函数签名吧:

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函数来动态获取:

// 获取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。解析地址信息和第一组数据的代码如下:

<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的地方做)。这个就仁者见仁,智者见智了。比如可以在新连接接收成功之后,立即准备给对端发数据;或者在收到对端数据的时候准备给对端发数据;在发送数据完成后准备收对端数据。伪码如下:

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准备收取数据工作(这一步,不是必须)  
        }  
    }  

}  

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

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

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏FreeBuf

如何在CTF中少走弯路(基础篇)

自己并不是专业的赛棍也没有打过很多比赛,这篇文章是自己在CTF中对于杂项这块知识学习的小结,希望可以对初入CTF的同学有所帮助,在CTF中少走弯路从而更快的提升...

1.5K40
来自专栏24K纯开源

Windows下Thumbnail的开发总结

一、引言        Windows Thumbnail Handler是Windows平台下用来为关联的文件类型提供内容预览图的一套COM接口。通过实现Th...

37060
来自专栏跟着阿笨一起玩NET

ASP.NET WebApi 基于JWT实现Token签名认证(发布版)

32230
来自专栏blackheart的专栏

[信息安全] 4.一次性密码 && 身份认证三要素

在信息安全领域,一般把Cryptography称为密码,而把Password称为口令。日常用户的认知中,以及我们开发人员沟通过程中,绝大多数被称作密码的东西其...

32760
来自专栏张善友的专栏

Windows 7上执行Cake 报错原因是Powershell 版本问题

在Windows 7 SP1 电脑上执行Cake的的例子 http://cakebuild.net/docs/tutorials/getting-started...

23990
来自专栏向治洪

Android App瘦身实战

随着业务的快速迭代增长,不断引入新的业务逻辑代码、图片资源和第三方SDK等,很多app都面临一个一个结果,app越来越大,甚至很多无用的代码,包体积的增大带来了...

39080
来自专栏张善友的专栏

依赖注入容器Autofac

在.NET上现在存在许多的依赖注入容器, 我也在实践中使用过Castle Windsor、StructureMap、Autofac 、Unity。这些容器的简要...

27890
来自专栏Kubernetes

从源码解析kube-scheduler默认的配置

本文作为Kubernetes Scheduler源码分析的番外篇,补充一个方面的分析:从源码层面解析kube-scheduler的默认配置是怎么做的。 从头来看...

34360
来自专栏美团技术团队

Android Hook技术防范漫谈

背景 当下,数据就像水、电、空气一样无处不在,说它是“21世纪的生产资料”一点都不夸张,由此带来的是,各行业对于数据的争夺热火朝天。随着互联网和数据的思维深入人...

62470
来自专栏王大锤

再谈RunLoop

29740

扫码关注云+社区

领取腾讯云代金券