对于TCP协议中IOCP模型的一些简单的理解

请不要觉得这一篇没有代码的文章没意义,对IOCP模型的代码,百度搜索可以得到很多,但是后续很多需要纠结的地方,很多人都经历过,如果你已经在尝试写IOCP服务端了,那么你很可能会对写代码之外的一些设计问题很纠结,那么本文很可能是对你有所帮助的,这一个帖子是我开的讨论帖,我不是很懂CSDN的帖子分数的意义,我觉得那对于我这种1年难得发1贴的人来说估计也没什么作用,但我很希望大家能一起参与进来讨论:http://bbs.csdn.net/topics/390890567?page=1#post-398223652

首先一点,我要说的是,我个人认为,在IOCP TCP服务端,IO重叠的意义不大,为什么这么说呢?

TCP是一个数据包流,TCP并不会向使用者保证所谓的一条封包的完整性,同样的,TCP也没有限定使用必须每一次发送只能是一条完整的封包,那么实际上在TCP 服务器中,如果使用者真正的考虑到这一点,就应该尽可能的将数条客户端的请求结果在一段固定分配的缓冲区上构建出来,然后通过WSASend一次性投递给操作系统。


那么,说到WSASend,就应该好好考虑WSABUF参数的问题了,百度百科中提到 lpBuffers 是可以是局部变量,因为操作系统会在调用返回前对参数数组做一份拷贝,这里指的仅仅是WSABUF数组,并不是这个结构中的东西操作系统也会保存,msdn中对WSABUF.buf 的解释如下:

 once the WSASend function is called, the system owns these buffers and the application may not access them


当 WSASend 被调用时,操作系统拥有这些缓冲区,应用程序不能访问它们,注意它的用词是may not,我的理解是,应用程序其实是可以访问的,但是如果是在发送完成前操作了这个缓冲区的数据,那么,发送出去的结果,就有问题了,这个问题先不谈了。


那么从这里可以看出,在IOCP中,在完成端口通知完成之前,使用者是有责任保证WSABUF.buf是必须有效的指针,可这和IO重叠有什么关系呢?举个简单的例子说下:


服务端投递了WSARecv,完成端口通知完成时,收到了一大串东西,假如,这一串东西里含有100条客户端的请求,每一条请求,都需要服务端一 一应答。


如果是这种情况,虽然有很多文章提到过,连续的使用WSASend是没有问题的,多次调用,那么必然要涉及到一个问题,缓冲区必须要是动态分配的,发完了又清理掉,或者你有能力实现高效的内存池,但是不管是如何实现,IOCP这种基于线程池的技术,都必要要涉及到互斥,不管你设计的东西有多么高效,但是在这一步骤上,线程池已经没有任何意义了,就算你从new malloc VirtualAlloc等等分配内存的东西,操作系统也是要经过互斥,速度比自己的内存池还要慢,那么从这里来看,任何影响到线程池效率的方式,在这里,都是和IOCP模型的基本思想背道而驰的,因为我们的目的是要利用IOCP处理更多的连接,处理更多的请求,如果我们把主要的精力放到如何释放内存,如何管理内存池,那么,这和我们最初的目的,不是就完全不相同了么?


我要说的是,请不要忽视TCP 流的思想,既然是TCP,我们何必去一条一条的发送呢?在TCP中,实现一套完整的协议头来正确的处理,是一种必须做的事了,否则还用TCP干什么?那么,对于大家所谓的粘包,半包这种问题,都已经处理得很熟练了,实际上,也就是在协议头中有长度信息嘛,那么为什么不考虑,将数条包直接在一段缓冲区中直接构建出来呢?客户端发送给我们的东西,既然是会粘在一起的,那么,我们发送给客户端的东西,实际上也可能是粘在一起的,既然可能粘在一起,已经是一种必然,我们为什么不索性让它们直接粘在一起呢?


说到这里,没有这么做的大部分人已经都知道应该怎么做了,但此时,新的问题来了,既然决定要在一段缓冲区上直接构建应答包了,那么我们如何保证这个缓冲区的大小是否够用,我们不希望随时new delete malloc mfree等,如果不动态分配,不使用后备缓冲区的技术,我们无法保证缓冲区是否够用。


思前想后,想了无数种办法,要保证缓冲区绝对够用,就没可能不动态分配,那么,干脆就不要保证缓冲区是否够,而且对于较长的应答包,我们也无法完全保证缓冲区是否够。

步骤如下:

1、投递WSARecv
2、假如收到客户端100条请求粘在一起
3、进行协议解密,分包流程,一条一条的进行,同时将应答结果直接填到发送缓冲区里,这个过程不使用任何动态分配,如果缓冲区不够了,保存好处理到什么位置。然后将已经处理好的部分WSASend投递出去。
4、完成端口通知完成WSASend后,再来看,上次请求有没有处理完,如果没有,继续循环3,4步骤。
5、直到上次接收到的请求处理完成时,如果最后余下的字节为不完整的请求,使用memmove将数据往接收缓冲区头部移动,下次投递的buf为,首地址 + 剩余的长度,len为固定分配的长度 - 剩余的长度,执行步骤1.






  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
IOCP是一种高效的I/O多路复用模型,适用于Windows系统上的网络编程。下面是一个简单的使用IOCP模型的C代码示例,实现了一个简单的回显服务器: ``` #include <stdio.h> #include <stdlib.h> #include <string.h> #include <winsock2.h> #include <windows.h> #define BUFSIZE 1024 typedef struct _PER_HANDLE_DATA { SOCKET Socket; SOCKADDR_STORAGE ClientAddr; } PER_HANDLE_DATA, *LPPER_HANDLE_DATA; typedef struct _PER_IO_DATA { OVERLAPPED Overlapped; WSABUF DataBuf; char Buffer[BUFSIZE]; int OperationType; } PER_IO_DATA, *LPPER_IO_DATA; DWORD WINAPI WorkerThread(LPVOID lpParam); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET ListenSocket, AcceptSocket; SOCKADDR_STORAGE LocalAddr, ClientAddr; DWORD Flags; LPPER_HANDLE_DATA PerHandleData; LPPER_IO_DATA PerIoData; DWORD RecvBytes, SendBytes; DWORD BytesTransferred; DWORD ThreadId; HANDLE CompletionPort, ThreadHandle; int i; // 初始化Winsock if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { printf("WSAStartup failed with error: %d\n", WSAGetLastError()); return 1; } // 创建完成端口 CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); if (CompletionPort == NULL) { printf("CreateIoCompletionPort failed with error: %d\n", GetLastError()); return 1; } // 创建监听套接字 ListenSocket = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); if (ListenSocket == INVALID_SOCKET) { printf("socket failed with error: %d\n", WSAGetLastError()); return 1; } // 绑定地址并监听 memset(&LocalAddr, 0, sizeof(LocalAddr)); LocalAddr.ss_family = AF_INET6; ((SOCKADDR_IN6*)&LocalAddr)->sin6_port = htons(12345); ((SOCKADDR_IN6*)&LocalAddr)->sin6_addr = in6addr_any; if (bind(ListenSocket, (SOCKADDR*)&LocalAddr, sizeof(LocalAddr)) == SOCKET_ERROR) { printf("bind failed with error: %d\n", WSAGetLastError()); closesocket(ListenSocket); return 1; } if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR) { printf("listen failed with error: %d\n", WSAGetLastError()); closesocket(ListenSocket); return 1; } // 创建工作线程 for (i = 0; i < 2; i++) { ThreadHandle = CreateThread(NULL, 0, WorkerThread, CompletionPort, 0, &ThreadId); if (ThreadHandle == NULL) { printf("CreateThread failed with error: %d\n", GetLastError()); return 1; } CloseHandle(ThreadHandle); } // 接受连接并关联到完成端口 while (1) { AcceptSocket = accept(ListenSocket, (SOCKADDR*)&ClientAddr, NULL); if (AcceptSocket == INVALID_SOCKET) { printf("accept failed with error: %d\n", WS

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值