频道栏目
首页 > 程序开发 > 综合编程 > 其他综合 > 正文
IOCP模型与网络编程
2016-05-03 09:06:32      个评论    来源:jiangqin115的专栏  
收藏   我要投稿

一。前言:
在老师分配任务(“尝试利用IOCP模型写出服务端和客户端的代码”)给我时,脑子一片空白,并不知道什么是IOCP模型,会不会是像软件设计模式里面的工厂模式,装饰模式之类的那些呢?嘿嘿,不过好像是一个挺好玩的东西,挺好奇是什么东西来的,又是一个新知识啦~于是,开始去寻找一大堆的资料,为这个了解做准备,只是呢,有时还是想去找一本书去系统地学习一下,毕竟网络的资料还是有点零散。话说,本人学习这个模型的基础是,写过一个简单的Socket服务器及客户端程序,外加一个简单的Socket单服务器对多客户端程序,懂一点点的操作系统原理的知识。于是,本着一个学习与应用的态度开始探究这个IOCP是个什么东西。

 

二。提出相关问题:
1. IOCP模型是什么?
2. IOCP模型是用来解决什么问题的?它为什么存在?
3. 使用IOCP模型需要用到哪些知识?
4. 如何使用IOCP模型与Socket网络编程结合起来?
5. 学会了这个模型以后与我之前写过的简单的socket程序主要有哪些不同点?

 

三。部分问题探究及解决:(绝大多数是个人理解,再加上个人是菜鸟,如果有什么不对的地方,欢迎指正)
1. 什么是IOCP?什么是IOCP模型?IOCP模型有什么作用?
1) IOCP(I/O Completion Port),常称I/O完成端口。
2) IOCP模型属于一种通讯模型,适用于(能控制并发执行的)高负载服务器的一个技术。
3) 通俗一点说,就是用于高效处理很多很多的客户端进行数据交换的一个模型。
4) 或者可以说,就是能异步I/O操作的模型。
5) 只是了解到这些会让人很糊涂,因为还是不知道它究意具体是个什么东东呢?


下面我想给大家看三个图:
第一个是IOCP的内部工作队列图。(整合于《IOCP本质论》文章,在英文的基础上加上中文对照)
\

第二个是程序实现IOCP模型的基本步骤。(整合于《深入解释IOCP》,加个人观点、理解、翻译)
\
?

第三个是使用了IOCP模型及没使用IOCP模型的程序流程图。(个人理解绘制)
\

 

2. IOCP的存在理由(IOCP的优点)及技术相关有哪些?
之前说过,很通俗地理解可以理解成是用于高效处理很多很多的客户端进行数据交换的一个模型,那么,它具体的优点有些什么呢?它到底用到了哪些技术了呢?在Windows环境下又如何去使用这些技术来编程呢?它主要使用上哪些API函数呢?呃~看来我真是一个问题多多的人,跟前面提出的相关问题变种延伸了不少的问题,好吧,下面一个个来解决。

 

1) 使用IOCP模型编程的优点
① 帮助维持重复使用的内存池。(与重叠I/O技术有关)
② 去除删除线程创建/终结负担。
③ 利于管理,分配线程,控制并发,最小化的线程上下文切换。
④ 优化线程调度,提高CPU和内存缓冲的命中率。

2) 使用IOCP模型编程汲及到的知识点(无先后顺序)
① 同步与异步
② 阻塞与非阻塞
③ 重叠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、创建线程:HANDLE CreateThread(...);

③ 重叠I/O技术相关
1、向套接字发送数据: int WSASend(...);
2、向套接字发送数据包: int WSASendFrom(...);
3、从套接字接收数据: int WSARecv(...);
4、从套接字接收数据包: int WSARecvFrom(...);

④ IOCP相关
1、创建完成端口: HANDLE WINAPI CreateIoCompletionPort(...);
2、关联完成端口: HANDLE WINAPI CreateIoCompletionPort(...);
3、获取队列完成状态: BOOL WINAPI GetQueuedCompletionStatus(...);
4、投递一个队列完成状态:BOOL WINAPI PostQueuedCompletionStatus(...);

 

四。完整的简单的IOCP服务器与客户端代码实例:

\

 

  1. //IOCP_TCPIP_Socket_Server.cpp
  2.  
  3. #include
  4. #include
  5. #include
  6. #include
  7.  
  8. usingnamespacestd;
  9.  
  10. #pragmacomment(lib,"Ws2_32.lib")//Socket编程需用的动态链接库
  11. #pragmacomment(lib,"Kernel32.lib")//IOCP需要用到的动态链接库
  12.  
  13. /**
  14. *结构体名称:PER_IO_DATA
  15. *结构体功能:重叠I/O需要用到的结构体,临时记录IO数据
  16. **/
  17. constintDataBuffSize=2*1024;
  18. typedefstruct
  19. {
  20. OVERLAPPEDoverlapped;
  21. WSABUFdatabuff;
  22. charbuffer[DataBuffSize];
  23. intBufferLen;
  24. intoperationType;
  25. }PER_IO_OPERATEION_DATA,*LPPER_IO_OPERATION_DATA,*LPPER_IO_DATA,PER_IO_DATA;
  26.  
  27. /**
  28. *结构体名称:PER_HANDLE_DATA
  29. *结构体存储:记录单个套接字的数据,包括了套接字的变量及套接字的对应的客户端的地址。
  30. *结构体作用:当服务器连接上客户端时,信息存储到该结构体中,知道客户端的地址以便于回访。
  31. **/
  32. typedefstruct
  33. {
  34. SOCKETsocket;
  35. SOCKADDR_STORAGEClientAddr;
  36. }PER_HANDLE_DATA,*LPPER_HANDLE_DATA;
  37.  
  38. //定义全局变量
  39. constintDefaultPort=6000;
  40. vectorclientGroup;//记录客户端的向量组
  41.  
  42. HANDLEhMutex=CreateMutex(NULL,FALSE,NULL);
  43. DWORDWINAPIServerWorkThread(LPVOIDCompletionPortID);
  44. DWORDWINAPIServerSendThread(LPVOIDIpParam);
  45.  
  46. //开始主函数
  47. intmain()
  48. {
  49. //加载socket动态链接库
  50. WORDwVersionRequested=MAKEWORD(2,2);//请求2.2版本的WinSock库
  51. WSADATAwsaData;//接收WindowsSocket的结构信息
  52. DWORDerr=WSAStartup(wVersionRequested,&wsaData);
  53.  
  54. if(0!=err){//检查套接字库是否申请成功
  55. cerr<<"RequestWindowsSocketLibraryError!\n";
  56. system("pause");
  57. return-1;
  58. }
  59. if(LOBYTE(wsaData.wVersion)!=2||HIBYTE(wsaData.wVersion)!=2){//检查是否申请了所需版本的套接字库
  60. WSACleanup();
  61. cerr<<"RequestWindowsSocketVersion2.2Error!\n";
  62. system("pause");
  63. return-1;
  64. }
  65.  
  66. //创建IOCP的内核对象
  67. /**
  68. *需要用到的函数的原型:
  69. *HANDLEWINAPICreateIoCompletionPort(
  70. *__inHANDLEFileHandle,//已经打开的文件句柄或者空句柄,一般是客户端的句柄
  71. *__inHANDLEExistingCompletionPort,//已经存在的IOCP句柄
  72. *__inULONG_PTRCompletionKey,//完成键,包含了指定I/O完成包的指定文件
  73. *__inDWORDNumberOfConcurrentThreads//真正并发同时执行最大线程数,一般推介是CPU核心数*2
  74. *);
  75. **/
  76. HANDLEcompletionPort=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);
  77. if(NULL==completionPort){//创建IO内核对象失败
  78. cerr<<"CreateIoCompletionPortfailed.Error:"< system("pause");
  79. return-1;
  80. }
  81.  
  82. //创建IOCP线程--线程里面创建线程池
  83.  
  84. //确定处理器的核心数量
  85. SYSTEM_INFOmySysInfo;
  86. GetSystemInfo(&mySysInfo);
  87.  
  88. //基于处理器的核心数量创建线程
  89. for(DWORDi=0;i<(mySysInfo.dwNumberOfProcessors*2);++i){
  90. //创建服务器工作器线程,并将完成端口传递到该线程
  91. HANDLEThreadHandle=CreateThread(NULL,0,ServerWorkThread,completionPort,0,NULL);
  92. if(NULL==ThreadHandle){
  93. cerr<<"CreateThreadHandlefailed.Error:"< system("pause");
  94. return-1;
  95. }
  96. CloseHandle(ThreadHandle);
  97. }
  98.  
  99. //建立流式套接字
  100. SOCKETsrvSocket=socket(AF_INET,SOCK_STREAM,0);
  101.  
  102. //绑定SOCKET到本机
  103. SOCKADDR_INsrvAddr;
  104. srvAddr.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
  105. srvAddr.sin_family=AF_INET;
  106. srvAddr.sin_port=htons(DefaultPort);
  107. intbindResult=bind(srvSocket,(SOCKADDR*)&srvAddr,sizeof(SOCKADDR));
  108. if(SOCKET_ERROR==bindResult){
  109. cerr<<"Bindfailed.Error:"< system("pause");
  110. return-1;
  111. }
  112.  
  113. //将SOCKET设置为监听模式
  114. intlistenResult=listen(srvSocket,10);
  115. if(SOCKET_ERROR==listenResult){
  116. cerr<<"Listenfailed.Error:"< system("pause");
  117. return-1;
  118. }
  119.  
  120. //开始处理IO数据
  121. cout<<"本服务器已准备就绪,正在等待客户端的接入...\n";
  122.  
  123. //创建用于发送数据的线程
  124. HANDLEsendThread=CreateThread(NULL,0,ServerSendThread,0,0,NULL);
  125.  
  126. while(true){
  127. PER_HANDLE_DATA*PerHandleData=NULL;
  128. SOCKADDR_INsaRemote;
  129. intRemoteLen;
  130. SOCKETacceptSocket;
  131.  
  132. //接收连接,并分配完成端,这儿可以用AcceptEx()
  133. RemoteLen=sizeof(saRemote);
  134. acceptSocket=accept(srvSocket,(SOCKADDR*)&saRemote,&RemoteLen);
  135. if(SOCKET_ERROR==acceptSocket){//接收客户端失败
  136. cerr<<"AcceptSocketError:"< system("pause");
  137. return-1;
  138. }
  139.  
  140. //创建用来和套接字关联的单句柄数据信息结构
  141. PerHandleData=(LPPER_HANDLE_DATA)GlobalAlloc(GPTR,sizeof(PER_HANDLE_DATA));//在堆中为这个PerHandleData申请指定大小的内存
  142. PerHandleData->socket=acceptSocket;
  143. memcpy(&PerHandleData->ClientAddr,&saRemote,RemoteLen);
  144. clientGroup.push_back(PerHandleData);//将单个客户端数据指针放到客户端组中
  145.  
  146. //将接受套接字和完成端口关联
  147. CreateIoCompletionPort((HANDLE)(PerHandleData->socket),completionPort,(DWORD)PerHandleData,0);
  148.  
  149.  
  150. //开始在接受套接字上处理I/O使用重叠I/O机制
  151. //在新建的套接字上投递一个或多个异步
  152. //WSARecv或WSASend请求,这些I/O请求完成后,工作者线程会为I/O请求提供服务
  153. //单I/O操作数据(I/O重叠)
  154. LPPER_IO_OPERATION_DATAPerIoData=NULL;
  155. PerIoData=(LPPER_IO_OPERATION_DATA)GlobalAlloc(GPTR,sizeof(PER_IO_OPERATEION_DATA));
  156. ZeroMemory(&(PerIoData->overlapped),sizeof(OVERLAPPED));
  157. PerIoData->databuff.len=1024;
  158. PerIoData->databuff.buf=PerIoData->buffer;
  159. PerIoData->operationType=0;//read
  160.  
  161. DWORDRecvBytes;
  162. DWORDFlags=0;
  163. WSARecv(PerHandleData->socket,&(PerIoData->databuff),1,&RecvBytes,&Flags,&(PerIoData->overlapped),NULL);
  164. }
  165.  
  166. system("pause");
  167. return0;
  168. }
  169.  
  170. //开始服务工作线程函数
  171. DWORDWINAPIServerWorkThread(LPVOIDIpParam)
  172. {
  173. HANDLECompletionPort=(HANDLE)IpParam;
  174. DWORDBytesTransferred;
  175. LPOVERLAPPEDIpOverlapped;
  176. LPPER_HANDLE_DATAPerHandleData=NULL;
  177. LPPER_IO_DATAPerIoData=NULL;
  178. DWORDRecvBytes;
  179. DWORDFlags=0;
  180. BOOLbRet=false;
  181.  
  182. while(true){
  183. bRet=GetQueuedCompletionStatus(CompletionPort,&BytesTransferred,(PULONG_PTR)&PerHandleData,(LPOVERLAPPED*)&IpOverlapped,INFINITE);
  184. if(bRet==0){
  185. cerr<<"GetQueuedCompletionStatusError:"< return-1;
  186. }
  187. PerIoData=(LPPER_IO_DATA)CONTAINING_RECORD(IpOverlapped,PER_IO_DATA,overlapped);
  188.  
  189. //检查在套接字上是否有错误发生
  190. if(0==BytesTransferred){
  191. closesocket(PerHandleData->socket);
  192. GlobalFree(PerHandleData);
  193. GlobalFree(PerIoData);
  194. continue;
  195. }
  196.  
  197. //开始数据处理,接收来自客户端的数据
  198. WaitForSingleObject(hMutex,INFINITE);
  199. cout<<"AClientsays:"<databuff.buf< ReleaseMutex(hMutex);
  200.  
  201. //为下一个重叠调用建立单I/O操作数据
  202. ZeroMemory(&(PerIoData->overlapped),sizeof(OVERLAPPED));//清空内存
  203. PerIoData->databuff.len=1024;
  204. PerIoData->databuff.buf=PerIoData->buffer;
  205. PerIoData->operationType=0;//read
  206. WSARecv(PerHandleData->socket,&(PerIoData->databuff),1,&RecvBytes,&Flags,&(PerIoData->overlapped),NULL);
  207. }
  208.  
  209. return0;
  210. }
  211.  
  212.  
  213. //发送信息的线程执行函数
  214. DWORDWINAPIServerSendThread(LPVOIDIpParam)
  215. {
  216. while(1){
  217. chartalk[200];
  218. gets(talk);
  219. intlen;
  220. for(len=0;talk[len]!='\0';++len){
  221. //找出这个字符组的长度
  222. }
  223. talk[len]='\n';
  224. talk[++len]='\0';
  225. printf("ISay:");
  226. cout< WaitForSingleObject(hMutex,INFINITE);
  227. for(inti=0;i send(clientGroup[i]->socket,talk,200,0);//发送信息
  228. }
  229. ReleaseMutex(hMutex);
  230. }
  231. return0;
  232. }

     

    1. //IOCP_TCPIP_Socket_Client.cpp
    2.  
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9.  
    10. usingnamespacestd;
    11.  
    12. #pragmacomment(lib,"Ws2_32.lib")//Socket编程需用的动态链接库
    13.  
    14. SOCKETsockClient;//连接成功后的套接字
    15. HANDLEbufferMutex;//令其能互斥成功正常通信的信号量句柄
    16. constintDefaultPort=6000;
    17.  
    18. intmain()
    19. {
    20. //加载socket动态链接库(dll)
    21. WORDwVersionRequested;
    22. WSADATAwsaData;//这结构是用于接收WjndowsSocket的结构信息的
    23. wVersionRequested=MAKEWORD(2,2);//请求2.2版本的WinSock库
    24. interr=WSAStartup(wVersionRequested,&wsaData);
    25. if(err!=0){//返回值为零的时候是表示成功申请WSAStartup
    26. return-1;
    27. }
    28. if(LOBYTE(wsaData.wVersion)!=2||HIBYTE(wsaData.wVersion)!=2){//检查版本号是否正确
    29. WSACleanup();
    30. return-1;
    31. }
    32.  
    33. //创建socket操作,建立流式套接字,返回套接字号sockClient
    34. sockClient=socket(AF_INET,SOCK_STREAM,0);
    35. if(sockClient==INVALID_SOCKET){
    36. printf("Erroratsocket():%ld\n",WSAGetLastError());
    37. WSACleanup();
    38. return-1;
    39. }
    40.  
    41. //将套接字sockClient与远程主机相连
    42. //intconnect(SOCKETs,conststructsockaddr*name,intnamelen);
    43. //第一个参数:需要进行连接操作的套接字
    44. //第二个参数:设定所需要连接的地址信息
    45. //第三个参数:地址的长度
    46. SOCKADDR_INaddrSrv;
    47. addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");//本地回路地址是127.0.0.1;
    48. addrSrv.sin_family=AF_INET;
    49. addrSrv.sin_port=htons(DefaultPort);
    50. while(SOCKET_ERROR==connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR))){
    51. //如果还没连接上服务器则要求重连
    52. cout<<"服务器连接失败,是否重新连接?(Y/N):";
    53. charchoice;
    54. while(cin>>choice&&(!((choice!='Y'&&choice=='N')||(choice=='Y'&&choice!='N')))){
    55. cout<<"输入错误,请重新输入:";
    56. cin.sync();
    57. cin.clear();
    58. }
    59. if(choice=='Y'){
    60. continue;
    61. }
    62. else{
    63. cout<<"退出系统中...";
    64. system("pause");
    65. return0;
    66. }
    67. }
    68. cin.sync();
    69. cout<<"本客户端已准备就绪,用户可直接输入文字向服务器反馈信息。\n";
    70.  
    71. send(sockClient,"\nAttention:AClienthasenter...\n",200,0);
    72.  
    73. bufferMutex=CreateSemaphore(NULL,1,1,NULL);
    74.  
    75. DWORDWINAPISendMessageThread(LPVOIDIpParameter);
    76. DWORDWINAPIReceiveMessageThread(LPVOIDIpParameter);
    77.  
    78. HANDLEsendThread=CreateThread(NULL,0,SendMessageThread,NULL,0,NULL);
    79. HANDLEreceiveThread=CreateThread(NULL,0,ReceiveMessageThread,NULL,0,NULL);
    80.  
    81.  
    82. WaitForSingleObject(sendThread,INFINITE);//等待线程结束
    83. closesocket(sockClient);
    84. CloseHandle(sendThread);
    85. CloseHandle(receiveThread);
    86. CloseHandle(bufferMutex);
    87. WSACleanup();//终止对套接字库的使用
    88.  
    89. printf("Endlinking...\n");
    90. printf("\n");
    91. system("pause");
    92. return0;
    93. }
    94.  
    95.  
    96. DWORDWINAPISendMessageThread(LPVOIDIpParameter)
    97. {
    98. while(1){
    99. stringtalk;
    100. getline(cin,talk);
    101. WaitForSingleObject(bufferMutex,INFINITE);//P(资源未被占用)
    102. if("quit"==talk){
    103. talk.push_back('\0');
    104. send(sockClient,talk.c_str(),200,0);
    105. break;
    106. }
    107. else{
    108. talk.append("\n");
    109. }
    110. printf("\nISay:(\"quit\"toexit):");
    111. cout< send(sockClient,talk.c_str(),200,0);//发送信息
    112. ReleaseSemaphore(bufferMutex,1,NULL);//V(资源占用完毕)
    113. }
    114. return0;
    115. }
    116.  
    117.  
    118. DWORDWINAPIReceiveMessageThread(LPVOIDIpParameter)
    119. {
    120. while(1){
    121. charrecvBuf[300];
    122. recv(sockClient,recvBuf,200,0);
    123. WaitForSingleObject(bufferMutex,INFINITE);//P(资源未被占用)
    124.  
    125. printf("%sSays:%s","Server",recvBuf);//接收信息
    126.  
    127. ReleaseSemaphore(bufferMutex,1,NULL);//V(资源占用完毕)
    128. }
    129. return0;


    130. }
点击复制链接 与好友分享!回本站首页
相关TAG标签 网络编程 模型
上一篇:Generic Netlink内核实现分析(二):通信
下一篇:[从头学声学] 第202节 复合的声波(1)
相关文章
图文推荐
点击排行

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站