一。前言:
在老师分配任务(“尝试利用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