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

<一>IOCP实现步骤

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

它的实现步骤如下:

1.   创建好IOCP

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

3.   Socket关联到IOCP

4.   socketIOCP提交各种所需请求

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

6.   重复步骤34 ,直到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.// socketIOCP提交各种所需请求, 这里提交的是连接请求

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

BOOL CIOCP_ClientDlg::ConnectToServer()

{

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

    LPFN_CONNECTEXm_lpfnConnectEx = NULL;

    DWORDdwBytes = 0;

    GUIDGuidConnectEx = 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());

        returnFALSE;

    }

 

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

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

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

 

    // 设置连接目标地址

    sockaddr_inaddrPeer;  

    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 );

 

    intnLen = sizeof(addrPeer);

    PVOID lpSendBuffer = NULL;

    DWORDdwSendDataLength = 0;

    DWORD dwBytesSent =0;

 

    // 重点

    BOOLbResult = 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("ConnextExerror: %d/n"),WSAGetLastError());

            returnFALSE;

        }

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

        {

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

        }

    }

 

    returnTRUE;

}

       

// 在这个函数中重点是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//重复步骤34 ,直到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能够连接对方,主要就在ConnectEx,GetQueuedCompletionStatus得到的是ConnectEx的结果是否成功。

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

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
最近有项目要做一个高性能网络服务器,去网络上搜到到的都是C++版本而且是英文或者简单的DEMO,所以自己动手写了C# 的DEMO。 网络上只写接收到的数据,没有说怎么处理缓冲区数据,本DEMO简单的介绍如何处理接收到的数据。简单易用,希望对大家有用. 1、在C#中,不用去面对完成端口的操作系统内核对象,Microsoft已经为我们提供了SocketAsyncEventArgs类,它封装了IOCP的使用。请参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socketasynceventargs.aspx?cs-save-lang=1&cs-lang=cpp#code-snippet-1。 2、我的SocketAsyncEventArgsPool类使用List对象来存储对客户端来通信的SocketAsyncEventArgs对象,它相当于直接使用内核对象时的IoContext。我这样设计比用堆栈来实现的好处理是,我可以在SocketAsyncEventArgsPool池中找到任何一个与服务器连接的客户,主动向它发信息。而用堆栈来实现的话,要主动给客户发信息,则还要设计一个结构来存储已连接上服务器的客户。 3、对每一个客户端不管还发送还是接收,我使用同一个SocketAsyncEventArgs对象,对每一个客户端来说,通信是同步进行的,也就是说服务器高度保证同一个客户连接上要么在投递发送请求,并等待;或者是在投递接收请求,等待中。本例只做echo服务器,还未考虑由服务器主动向客户发送信息。 4、SocketAsyncEventArgs的UserToken被直接设定为被接受的客户端Socket。 5、没有使用BufferManager 类,因为我在初始化时给每一个SocketAsyncEventArgsPool中的对象分配一个缓冲区,发送时使用Arrary.Copy来进行字符拷贝,不去改变缓冲区的位置,只改变使用的长度,因此在下次投递接收请求时恢复缓冲区长度就可以了!如果要主动给客户发信息的话,可以new一个SocketAsyncEventArgs对象,或者在初始化中建立几个来专门用于主动发送信息,因为这种需求一般是进行信息群发,建立一个对象可以用于很多次信息发送,总体来看,这种花销不大,还减去了字符拷贝和消耗。 6、测试结果:(在我的笔记本上时行的,我的本本是T420 I7 8G内存) 100客户 100,000(十万次)不间断的发送接收数据(发送和接收之间没有Sleep,就一个一循环,不断的发送与接收) 耗时3004.6325 秒完成 总共 10,000,000 一千万次访问 平均每分完成 199,691.6 次发送与接收 平均每秒完成 3,328.2 次发送与接收 整个运行过程中,内存消耗在开始两三分种后就保持稳定不再增涨。 看了一下对每个客户端的延迟最多不超过2秒。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值