完成端口模型IOCP详解 (一)

<一> IOCP 实现步骤

      如果懂得了 IOCP 的工作原理,它实现起来是很简单的。

它的实现步骤如下:

1.   创建好 IOCP

2.   创建 Socket socket 可以是由 Accept 得到)

3.   Socket 关联到 IOCP

4.   socket IOCP 提交各种所需请求

5.   IOCP 操作完成之后将结果返回给 socket

6.   重复步骤 3 4 ,直到 socket 关闭

它就那么几个步骤,但实现起来需要不少的代码。以下就以创建一个客户端的 socket 为例,先做部分的讲解。这里主要讲解原理,函数的参数和返回值先忽略。

1 // 创建 IOCP

// 利用函数 CreateIoCompletionPort 创建IOCP

// 注意参数设置

m_hIocp = CreateIoCompletionPort (INVALID_HANDLE_VALUE ,NULL ,(ULONG_PTR )0,0);

// m_hIocp 就代表一个完成端口了

 

2 // 创建连接型的 Socket

// 利用 利用函数WSASocket 创建 socket ,必须指定 WSA_FLAG_OVERLAPPED

m_sockClient = ::WSASocket (AF_INET ,

        SOCK_STREAM , IPPROTO_TCP , NULL , NULL , WSA_FLAG_OVERLAPPED );

//  m_sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 这样也可以

                                                    // 默认为WSA_FLAG_OVERLAPPED

 

// 以下的绑定很重要,也是容易漏掉的。(如果少了绑定,在 ConnextEx 时将得到错误代码:10022 提供了一个无效的参数

sockaddr_in local_addr ;

    ZeroMemory (&local_addr , sizeof (sockaddr_in ));

    local_addr .sin_family = AF_INET ;

int irt = ::bind (m_sockClient , (sockaddr *)(&local_addr ), sizeof (sockaddr_in ));

// m_sockClient 是创建好的socket

 

3 // Socket 关联到 IOCP

// 又利用上了CreateIoCompletionPort ,它将socket 关联到IOCP

CreateIoCompletionPort ((HANDLE )m_sockClient ,m_hIocp , (ULONG_PTR )m_sockClient ,0);

// 已将sockClient 关联到m_hIocp

// (ULONG_PTR)m_sockClient 为以后识别是那个socket 的操作

 

4.// socket IOCP 提交各种所需请求 , 这里提交的是连接请求

// 代码比较长,将整个函数都拷贝了过来

BOOL CIOCP_ClientDlg ::ConnectToServer ()

{

    //----------------------

    LPFN_CONNECTEX m_lpfnConnectEx = NULL ;

    DWORD dwBytes = 0;

    GUID GuidConnectEx = WSAID_CONNECTEX ;

 

    // 重点,获得ConnectEx 函数的指针

    if (SOCKET_ERROR == WSAIoctl (m_sockClient , SIO_GET_EXTENSION_FUNCTION_POINTER ,

        &GuidConnectEx , sizeof (GuidConnectEx ),

        &m_lpfnConnectEx , sizeof (m_lpfnConnectEx ), &dwBytes , 0, 0))

    {

        TRACE ( "WSAIoctl is failed. Error code = %d" , WSAGetLastError ());

        return FALSE ;

    }

 

    MYOVERLAPPED *pmyoverlapped = new MYOVERLAPPED ; // socket I/O 通讯的载体

    pmyoverlapped ->operateType = OP_CONNECT// 设置请求类型,得到I/O 结果时根据此                                   //                                          // 来识别请求类型

    pmyoverlapped ->hEvent = NULL// 非事件模型

 

    // 设置连接目标地址

    sockaddr_in addrPeer ;  

    ZeroMemory (&addrPeer , sizeof (sockaddr_in ));

    addrPeer .sin_family = AF_INET ;

    addrPeer .sin_addr .s_addr = inet_addr ( "192.168.0.15" );

    addrPeer .sin_port = htons ( 5400 );

 

    int nLen = sizeof (addrPeer );

    PVOID lpSendBuffer = NULL;

    DWORD dwSendDataLength = 0;

    DWORD dwBytesSent = 0;

 

    // 重点

    BOOL bResult = m_lpfnConnectEx (m_sockClient ,

        (sockaddr *)&addrPeer// [in] 对方地址

        nLen ,               // [in] 对方地址长度

        lpSendBuffer ,       // [in] 连接后要发送的内容,这里不用

        dwSendDataLength ,   // [in] 发送内容的字节数 ,这里不用

        &dwBytesSent ,       // [out] 发送了多少个字节,这里不用

        (OVERLAPPED *)pmyoverlapped ); // [in] 这东西复杂,下一篇有详解

 

    if (!bResult )      // 返回值处理

    {

        if ( WSAGetLastError () != ERROR_IO_PENDING )   // 调用失败

        {

            TRACE (TEXT ("ConnextEx error: %d/n" ),WSAGetLastError ());

            return FALSE ;

        }

        else ;// 操作未决(正在进行中

        {

            TRACE0 ("WSAGetLastError() == ERROR_IO_PENDING/n" );// 操作正在进行中

        }

    }

 

    return TRUE ;

}

       

// 在这个函数中重点是 WSAIoctl 函数(得到ConnectEx 函数地址)和m_lpfnConnectEx 函数指针(进行ConnectEx 调用,也就是向I/O 提交连接对方的请求)。

// 这样,实现了向I/O 提交Connect 请求。

 

5 //  IOCP 操作完成之后将结果返回给 socket

  //  提交请求之后, sokcet 是如何得到结果的呢? 靠的是主动要到 I/O 的出口那里等 // 待,直到拿到数据。一般都有专门的一条或多条线程在等候结果。重点在 // GetQueuedCompletionStatus 函数。

 

void CIOCP_ClientDlg ::IocpWorkerThread ()

{

    MYOVERLAPPED *lpOverlapped = NULL ;

    DWORD        dwByteTransfered = 0;

    ULONG_PTR        *PerHandleKey = NULL ;

 

    while (1)

    {

        lpOverlapped = NULL ;

        if (m_hIocp == NULL )

        {

            break ;

        }

       

        // 下面的函数调用就是去I/O 出口那里等待,并获得I/O 操作结果

        BOOL bResult = GetQueuedCompletionStatus (

            m_hIocp , // 指定从哪个IOCP 那里或地数据

            &dwByteTransfered , // 或得或是发送了多少字节的数据

            (PULONG_PTR )&PerHandleKey , // socket 关联到IOCP 时指定的一个关联值

            (LPWSAOVERLAPPED *)&lpOverlapped ,   // 或得ConnectEx 传进来的结构

             INFINITE );              // 一直等待,直到有结果

 

  ......// 处理从I/O 获得的数据,代码很多,省略!

}

}

 

// 如果ConnecEx 是成功的,那么到GetQueuedCompletionStatus 调用结束,m_sockClient 则是可以用来发送和接收数据的socket 了。

 

6 // 重复步骤 3 4 ,直到 socket 关闭

  // 现在可以用 m_sockClient 来发送和接收数据了。

 

// 下面提交接收请求

WSARecv (socket , &lpOverlapped ->wsabuf , 1,

            &lpOverlapped ->dwByteRecvSend , &Flags ,

        ( LPWSAOVERLAPPED )lpOverlapped , NULL );

 

// 下面提交发送请求

int nResultWSASend (

                            socket

                            &pmyoverlapped ->wsabuf , // WSABUF 指针,发送数据在这里

                            1,                      // WSABUF 指针指向的数组大小

                            &pmyoverlapped ->dwByteRecvSend , // 实际发送字节数

                            0,

                            (LPWSAOVERLAPPED )pmyoverlapped ,   //OVERLAPPED 结构

                            0);

 

  IOCP 的客户端实现步骤也就上面的六点。一个socket 能够连接对方,主要就在ConnectExGetQueuedCompletionStatus 得到的是 ConnectEx 的结果是否成功。

上面的例子展示的是如何实现一个客户端,服务器端的实现也差不多,就是创建socket- 〉绑定socket- 〉监听- 〉接受连接。

 

 

 

你可能感兴趣的:(Socket)