手把手教你完成端口之一(理论篇)

泰山鲁我辛辛苦苦写的东西,你转载的话可以但是写明转载,尊重原创,后面才会有更好的作品。
完成端口的例子见的太多了,著名的手把手教你完成端口,这个虽然经典但是一篇文章下来快1000个字!!写的人累,看的人更累,,而且附带的程序竟然是个有复杂结构的代码,初学完成端口的人看看直接吓跑了去linux下搞epool去了 ,我几次想看懂,最后都没看懂,后面实在没法看英文原著总算搞懂了。今天泰山鲁就手把手教你最简单的完成端口模型。代码我也上传了,想看的下了。

完成端口第一步:

完成端口完成端口就是一个句柄而已,没想象的那么复杂,至少留给我们使用的就是一个函数和一个handle指针而已。开始什么也不说创建一个对象。
// 创建完成端口对象
HANDLE hCompletion = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);

第二步:
跟普通的tcp通讯模型一样,创建一个套接字,并在套接字上监听,等待客户端连接信息的到来。这步完全的正常tcp模型,跟上步的完成端口还没到扯上关系的时候。

    // 创建监听套接字
    SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    SOCKADDR_IN si;
    si.sin_family = AF_INET;
    si.sin_port = ::htons(nPort);
    si.sin_addr.s_addr = INADDR_ANY;
    ::bind(sListen, (sockaddr*)&si, sizeof(si));
    ::listen(sListen, 10);


   //等待客户端来连
        SOCKADDR_IN saRemote;
        int nRemoteLen = sizeof(saRemote);
        printf("Accepting...\n");
        SOCKET sAcceptComport = ::accept(sListen, (sockaddr*)&saRemote, &nRemoteLen);
        //SOCKET sNew = ::accept(sListen, NULL, NULL);
        if (sAcceptComport == INVALID_SOCKET)
        {
            continue;
        }

第三步
当有连接来的时候,才开始讲这个连接跟第一步中创建的完成端口绑定起来
::CreateIoCompletionPort((HANDLE) pPerHandle->s, hCompletion, (ULONG_PTR)pPerHandle, 0);将来连接的客户端socket以及客户端的地址等信息封装在aPerHandle中,(完成端口终于跟我们常规的那种接受连接的方法撤上关系了,这也是画龙点睛之处之一),这样你暂时可以这样理解,我们已经通知完成端口了,以后这个连接上发生的事情都交给他管了。具体怎么管怎么管,各位看官向后看。


        PPER_HANDLE_DATA pPerHandle = (PPER_HANDLE_DATA)::GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA));
        pPerHandle->s = sAcceptComport;
        memcpy(&pPerHandle->addr, &saRemote, nRemoteLen);
        pPerHandle->nOperationType = OP_READ;
        ::CreateIoCompletionPort((HANDLE) pPerHandle->s, hCompletion, (ULONG_PTR)pPerHandle, 0);
        // 投递一个接收请求
        OVERLAPPED *pol = (OVERLAPPED *)::GlobalAlloc(GPTR, sizeof(OVERLAPPED));
        pPe**重点内容**rHandle->waDataBuf.buf = pPerHandle->buf;
        pPerHandle->waDataBuf.len = BUFFER_SIZE;
        DWORD dwRecv = 0;
        DWORD dwFlags = 0;
        ```

第四步 完成端口已经和客户端连接来的socket等信息关联了,接下来就是让完成端口帮你收听客户端的动静了。
就这么简单的一句,看似和完成端口扯不上关系。

::WSARecv(pPerHandle->s, &(pPerHandle->waDataBuf), 1, &dwRecv, &dwFlags, pol, NULL);

泰山鲁不仅要问读者是不是觉得还和传统接收的没什么区别吧,但是由于第三部已经将socket和完成端口绑定了,所以传统方式去接收,接收完了后,完成端口上也会收到收信息的信号和内容了。读者可以再翻到第三步看,是不是这样。注意有人看到 pPerHandle这个结构就害怕,其实别怕,没那么复杂,你就理解成把每个网络包来了就是一个aPerHandle结构,这你还怕个熊的。每来一个包一个这个结构,问我结构里是什么,你自己看。一个socket就是跟客户端通讯的socket,一个地址就是客户端地址,waDataBuf 就是客户端发过来的消息内容,比方说客户端说个“我爱你”就在waDataBuf 中,保存着的。懂了吧。注意pPerHandle->waDataBuf结构,这个结构直接决定了,客户端来的消息放在哪个里面,
这个参数是谁就决定一会从完成端口中取东西的时候从哪去取。

第五步
完成端口知道了,第四部中客户端发了条消息,我们怎么把数据取出来哪。
对,调用
BOOL bOK = ::GetQueuedCompletionStatus(hCompletion,
&dwTrans, (PULONG_PTR)&pPerHandle, &pOverLapped, WSA_INFINITE);
诸位:这个函数的两个参数一个是aperHandle,一个是overlapped。
这两个参数做什么用的哪简单来说,一个消息来了之后有两个必要的东西我们需要知道,一是 说话的哪个人是谁,二是那个人说了什么话。这样就一个存在pPerHandle,后者存在pOverLapped。这个跟第四部WSARecv骤中你把这两个变量怎么设的有关,我们这个第四步把pPerHandle的waDataBuf放在
WSARecv的缓存区中,所有取消息的时候也从pPerHandle去取了。pOverLapped参数就没什么大意思了,但是大型项目一般喜欢将句柄和消息分开的。就是pPerHandle只存客户端信息,pOverLapped只存消息信息。

取到东西后打印出来

        switch(pPerHandle->nOperationType)
        {
        case OP_READ:
            {
                MSG_ASK msgAsk = {0};
                memcpy(&msgAsk, pPerHandle->buf, sizeof(msgAsk));

                {
                    msgAsk.szBuffer[strlen(msgAsk.szBuffer) + 1] = '\n';
                    printf(msgAsk.szBuffer);
                    printf("Recv bytes = %d, msgAsk.size = %d msgAsk.userid=%d \n", dwTrans, msgAsk.iBodySize,msgAsk.UserID);
                }
                MSG_BODY msgBody = {0};
                memcpy(&msgBody, pPerHandle->buf + msgAsk.iBodySize, sizeof(MSG_BODY));
                if (msgBody.iOpType == OP_READ && msgBody.iBodySize == sizeof(MSG_BODY))
                {
                    printf("msgBody.szBuffer = %s\n", msgBody.szBuffer);
                }
                MSG_ACK msgAck = {0};
                msgAck.iCheckCode = CHECK_CODE;
                memcpy(msgAck.szBuffer, "This is the ack package",
                    strlen("This is the ack package"));
                ..............................
                }

就可以了,从完成端口中把里面的收到的东西 一条一条取出来。
第六步
第五步已经接受完毕了,大家晓得WSARecv(pPerHandle->s, &(pPerHandle->waDataBuf), 1, &dwRecv, &dwFlags, pol, NULL)函数只执行一遍啊,所以为了让完成端口收到这条消息之后,继续听客户端的下一个数据”我又爱你”,就需要再次调用WSARecv(pPerHandle->s, &(pPerHandle->waDataBuf), 1, &dwRecv, &dwFlags, pol, NULL),专业的话说就是完成端口上再次投递一次收听事件。微软为什么不弄个参数,使调用者在投递一次recver就可以收听客户端后面所有的消息哪?泰山鲁只能告诉你,这个你问比尔盖茨去,目前的完成端口模式都是必须这样用的。

                // 继续投递发送I/O请求
                pPerHandle->nOperationType = OP_READ;
                WSABUF buf;
                buf.buf = (char*)&msgAck;
                buf.len = sizeof(MSG_ACK);
                OVERLAPPED *pol = (OVERLAPPED *)::GlobalAlloc(GPTR, sizeof(OVERLAPPED));
                pol->Pointer=(void*)new char[100];
                DWORD dwFlags = 0, dwSend = 0;
                DWORD dwRecv=0;
                WSABUF *buf2;
                ZeroMemory(pPerHandle->waDataBuf.buf,32);
                buf2=&(pPerHandle->waDataBuf);
                ::WSARecv(pPerHandle->s, buf2, 1, &dwRecv, &dwFlags, pol, NULL);

各位看完了吗,泰山鲁欢迎大家的提问,源码在我的资源里,大家可以找,项目的名字叫做“c+完成端口,最简单的例子(附带测试客户端程序)”和”完成端口的测试程序(服务器程序查本人上传的资源中找)”,一个是这个博客对应的程序,一个是一个简单连接测试工具,这个只是个简单的完成端口模型,仅供学习用,实际生产中这个代码很多地方都是得改进的。
欢迎大家提宝贵意见,泰山鲁写了一个小时累了,回家哄孩子去了。20160912

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值