網絡模型—IOCP模型
一. 什么是IOCP?什么是IOCP模型?IOCP模型有什么作用?
1) IOCP(I/O Completion Port),常稱I/O完成端口。
2) IOCP模型屬於一種通訊模型,適用於(能控制並發執行的)高負載服務器的一個技術,適用於大型項目,處理高並發問題。
3) 通俗一點說,就是用於高效處理很多很多的客戶端進行數據交換的一個模型。
4) 或者可以說,就是能異步I/O操作的模型。
二. IOCP 工作機制
盡管select、WSAA、WSAE這些socket通信模型可以讓我們不用開
更多的線程來處理每一連接,但它們收發數據時仍然要調用Recv和Send,Recv和Send實際上仍然會與操作系統底層進行交互,仍然會進入內核,所以還是會有效率上的損失。
IOCP怎么解決這個問題呢?IOCP有一個隊列,當你要發數據時,收數據和連接時,都交由IOCP隊列處理,不會與操作系統底層交互。

發送數據時,先將緩沖區和長度封好,這個請求會發送到IOCP隊列,IOCP內部會幫你把請求發出去。
收數據時,收數據的請求丟掉IOCP隊列,IOCP會將收到的數據填入指定的緩沖區里邊,當數據收好后會通知你來收數據。
建立連接時,IOCP幫你把連接建立好,告訴你新的連接已經來了。
開發者使用IOCP時無需關注數據收、發、連接,只需關注處理數據
三. IOCP的存在理由(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、創建ICOCP對象: HANDLE WINAPI CreateIoCompletionPort(...);
( 這個對象可以收發對象)
HANDLE CreateIoCompletionPort (
HANDLE FileHandle, // 句柄,首次創建時填INVALID_HANDLE_VALUE
HANDLE ExistingCompletionPort, // I/O完成端口句柄 ,首次創建給NULL
ULONG_PTR CompletionKey, // 創建自定義對象
DWORD NumberOfConcurrentThreads //允許應用程序同時執行的線程數量,填0,根據CPU核數自動計算核數
該函數實際用於兩個明顯有別的目的:
a. 用於創建一個完成端口對象。
b. 將一個句柄同完成端口關聯到一起。
2、關聯完成端口: HANDLE WINAPI CreateIoCompletionPort(...);
(關聯需要通過IOCP收發數據的socket)
3.向IOCP隊列投遞接受連接的請求:BOOL AcceptEx(...);
通知IOCP,讓IOCP建立連接(可以異步操作),
它可以接收連接,還可以在建立連接之后,等客戶端發第一次數據(或者在建立連接之后等客戶端不收后邊的數據)
BOOL AcceptEx(
SOCKET sListenSocket, //監聽socket,之前用到的socket
SOCKET sAcceptSocket, //用來接收傳入socket,與客戶端socket建立連接
PVOID lpOutputBuffer, //用來接收數據的緩沖區
DWORD dwReceiveDataLength, //緩沖區大小,一般填0
DWORD dwLocalAddressLength, //本地地址sockaddr大小,
此值必須至少比正在使用的傳輸協議的最大地址長度多16個字節
DWORD dwRemoteAddressLength, //遠程地址信息保留的字節數,此值必須至少
比正在使用的傳輸協議的最大地址長度多16個字節(填寫同上)
LPDWORD lpdwBytesReceived, //返回數據的大小
LPOVERLAPPED lpOverlapped);
4、.檢測隊列,從隊列中取出完成的請求 BOOL WINAPI GetQueuedCompletionStatus(...);
BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort, // 句柄
LPDWORD lpNumberOfBytes, // 接收字節數
PULONG_PTR lpCompletionKey, // 自定義參數
LPOVERLAPPED *lpOverlapped, //返回的結構體參數
DWORD dwMilliseconds); //等待的時間
5、投遞一個隊列完成狀態:BOOL WINAPI PostQueuedCompletionStatus(...);
四. 使用實例:(使用IOCP用來處理收發數據)
服務端:
1 #include <iostream> 2 #include <vector> 3 using namespace std; 4 5 #define FD_SETSIZE 128 6 #define WIN32_LEAN_AND_MEAN 7 #include <windows.h> 8 #include <Winsock2.h> 9 #pragma comment(lib, "Ws2_32.lib") 10 11 #include <Mswsock.h> 12 #pragma comment(lib, "Mswsock.lib") 13 14 void InitWs2(); 15 void UninitWs32(); 16 void PostAccept(SOCKET sockListen, HANDLE hIocp); 17 void PostRecv(SOCKET sock); 18 19 enum IO_EVENT 20 { 21 IO_ACCEPT, 22 IO_RECV, 23 IO_SEND 24 }; 25 26 struct MYOV :public OVERLAPPED 27 { 28 MYOV(SOCKET sock, IO_EVENT event) 29 { 30 memset(this, 0, sizeof(MYOV)); 31 m_sockClient = sock; 32 m_buf.buf = m_btBuf; 33 m_buf.len = sizeof(m_btBuf); 34 m_dwBytesRecved = 0; 35 m_dwFlag = 0; 36 m_event = event; 37 } 38 IO_EVENT m_event; 39 SOCKET m_sockClient; 40 WSABUF m_buf; 41 CHAR m_btBuf[MAXBYTE]; 42 DWORD m_dwBytesRecved; 43 DWORD m_dwFlag; 44 }; 45 46 int main() 47 { 48 InitWs2(); 49 50 SOCKET sockServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 51 if (sockServer == SOCKET_ERROR) 52 { 53 printf("socket 創建失敗\r\n"); 54 return 0; 55 } 56 else 57 { 58 printf("socket 創建成功\r\n"); 59 } 60 61 //2) 62 sockaddr_in si; 63 si.sin_family = AF_INET; 64 si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); 65 si.sin_port = htons(9527); 66 int nRet = bind(sockServer, (sockaddr*)&si, sizeof(si)); 67 if (nRet == SOCKET_ERROR) 68 { 69 printf("綁定端口失敗\r\n"); 70 return 0; 71 } 72 else 73 { 74 printf("綁定端口成功\r\n"); 75 } 76 77 //3) 78 nRet = listen(sockServer, SOMAXCONN); 79 if (nRet == SOCKET_ERROR) 80 { 81 printf("監聽失敗 \r\n"); 82 return 0; 83 } 84 else 85 { 86 printf("監聽成功 \r\n"); 87 } 88 89 //1)創建IOCP對象 90 ULONG uKey = 0; 91 HANDLE hIocp = CreateIoCompletionPort( 92 INVALID_HANDLE_VALUE, 93 NULL, 94 NULL, 95 0); 96 97 //2) 關聯IOCP和socket對象 98 HANDLE bRet=CreateIoCompletionPort( 99 (HANDLE)sockServer, 100 hIocp, 101 NULL, 102 0); 103 104 //3)投遞一個接收連接的請求 105 PostAccept(sockServer,hIocp); 106 107 //遍歷隊列 108 while (true) 109 { 110 DWORD dwBytesTranfered = 0; 111 ULONG_PTR uKey; 112 LPOVERLAPPED pOv = NULL; 113 GetQueuedCompletionStatus( 114 hIocp, 115 &dwBytesTranfered, 116 &uKey, 117 &pOv, 118 INFINITE 119 ); 120 121 122 MYOV* pov = (MYOV*)pOv; 123 124 switch (pov->m_event) 125 { 126 //接收新的連接 127 case IO_ACCEPT: 128 //連接完成后,再次投遞一個連接的請求 129 PostAccept(sockServer, hIocp); 130 cout << " 有新的連接接入" << endl; 131 PostRecv(pov->m_sockClient); 132 break; 133 case IO_RECV: 134 //投遞一個接收數據的請求 135 printf("接收到數據%s\r\n", pov->m_btBuf); 136 PostRecv(pov->m_sockClient); 137 break; 138 default: 139 break; 140 } 141 } 142 } 143 144 void PostRecv(SOCKET sock) 145 { 146 //接收數據的請求 147 MYOV* pOv = new MYOV(sock, IO_RECV); 148 int nRet = WSARecv( 149 sock, 150 &pOv->m_buf, 1, 151 &pOv->m_dwBytesRecved, 152 &pOv->m_dwFlag, 153 pOv, 154 NULL); 155 } 156 157 void PostAccept(SOCKET sockListen,HANDLE hIocp) 158 { 159 //接收連接的請求 160 SOCKET sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 161 HANDLE hRet = CreateIoCompletionPort( 162 (HANDLE)sockClient, 163 hIocp, 164 NULL, 165 0); 166 167 char szBuff[MAXBYTE] = { 0 }; 168 DWORD dwRecved = 0; 169 170 MYOV* pOv = new MYOV(sockClient, IO_ACCEPT); 171 172 AcceptEx( 173 sockListen, 174 sockClient, 175 szBuff, 176 0, 177 sizeof(sockaddr) + 16, 178 sizeof(sockaddr) + 16, 179 &dwRecved, 180 pOv 181 ); 182 } 183 184 void InitWs2() 185 { 186 WORD wVersionRequested; 187 WSADATA wsaData; 188 int err; 189 190 wVersionRequested = MAKEWORD(2, 2); 191 err = WSAStartup(wVersionRequested, &wsaData); 192 if (err != 0) { 193 return; 194 } 195 196 if (LOBYTE(wsaData.wVersion) != 2 || 197 HIBYTE(wsaData.wVersion) != 2) { 198 WSACleanup(); 199 return; 200 } 201 } 202 203 void UninitWs32() 204 { 205 WSACleanup(); 206 }
客戶端:
#include <iostream> using namespace std; #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <Winsock2.h> #pragma comment(lib, "Ws2_32.lib") void InitWs2(); void UninitWs32(); int main() { InitWs2(); SOCKET sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sockClient == SOCKET_ERROR) { printf("socket 創建失敗\r\n"); return 0; } else { printf("socket 創建成功\r\n"); } sockaddr_in si; si.sin_family = AF_INET; si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); si.sin_port = htons(9527); int nRet = connect(sockClient, (sockaddr*)&si, sizeof(si)); if (nRet == SOCKET_ERROR) { printf("連接服務器失敗 \r\n"); return 0; } else { printf("連接服務器成功 \r\n"); } while (true) { char szBuff[MAXBYTE] = { 0 }; std::cin >> szBuff; nRet = send(sockClient, szBuff, sizeof(szBuff), 0); if (nRet == SOCKET_ERROR) { printf("發送失敗\r\n"); } else { printf("發送成功 \r\n"); } } } void InitWs2() { WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD(2, 2); err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { return; } if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { WSACleanup(); return; } } void UninitWs32() { WSACleanup(); }
測試效果:

