IOCP模型与网络编

一。前言:
        在老师分配任务(“尝试利用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 <WinSock2.h>  
  4 #include <Windows.h>  
  5 #include <vector>  
  6 #include <iostream>  
  7   
  8 using namespace std;  
  9   
 10 #pragma comment(lib, "Ws2_32.lib")      // Socket编程需用的动态链接库  
 11 #pragma comment(lib, "Kernel32.lib")    // IOCP需要用到的动态链接库  
 12   
 13 /** 
 14  * 结构体名称:PER_IO_DATA 
 15  * 结构体功能:重叠I/O需要用到的结构体,临时记录IO数据 
 16  **/  
 17 const int DataBuffSize  = 2 * 1024;  
 18 typedef struct  
 19 {  
 20     OVERLAPPED overlapped;  
 21     WSABUF databuff;  
 22     char buffer[ DataBuffSize ];  
 23     int BufferLen;  
 24     int operationType;  
 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 typedef struct  
 33 {  
 34     SOCKET socket;  
 35     SOCKADDR_STORAGE ClientAddr;  
 36 }PER_HANDLE_DATA, *LPPER_HANDLE_DATA;  
 37   
 38 // 定义全局变量  
 39 const int DefaultPort = 6000;         
 40 vector < PER_HANDLE_DATA* > clientGroup;      // 记录客户端的向量组  
 41   
 42 HANDLE hMutex = CreateMutex(NULL, FALSE, NULL);  
 43 DWORD WINAPI ServerWorkThread(LPVOID CompletionPortID);  
 44 DWORD WINAPI ServerSendThread(LPVOID IpParam);  
 45   
 46 // 开始主函数  
 47 int main()  
 48 {  
 49 // 加载socket动态链接库  
 50     WORD wVersionRequested = MAKEWORD(2, 2); // 请求2.2版本的WinSock库  
 51     WSADATA wsaData;    // 接收Windows Socket的结构信息  
 52     DWORD err = WSAStartup(wVersionRequested, &wsaData);  
 53   
 54     if (0 != err){  // 检查套接字库是否申请成功  
 55         cerr << "Request Windows Socket Library Error!\n";  
 56         system("pause");  
 57         return -1;  
 58     }  
 59     if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2){// 检查是否申请了所需版本的套接字库  
 60         WSACleanup();  
 61         cerr << "Request Windows Socket Version 2.2 Error!\n";  
 62         system("pause");  
 63         return -1;  
 64     }  
 65   
 66 // 创建IOCP的内核对象  
 67     /** 
 68      * 需要用到的函数的原型: 
 69      * HANDLE WINAPI CreateIoCompletionPort( 
 70      *    __in   HANDLE FileHandle,     // 已经打开的文件句柄或者空句柄,一般是客户端的句柄 
 71      *    __in   HANDLE ExistingCompletionPort, // 已经存在的IOCP句柄 
 72      *    __in   ULONG_PTR CompletionKey,   // 完成键,包含了指定I/O完成包的指定文件 
 73      *    __in   DWORD NumberOfConcurrentThreads // 真正并发同时执行最大线程数,一般推介是CPU核心数*2 
 74      * ); 
 75      **/  
 76     HANDLE completionPort = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 0, 0);  
 77     if (NULL == completionPort){    // 创建IO内核对象失败  
 78         cerr << "CreateIoCompletionPort failed. Error:" << GetLastError() << endl;  
 79         system("pause");  
 80         return -1;  
 81     }  
 82   
 83 // 创建IOCP线程--线程里面创建线程池  
 84   
 85     // 确定处理器的核心数量  
 86     SYSTEM_INFO mySysInfo;  
 87     GetSystemInfo(&mySysInfo);  
 88   
 89     // 基于处理器的核心数量创建线程  
 90     for(DWORD i = 0; i < (mySysInfo.dwNumberOfProcessors * 2); ++i){  
 91         // 创建服务器工作器线程,并将完成端口传递到该线程  
 92         HANDLE ThreadHandle = CreateThread(NULL, 0, ServerWorkThread, completionPort, 0, NULL);  
 93         if(NULL == ThreadHandle){  
 94             cerr << "Create Thread Handle failed. Error:" << GetLastError() << endl;  
 95         system("pause");  
 96             return -1;  
 97         }  
 98         CloseHandle(ThreadHandle);  
 99     }  
100   
101 // 建立流式套接字  
102     SOCKET srvSocket = socket(AF_INET, SOCK_STREAM, 0);  
103   
104 // 绑定SOCKET到本机  
105     SOCKADDR_IN srvAddr;  
106     srvAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);  
107     srvAddr.sin_family = AF_INET;  
108     srvAddr.sin_port = htons(DefaultPort);  
109     int bindResult = bind(srvSocket, (SOCKADDR*)&srvAddr, sizeof(SOCKADDR));  
110     if(SOCKET_ERROR == bindResult){  
111         cerr << "Bind failed. Error:" << GetLastError() << endl;  
112         system("pause");  
113         return -1;  
114     }  
115   
116 // 将SOCKET设置为监听模式  
117     int listenResult = listen(srvSocket, 10);  
118     if(SOCKET_ERROR == listenResult){  
119         cerr << "Listen failed. Error: " << GetLastError() << endl;  
120         system("pause");  
121         return -1;  
122     }  
123       
124 // 开始处理IO数据  
125     cout << "本服务器已准备就绪,正在等待客户端的接入...\n";  
126   
127     // 创建用于发送数据的线程  
128     HANDLE sendThread = CreateThread(NULL, 0, ServerSendThread, 0, 0, NULL);  
129   
130     while(true){  
131         PER_HANDLE_DATA * PerHandleData = NULL;  
132         SOCKADDR_IN saRemote;  
133         int RemoteLen;  
134         SOCKET acceptSocket;  
135   
136         // 接收连接,并分配完成端,这儿可以用AcceptEx()  
137         RemoteLen = sizeof(saRemote);  
138         acceptSocket = accept(srvSocket, (SOCKADDR*)&saRemote, &RemoteLen);  
139         if(SOCKET_ERROR == acceptSocket){   // 接收客户端失败  
140             cerr << "Accept Socket Error: " << GetLastError() << endl;  
141             system("pause");  
142             return -1;  
143         }  
144           
145         // 创建用来和套接字关联的单句柄数据信息结构  
146         PerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA));  // 在堆中为这个PerHandleData申请指定大小的内存  
147         PerHandleData -> socket = acceptSocket;  
148         memcpy (&PerHandleData -> ClientAddr, &saRemote, RemoteLen);  
149         clientGroup.push_back(PerHandleData);       // 将单个客户端数据指针放到客户端组中  
150   
151         // 将接受套接字和完成端口关联  
152         CreateIoCompletionPort((HANDLE)(PerHandleData -> socket), completionPort, (DWORD)PerHandleData, 0);  
153   
154           
155         // 开始在接受套接字上处理I/O使用重叠I/O机制  
156         // 在新建的套接字上投递一个或多个异步  
157         // WSARecv或WSASend请求,这些I/O请求完成后,工作者线程会为I/O请求提供服务      
158         // 单I/O操作数据(I/O重叠)  
159         LPPER_IO_OPERATION_DATA PerIoData = NULL;  
160         PerIoData = (LPPER_IO_OPERATION_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_OPERATEION_DATA));  
161         ZeroMemory(&(PerIoData -> overlapped), sizeof(OVERLAPPED));  
162         PerIoData->databuff.len = 1024;  
163         PerIoData->databuff.buf = PerIoData->buffer;  
164         PerIoData->operationType = 0;    // read  
165   
166         DWORD RecvBytes;  
167         DWORD Flags = 0;  
168         WSARecv(PerHandleData->socket, &(PerIoData->databuff), 1, &RecvBytes, &Flags, &(PerIoData->overlapped), NULL);  
169     }  
170   
171     system("pause");  
172     return 0;  
173 }  
174   
175 // 开始服务工作线程函数  
176 DWORD WINAPI ServerWorkThread(LPVOID IpParam)  
177 {  
178     HANDLE CompletionPort = (HANDLE)IpParam;  
179     DWORD BytesTransferred;  
180     LPOVERLAPPED IpOverlapped;  
181     LPPER_HANDLE_DATA PerHandleData = NULL;  
182     LPPER_IO_DATA PerIoData = NULL;  
183     DWORD RecvBytes;  
184     DWORD Flags = 0;  
185     BOOL bRet = false;  
186   
187     while(true){  
188         bRet = GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (PULONG_PTR)&PerHandleData, (LPOVERLAPPED*)&IpOverlapped, INFINITE);  
189         if(bRet == 0){  
190             cerr << "GetQueuedCompletionStatus Error: " << GetLastError() << endl;  
191             return -1;  
192         }  
193         PerIoData = (LPPER_IO_DATA)CONTAINING_RECORD(IpOverlapped, PER_IO_DATA, overlapped);  
194           
195         // 检查在套接字上是否有错误发生  
196         if(0 == BytesTransferred){  
197             closesocket(PerHandleData->socket);  
198             GlobalFree(PerHandleData);  
199             GlobalFree(PerIoData);  
200             continue;  
201         }  
202           
203         // 开始数据处理,接收来自客户端的数据  
204         WaitForSingleObject(hMutex,INFINITE);  
205         cout << "A Client says: " << PerIoData->databuff.buf << endl;  
206         ReleaseMutex(hMutex);  
207   
208         // 为下一个重叠调用建立单I/O操作数据  
209         ZeroMemory(&(PerIoData->overlapped), sizeof(OVERLAPPED)); // 清空内存  
210         PerIoData->databuff.len = 1024;  
211         PerIoData->databuff.buf = PerIoData->buffer;  
212         PerIoData->operationType = 0;    // read  
213         WSARecv(PerHandleData->socket, &(PerIoData->databuff), 1, &RecvBytes, &Flags, &(PerIoData->overlapped), NULL);  
214     }  
215   
216     return 0;  
217 }  
218   
219   
220 // 发送信息的线程执行函数  
221 DWORD WINAPI ServerSendThread(LPVOID IpParam)  
222 {  
223     while(1){  
224         char talk[200];  
225         gets(talk);  
226         int len;  
227         for (len = 0; talk[len] != '\0'; ++len){  
228             // 找出这个字符组的长度  
229         }  
230         talk[len] = '\n';  
231         talk[++len] = '\0';  
232         printf("I Say:");  
233         cout << talk;  
234         WaitForSingleObject(hMutex,INFINITE);  
235         for(int i = 0; i < clientGroup.size(); ++i){  
236             send(clientGroup[i]->socket, talk, 200, 0);  // 发送信息  
237         }  
238         ReleaseMutex(hMutex);   
239     }  
240     return 0;  
241 }  
  1 // IOCP_TCPIP_Socket_Client.cpp  
  2   
  3 #include <iostream>  
  4 #include <cstdio>  
  5 #include <string>  
  6 #include <cstring>  
  7 #include <winsock2.h>  
  8 #include <Windows.h>  
  9   
 10 using namespace std;  
 11   
 12 #pragma comment(lib, "Ws2_32.lib")      // Socket编程需用的动态链接库  
 13   
 14 SOCKET sockClient;      // 连接成功后的套接字  
 15 HANDLE bufferMutex;     // 令其能互斥成功正常通信的信号量句柄  
 16 const int DefaultPort = 6000;  
 17   
 18 int main()  
 19 {  
 20 // 加载socket动态链接库(dll)  
 21     WORD wVersionRequested;  
 22     WSADATA wsaData;    // 这结构是用于接收Wjndows Socket的结构信息的  
 23     wVersionRequested = MAKEWORD( 2, 2 );   // 请求2.2版本的WinSock库  
 24     int err = 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("Error at socket():%ld\n", WSAGetLastError());   
 37         WSACleanup();   
 38         return -1;   
 39       }   
 40   
 41 // 将套接字sockClient与远程主机相连  
 42     // int connect( SOCKET s,  const struct sockaddr* name,  int namelen);  
 43     // 第一个参数:需要进行连接操作的套接字  
 44     // 第二个参数:设定所需要连接的地址信息  
 45     // 第三个参数:地址的长度  
 46     SOCKADDR_IN addrSrv;  
 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         char choice;  
 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             return 0;  
 66         }  
 67     }  
 68     cin.sync();  
 69     cout << "本客户端已准备就绪,用户可直接输入文字向服务器反馈信息。\n";  
 70   
 71     send(sockClient, "\nAttention: A Client has enter...\n", 200, 0);  
 72   
 73     bufferMutex = CreateSemaphore(NULL, 1, 1, NULL);   
 74   
 75     DWORD WINAPI SendMessageThread(LPVOID IpParameter);  
 76     DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter);  
 77   
 78     HANDLE sendThread = CreateThread(NULL, 0, SendMessageThread, NULL, 0, NULL);    
 79     HANDLE receiveThread = 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("End linking...\n");  
 90     printf("\n");  
 91     system("pause");  
 92     return 0;  
 93 }  
 94   
 95   
 96 DWORD WINAPI SendMessageThread(LPVOID IpParameter)  
 97 {  
 98     while(1){  
 99         string talk;  
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("\nI Say:(\"quit\"to exit):");  
111         cout << talk;  
112         send(sockClient, talk.c_str(), 200, 0); // 发送信息  
113         ReleaseSemaphore(bufferMutex, 1, NULL);     // V(资源占用完毕)   
114     }  
115     return 0;  
116 }  
117   
118   
119 DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter)  
120 {  
121     while(1){     
122         char recvBuf[300];  
123         recv(sockClient, recvBuf, 200, 0);  
124         WaitForSingleObject(bufferMutex, INFINITE);     // P(资源未被占用)    
125   
126         printf("%s Says: %s", "Server", recvBuf);       // 接收信息  
127           
128         ReleaseSemaphore(bufferMutex, 1, NULL);     // V(资源占用完毕)   
129     }  
130     return 0;  
131 } 

五。本次学习资料
       几翻周折,终于写出一个比较简单的IOCP模型的服务器与客户端啦,并且也大概了解这个模型的思路啦~没有买书的娃,伤不起啊,只能从网上搜罗资料,幸好有这些文章在,最后为下列这些文章的作者说声谢谢~

 转http://blog.csdn.net/neicole/article/details/7549497

posted @ 2014-08-25 09:24  _**  阅读(775)  评论(0编辑  收藏  举报