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服务器与客户端代码实例:
-
-
- #include
- #include
- #include
- #include
-
- using namespace std;
-
- #pragma comment(lib, "Ws2_32.lib") // Socket编程需用的动态链接库
- #pragma comment(lib, "Kernel32.lib") // IOCP需要用到的动态链接库
-
-
-
-
-
- const int DataBuffSize = 2 * 1024;
- typedef struct
- {
- OVERLAPPED overlapped;
- WSABUF databuff;
- char buffer[ DataBuffSize ];
- int BufferLen;
- int operationType;
- }PER_IO_OPERATEION_DATA, *LPPER_IO_OPERATION_DATA, *LPPER_IO_DATA, PER_IO_DATA;
-
-
-
-
-
-
- typedef struct
- {
- SOCKET socket;
- SOCKADDR_STORAGE ClientAddr;
- }PER_HANDLE_DATA, *LPPER_HANDLE_DATA;
-
-
- const int DefaultPort = 6000;
- vector < PER_HANDLE_DATA* > clientGroup;
-
- HANDLE hMutex = CreateMutex(NULL, FALSE, NULL);
- DWORD WINAPI ServerWorkThread(LPVOID CompletionPortID);
- DWORD WINAPI ServerSendThread(LPVOID IpParam);
-
-
- int main()
- {
-
- WORD wVersionRequested = MAKEWORD(2, 2);
- WSADATA wsaData;
- DWORD err = WSAStartup(wVersionRequested, &wsaData);
-
- if (0 != err){
- cerr << "Request Windows Socket Library Error!\n";
- system("pause");
- return -1;
- }
- if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2){
- WSACleanup();
- cerr << "Request Windows Socket Version 2.2 Error!\n";
- system("pause");
- return -1;
- }
-
-
-
-
-
-
-
-
-
-
-
- HANDLE completionPort = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 0, 0);
- if (NULL == completionPort){
- cerr << "CreateIoCompletionPort failed. Error:" << GetLastError() << endl;
- system("pause");
- return -1;
- }
-
-
-
-
- SYSTEM_INFO mySysInfo;
- GetSystemInfo(&mySysInfo);
-
-
- for(DWORD i = 0; i < (mySysInfo.dwNumberOfProcessors * 2); ++i){
-
- HANDLE ThreadHandle = CreateThread(NULL, 0, ServerWorkThread, completionPort, 0, NULL);
- if(NULL == ThreadHandle){
- cerr << "Create Thread Handle failed. Error:" << GetLastError() << endl;
- system("pause");
- return -1;
- }
- CloseHandle(ThreadHandle);
- }
-
-
- SOCKET srvSocket = socket(AF_INET, SOCK_STREAM, 0);
-
-
- SOCKADDR_IN srvAddr;
- srvAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
- srvAddr.sin_family = AF_INET;
- srvAddr.sin_port = htons(DefaultPort);
- int bindResult = bind(srvSocket, (SOCKADDR*)&srvAddr, sizeof(SOCKADDR));
- if(SOCKET_ERROR == bindResult){
- cerr << "Bind failed. Error:" << GetLastError() << endl;
- system("pause");
- return -1;
- }
-
-
- int listenResult = listen(srvSocket, 10);
- if(SOCKET_ERROR == listenResult){
- cerr << "Listen failed. Error: " << GetLastError() << endl;
- system("pause");
- return -1;
- }
-
-
- cout << "本服务器已准备就绪,正在等待客户端的接入...\n";
-
-
- HANDLE sendThread = CreateThread(NULL, 0, ServerSendThread, 0, 0, NULL);
-
- while(true){
- PER_HANDLE_DATA * PerHandleData = NULL;
- SOCKADDR_IN saRemote;
- int RemoteLen;
- SOCKET acceptSocket;
-
-
- RemoteLen = sizeof(saRemote);
- acceptSocket = accept(srvSocket, (SOCKADDR*)&saRemote, &RemoteLen);
- if(SOCKET_ERROR == acceptSocket){
- cerr << "Accept Socket Error: " << GetLastError() << endl;
- system("pause");
- return -1;
- }
-
-
- PerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA));
- PerHandleData -> socket = acceptSocket;
- memcpy (&PerHandleData -> ClientAddr, &saRemote, RemoteLen);
- clientGroup.push_back(PerHandleData);
-
-
- CreateIoCompletionPort((HANDLE)(PerHandleData -> socket), completionPort, (DWORD)PerHandleData, 0);
-
-
-
-
-
-
- LPPER_IO_OPERATION_DATA PerIoData = NULL;
- PerIoData = (LPPER_IO_OPERATION_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_OPERATEION_DATA));
- ZeroMemory(&(PerIoData -> overlapped), sizeof(OVERLAPPED));
- PerIoData->databuff.len = 1024;
- PerIoData->databuff.buf = PerIoData->buffer;
- PerIoData->operationType = 0;
-
- DWORD RecvBytes;
- DWORD Flags = 0;
- WSARecv(PerHandleData->socket, &(PerIoData->databuff), 1, &RecvBytes, &Flags, &(PerIoData->overlapped), NULL);
- }
-
- system("pause");
- return 0;
- }
-
-
- DWORD WINAPI ServerWorkThread(LPVOID IpParam)
- {
- HANDLE CompletionPort = (HANDLE)IpParam;
- DWORD BytesTransferred;
- LPOVERLAPPED IpOverlapped;
- LPPER_HANDLE_DATA PerHandleData = NULL;
- LPPER_IO_DATA PerIoData = NULL;
- DWORD RecvBytes;
- DWORD Flags = 0;
- BOOL bRet = false;
-
- while(true){
- bRet = GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (PULONG_PTR)&PerHandleData, (LPOVERLAPPED*)&IpOverlapped, INFINITE);
- if(bRet == 0){
- cerr << "GetQueuedCompletionStatus Error: " << 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 << "A Client says: " << PerIoData->databuff.buf << endl;
- ReleaseMutex(hMutex);
-
-
- ZeroMemory(&(PerIoData->overlapped), sizeof(OVERLAPPED));
- PerIoData->databuff.len = 1024;
- PerIoData->databuff.buf = PerIoData->buffer;
- PerIoData->operationType = 0;
- WSARecv(PerHandleData->socket, &(PerIoData->databuff), 1, &RecvBytes, &Flags, &(PerIoData->overlapped), NULL);
- }
-
- return 0;
- }
-
-
-
- DWORD WINAPI ServerSendThread(LPVOID IpParam)
- {
- while(1){
- char talk[200];
- gets(talk);
- int len;
- for (len = 0; talk[len] != '\0'; ++len){
-
- }
- talk[len] = '\n';
- talk[++len] = '\0';
- printf("I Say:");
- cout << talk;
- WaitForSingleObject(hMutex,INFINITE);
- for(int i = 0; i < clientGroup.size(); ++i){
- send(clientGroup[i]->socket, talk, 200, 0);
- }
- ReleaseMutex(hMutex);
- }
- return 0;
- }
-
-
- #include
- #include
- #include
- #include
- #include
- #include
-
- using namespace std;
-
- #pragma comment(lib, "Ws2_32.lib") // Socket编程需用的动态链接库
-
- SOCKET sockClient;
- HANDLE bufferMutex;
- const int DefaultPort = 6000;
-
- int main()
- {
-
- WORD wVersionRequested;
- WSADATA wsaData;
- wVersionRequested = MAKEWORD( 2, 2 );
- int err = WSAStartup( wVersionRequested, &wsaData );
- if ( err != 0 ) {
- return -1;
- }
- if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) {
- WSACleanup( );
- return -1;
- }
-
-
- sockClient = socket(AF_INET, SOCK_STREAM, 0);
- if(sockClient == INVALID_SOCKET) {
- printf("Error at socket():%ld\n", WSAGetLastError());
- WSACleanup();
- return -1;
- }
-
-
-
-
-
-
- SOCKADDR_IN addrSrv;
- addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
- addrSrv.sin_family = AF_INET;
- addrSrv.sin_port = htons(DefaultPort);
- while(SOCKET_ERROR == connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR))){
-
- cout << "服务器连接失败,是否重新连接?(Y/N):";
- char choice;
- while(cin >> choice && (!((choice != 'Y' && choice == 'N') || (choice == 'Y' && choice != 'N')))){
- cout << "输入错误,请重新输入:";
- cin.sync();
- cin.clear();
- }
- if (choice == 'Y'){
- continue;
- }
- else{
- cout << "退出系统中...";
- system("pause");
- return 0;
- }
- }
- cin.sync();
- cout << "本客户端已准备就绪,用户可直接输入文字向服务器反馈信息。\n";
-
- send(sockClient, "\nAttention: A Client has enter...\n", 200, 0);
-
- bufferMutex = CreateSemaphore(NULL, 1, 1, NULL);
-
- DWORD WINAPI SendMessageThread(LPVOID IpParameter);
- DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter);
-
- HANDLE sendThread = CreateThread(NULL, 0, SendMessageThread, NULL, 0, NULL);
- HANDLE receiveThread = CreateThread(NULL, 0, ReceiveMessageThread, NULL, 0, NULL);
-
-
- WaitForSingleObject(sendThread, INFINITE);
- closesocket(sockClient);
- CloseHandle(sendThread);
- CloseHandle(receiveThread);
- CloseHandle(bufferMutex);
- WSACleanup();
-
- printf("End linking...\n");
- printf("\n");
- system("pause");
- return 0;
- }
-
-
- DWORD WINAPI SendMessageThread(LPVOID IpParameter)
- {
- while(1){
- string talk;
- getline(cin, talk);
- WaitForSingleObject(bufferMutex, INFINITE);
- if("quit" == talk){
- talk.push_back('\0');
- send(sockClient, talk.c_str(), 200, 0);
- break;
- }
- else{
- talk.append("\n");
- }
- printf("\nI Say:(\"quit\"to exit):");
- cout << talk;
- send(sockClient, talk.c_str(), 200, 0);
- ReleaseSemaphore(bufferMutex, 1, NULL);
- }
- return 0;
- }
-
-
- DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter)
- {
- while(1){
- char recvBuf[300];
- recv(sockClient, recvBuf, 200, 0);
- WaitForSingleObject(bufferMutex, INFINITE);
-
- printf("%s Says: %s", "Server", recvBuf);
-
- ReleaseSemaphore(bufferMutex, 1, NULL);
- }
- return 0;
- }
五。本次学习资料
几翻周折,终于写出一个比较简单的IOCP模型的服务器与客户端啦,并且也大概了解这个模型的思路啦~没有买书的娃,伤不起啊,只能从网上搜罗资料,幸好有这些文章在,最后为下列这些文章的作者说声谢谢~