windows IOCP模型
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服務器與客戶端代碼實例:
?
// IOCP_TCPIP_Socket_Server.cpp#include <WinSock2.h> #include <Windows.h> #include <vector> #include <iostream>using namespace std;#pragma comment(lib, "Ws2_32.lib") // Socket編程需用的動態鏈接庫 #pragma comment(lib, "Kernel32.lib") // IOCP需要用到的動態鏈接庫/*** 結構體名稱:PER_IO_DATA* 結構體功能:重疊I/O需要用到的結構體,臨時記錄IO數據**/ 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;/*** 結構體名稱:PER_HANDLE_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() { // 加載socket動態鏈接庫WORD wVersionRequested = MAKEWORD(2, 2); // 請求2.2版本的WinSock庫WSADATA wsaData; // 接收Windows Socket的結構信息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;}// 創建IOCP的內核對象/*** 需要用到的函數的原型:* HANDLE WINAPI CreateIoCompletionPort(* __in HANDLE FileHandle, // 已經打開的文件句柄或者空句柄,一般是客戶端的句柄* __in HANDLE ExistingCompletionPort, // 已經存在的IOCP句柄* __in ULONG_PTR CompletionKey, // 完成鍵,包含了指定I/O完成包的指定文件* __in DWORD NumberOfConcurrentThreads // 真正并發同時執行最大線程數,一般推介是CPU核心數*2* );**/HANDLE completionPort = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 0, 0);if (NULL == completionPort){ // 創建IO內核對象失敗cerr << "CreateIoCompletionPort failed. Error:" << GetLastError() << endl;system("pause");return -1;}// 創建IOCP線程--線程里面創建線程池// 確定處理器的核心數量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);// 綁定SOCKET到本機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;}// 將SOCKET設置為監聽模式int listenResult = listen(srvSocket, 10);if(SOCKET_ERROR == listenResult){cerr << "Listen failed. Error: " << GetLastError() << endl;system("pause");return -1;}// 開始處理IO數據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;// 接收連接,并分配完成端,這兒可以用AcceptEx()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申請指定大小的內存PerHandleData -> socket = acceptSocket;memcpy (&PerHandleData -> ClientAddr, &saRemote, RemoteLen);clientGroup.push_back(PerHandleData); // 將單個客戶端數據指針放到客戶端組中// 將接受套接字和完成端口關聯CreateIoCompletionPort((HANDLE)(PerHandleData -> socket), completionPort, (DWORD)PerHandleData, 0);// 開始在接受套接字上處理I/O使用重疊I/O機制// 在新建的套接字上投遞一個或多個異步// WSARecv或WSASend請求,這些I/O請求完成后,工作者線程會為I/O請求提供服務 // 單I/O操作數據(I/O重疊)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; // readDWORD 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);// 為下一個重疊調用建立單I/O操作數據ZeroMemory(&(PerIoData->overlapped), sizeof(OVERLAPPED)); // 清空內存PerIoData->databuff.len = 1024;PerIoData->databuff.buf = PerIoData->buffer;PerIoData->operationType = 0; // readWSARecv(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; }
?
總結
以上是生活随笔為你收集整理的windows IOCP模型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: json调试
- 下一篇: angular学习笔记(十九)-自定义指