手把手教你完成端口之一(理论篇)
来源:互联网 发布:迷你电饭煲品牌知乎 编辑:程序博客网 时间:2024/05/21 14:06
泰山鲁我辛辛苦苦写的东西,你转载的话可以但是写明转载,尊重原创,后面才会有更好的作品。
完成端口的例子见的太多了,著名的手把手教你完成端口,这个虽然经典但是一篇文章下来快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
- 手把手教你完成端口之一(理论篇)
- 手把手教你完成端口之二(应用中的完成端口简单模型)
- 完成端口(CompletionPort)详解 - 手把手教你玩转网络编程系列之三
- 完成端口详解 - 手把手教你玩转网络编程系列之三
- 完成端口详解 - 手把手教你玩转网络编程系列之三
- 完成端口(CompletionPort)详解 - 手把手教你玩转网络编程系列之三
- 完成端口(CompletionPort)详解 - 手把手教你玩转网络编程系列之三
- 完成端口(CompletionPort)详解 - 手把手教你玩转网络编程系列之三
- 完成端口(CompletionPort)详解 - 手把手教你玩转网络编程系列之三 .
- 完成端口(CompletionPort)详解 - 手把手教你玩转网络编程系列之三 (转)
- 完成端口(CompletionPort)详解 - 手把手教你玩转网络编程系列之三
- 完成端口(CompletionPort)详解 - 手把手教你玩转网络编程系列之三
- 完成端口(CompletionPort)详解 - 手把手教你玩转网络编程系列之三
- 完成端口(CompletionPort)详解 - 手把手教你玩转网络编程系列之三
- 完成端口(CompletionPort)详解 - 手把手教你玩转网络编程系列之三
- 完成端口(CompletionPort)详解 - 手把手教你玩转网络编程系列之三
- 完成端口(CompletionPort)详解 - 手把手教你玩转网络编程系列之三
- 完成端口(CompletionPort)详解 - 手把手教你玩转网络编程系列之三
- easyui 里 table 的 form提交后台获取的值全为null
- 电脑连接打印机
- maven jetty 多模块部署 配置
- Pro Git学习笔记(二)
- 创建序列帧动画特效
- 手把手教你完成端口之一(理论篇)
- 搜狗笔试题:输入任意个偶数,找任意两个偶数之间的素数的个数的总和
- enum_learn
- 快捷使用netsh导出、导入windows网络配置
- TCP/IP协议三次握手与四次握手流程解析
- 快速幂及矩阵应用(学习)
- 定时器
- hadoop2.5.2加节点
- 第三周 项目3-求集合并集