IOCP的学习总结
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
IOCP的学习总结
IOCP(I/O Completion Port),常称I/O完成端口。
IOCP模型属于一种通讯模型,适用于(能控制并发执行的)高负载服务器的一个技术。
通俗一点说,就是用于高效处理很多很多的客户端进行数据交换的一个模型。
或者可以说,就是能异步I/O操作的模型。
1、基本概念
IOCP全称I/O Completion Port,中文译为I/O完成端口。
IOCP是一个异步I/O 的API,它可以高效地将I/O事件通知给应用程序。
与使用select()或是其它异步方法不同的是,一个套接字[socket]与一个完成端口关联了起来,然后就可继续进行正常的Winsock操作了。
然而,当一个事件发生的时候,此完成端口就将被操作系统加入一个队列中。
然后应用程序可以对核心层进行查询以得到此完成端口。
2、IOCP模型的优缺点
优点:
①帮助维持重复使用的内存池。
(与重叠I/O技术有关)
②去除删除线程创建/终结负担。
③利于管理,分配线程,控制并发,最小化的线程上下文切换。
④优化线程调度,提高CPU和内存缓冲的命中率。
缺点:
理解以及编码的复杂度较高。
对使用者有一定要求。
需了解以下基本知识:
①同步与异步
②阻塞与非阻塞
③重叠I/O技术
④多线程
⑤栈、队列这两种基本的数据结构
3、相关API
①与SOCKET相关
1、链接套接字动态链接库:int WSAStartup(...);
2、创建套接字库:SOCKET socket(...);
3、绑字套接字:int bind(...);
4、套接字设为监听状态:int listen(...);
5、接收套接字:SOCKET accept(...);
6、向指定套接字发送信息:int send(...);
7、从指定套接字接收信息:int recv(...);[1]
②与线程相关
1、创建线程:HANDLE CreateThread(...);
③重叠I/O技术相关
1、向套接字发送数据:int WSASend(...);
2、向套接字发送数据包:int WSASendTo(...);
3、从套接字接收数据:int WSARecv(...);
4、从套接字接收数据包:int WSARecvFrom(...);
④IOCP相关
1、创建/关联完成端口:HANDLE WINAPI CreateIoCompletionPort(...);
2、获取队列完成状态: BOOL WINAPI GetQueuedCompletionStatus(...);
3、投递一个队列完成状态:BOOL WINAPI PostQueuedCompletionStatus(...);
4、详细概念
这里我要对上面的一些概念略作补充,在解释[完成]两字之前,我想先简单的提一下同步和异步这两个概念,逻辑上来讲做完一件事后再去做另一件事就是同步,而同时一起做两件或两件以上事的话就是异步了。
你也可以拿单线程和多线程来作比喻。
但是我们一定要将同步和堵塞,异步和非堵塞区分开来,所谓的堵塞函数诸如accept(…),当调用此函数后,此时线程将挂起,直到操作系统来通知它,“HEY兄弟,有人连进来了”,那个挂起的线程将继续进行工作,也就符合”生产者-消费者”模型。
堵塞和同步看上去有两分相似,但却是完全不同的概念。
大家都知道I/O设备是个相对慢速的设备,不论打印机,调制解调器,甚至硬盘,与CPU相比都是奇慢无比的,坐下来等I/O 的完成是一件不甚明智的事情,有时候数据的流动率非常惊人,把数据从你的文件服务器中以Ethernet速度搬走,其速度可能高达每秒一百万字节,如果你尝试从文件服务器中读取100KB,在用户的眼光来看几乎是瞬间完成,但是,要知道,你的线程执行这个命令,已经浪费了10个一百万次CPU周期。
所以说,我们一般使用另一个线程来
进行I/O。
重叠IO[overlapped I/O]是Win32的一项技术,你可以要求操作系统为你传送数据,并且在传送完毕时通知你。
这也就是[完成]的含义。
这项技术使你的程序在I/O进行过程中仍然能够继续处理事务。
事实上,操作系统内部正是以线程来完成overlapped I/O。
你可以获得线程所有利益,而不需要付出什么痛苦的代价。
完成端口中所谓的[端口]并不是我们在TCP/IP中所提到的端口,可以说是完全没有关系。
IOCP只不过是用来进行读写操作,和文件I/O倒是有些类似。
既然是一个读写设备,我们所能要求它的只是在处理读与写上的高效。
下面是一个VC++实现IOCP服务器框架的流程:[1]
//IOCP需要包含的头文件
#include<WinSock2.h>
#include<Windows.h>
#pragmacomment(lib,"Ws2_32.lib")//Socket编程需用的动态链接库
#pragmacomment(lib,"Kernel32.lib")//IOCP需要用到的动态链接库
当然,IOCP也是基于Winsock2套接字库的,所以在开始先必须初始化套接字库。
//加载socket动态链接库
WORDwVersionRequested=MAKEWORD(2,2);//请求2.2版本的WinSock库WSADATAwsaData;//接收WindowsSocket的结构信息
DWORDerr=WSAStartup(wVersionRequested,&wsaData);
if(0!=err){//检查套接字库是否申请成功
cerr<<"RequestWindowsSocketLibraryError!\n";
system("pause");
return-1;
}
if(LOBYTE(wsaData.wVersion)!=2||HIBYTE(wsaData.wVersion)!=2){/ /检查是否申请了所需版本的套接字库
WSACleanup();
cerr<<"RequestWindowsSocketVersion2.2Error!\n";
system("pause");
return-1;
}
IOCP除了需一个服务器套接字外,还需一个完成端口的句柄,该句柄直接调用API
//创建IOCP句柄
/**
*函数的原型:
*HANDLEWINAPICreateIoCompletionPort(
*__inHANDLEFileHandle,//已经打开的文件句柄或者空句柄,一般是客户端的句柄
*__inHANDLEExistingCompletionPort,//已经存在的IOCP句柄
*__inULONG_PTRCompletionKey,//完成键,包含了指定I/O完成包的指定文件
*__inDWORDNumberOfConcurrentThreads//真正并发同时执行最大线程数,一般推荐是CPU核心数*2
*);
**/
HANDLE
m_hIocp=CreateIoCompletionPort(INVALID_HANDLE_VALUE,0,0,0);
//创建服务器套接字,这里要注意的是最后一个参数必须为:
WSA_FLAG_OVERLAPPED重叠模式
SOCKET
m_Server=WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERL APPED);
之后为套接字绑定一个本地端口,用来监听客户端的连接
SOCKADDR _INaddr;
addr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
addr.sin_family=AF_INET;
addr.sin_port=htons(2000);
bind(m_Server,(SOCKADDR*)&addr,sizeof(SOCKADDR);
绑定端口之后,建立一个监听线程,用来监听客户端的连接,当有连接进来时,将该连接的套接字加入到IOCP对队列中,同时再创建几个工作线程,这样在该连接发生请求时,IOCP模型就会在工作线程通知,这样我们就可以在工作线程中,完成对客户端的请求做出一系列响应。
//需要用到的结构体
/**
*结构体名称:PER_IO_DATA
*结构体功能:重叠I/O需要用到的结构体,临时记录IO数据
**/
constintDataBuffSize=2*1024;
typedefstruct
OVERLAPPEDoverlapped;
WSABUFdatabuff;
charbuffer[DataBuffSize];
intBufferLen;
intoperationType;
}PER_IO_OPERATEION_DATA,*LPPER_IO_OPERATION_DATA,*LPPER_IO_DAT A,PER_IO_DATA;
//监听线程流程
UINTWINAPIAcceptThreadProc(LPVOIDlpParameter){
sockaddr _insockAddr={0};
intlen=sizeof(sockAddr);
while(TRUE){
SOCKETsNew=WSAAccept(m_Server,(sockaddr*)&sockAddr,&len,NULL,N ULL);
//将客户端加入到IOCP队列中
CreateIoCompletionPort((HANDLE)sNew,m_hIocp,ULONG_PTR(sNew),0) ;
//这里要注意一下,对网上一些相关的资料,一些新手在写IOCP时
//会发现得不到用户请求及一些事件,那是因为没有对该用户投递一个接收IO //IOCP的工作原理就是,必须对一个连接投递一个接收IO,处理完一个,再投递一个...
LPPER_IO_OPERATION_DATAPerIoData=NULL;
PerIoData=(LPPER_IO_OPERATION_DATA)GlobalAlloc(GPTR,sizeof(PER _IO_OPERATEION_DATA));ZeroMemory(&(PerIoData->overlapped),size of(OVERLAPPED));
PerIoData->databuff.len=1024;
PerIoData->databuff.buf=PerIoData->buffer;
PerIoData->operationType=0;//read
DWORDRecvBytes;
DWORDFlags=0;
WSARecv(PerHandleData->socket,&(PerIoData->databuff),1,&RecvBy tes,&Flags,&(PerIoData->overlapped),NULL);
intnError=WSAGetLastError();
//在投递接收IO时。
返回值要注意。
如果为nError==ERROR_IO_PENDING说明已经投递成功。
}}
//工作线程流程
UINTWINAPIWorkerThreadProc(LPVOIDlpParameter)
{
DWORDCompleteBytes=0;
DWORDdwKey=0;
POVERLAPPEDPLUSol=NULL;
BOOLbRet=false;
bRet=GetQueuedCompletionStatus(CompletionPort,&BytesTransferre d,(PULONG_PTR)&PerHandleData,(LPOVERLAPPED*)&IpOverlapped,INFI NITE);
if(bRet==0){
cerr<<"GetQueuedCompletionStatusError:"<<GetLastError()<<endl; return-1;
}
PerIoData=(LPPER_IO_DATA)CONTAINING_RECORD(IpOverlapped,PER_IO _DATA,overlapped);
//检查在套接字上是否有错误发生
if(0==BytesTransferred){
closesocket(PerHandleData->socket);
GlobalFree(PerHandleData);
GlobalFree(PerIoData);
continue;
}
//开始数据处理,接收来自客户端的数据
WaitForSingleObject(hMutex,INFINITE);
cout<<"AClientsays:"<<PerIoData->databuff.buf<<endl; ReleaseMutex(hMutex);
//为下一个重叠调用建立单I/O操作数据
ZeroMemory(&(PerIoData->overlapped),sizeof(OVERLAPPED));//清空内存
PerIoData->databuff.len=1024;
PerIoData->databuff.buf=PerIoData->buffer;
PerIoData->operationType=0;//read
WSARecv(PerHandleData->socket,&(PerIoData->databuff),1,&RecvBy tes,&Flags,&(PerIoData->overlapped),NULL);
}
if(!bRet)
{
//客户端断开了连接
}
}
工作线程中循环调用了GetQueuedCompletionStatus 这个API来取出当前活动的套接字,当套接字有数据应请求或者断开时。
该函数也会立即返回。
如果要对工作线程停止,PostQueuedCompletionStatus 使用该API 向工作发送停止命令,工作线程收到此命令后,可以安全退出线程。
以上就是大致实现流程,仅限于VC++开发使用,由于其它平台语言实现起来有些困难,可以采用一些第三方做的IOCP支持库,如网上现比较好用的有易语言和C++版的,IOCP服务器模型支持库,该第三方库写的比较好用。
可以直接使用的。
还有一些其它版本的。
实现原理也是以上方法。