|
關(guān)于完成端口的概念及內(nèi)部機制,參考譯文《深度探索I/O完成端口》。 完成端口對象取代了WSAAsyncSelect中的消息驅(qū)動和WSAEventSelect中的事件對象,當然完成端口模型的內(nèi)部機制要比WSAAsyncSelect和WSAEventSelect模型復雜得多。 IOCP內(nèi)部機制如下圖所示:
在Winsock中編寫完成端口程序,首先要調(diào)用CreateIoCompletionPort函數(shù)創(chuàng)建完成端口。其原型如下: WINBASEAPI HANDLE WINAPI CreateIoCompletionPort( HANDLE FileHandle, HANDLE ExistingCompletionPort, DWORD CompletionKey, DWORD NumberOfConcurrentThreads ); 第一次調(diào)用此函數(shù)創(chuàng)建一個完成端口時,通常只關(guān)注NumberOfConcurrentThreads,它定義了在完成端口上同時允許執(zhí)行的線程數(shù)量。一般設(shè)為0,表示系統(tǒng)內(nèi)安裝了多少個處理器,便允許同時運行多少個線程為完成端口提供服務。每個處理器各自負責一個線程的運行,避免了過于頻繁的線程上下文切換。 hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)) 這個類比重疊I/O事件通知模型中(WSA)CreateEvent。 然后再調(diào)用GetSystemInfo(&SystemInfo);取得系統(tǒng)安裝的處理器的個數(shù)SystemInfo.dwNumberOfProcessors,根據(jù)CPU數(shù)創(chuàng)建線程池,在完成端口上,為已完成的I/O請求提供服務。一般線程池的規(guī)模,即線程數(shù) = CPU數(shù) * 2 + 2。 下面的代碼片段演示了線程池的創(chuàng)建。 // 創(chuàng)建線程池,規(guī)模為CPU數(shù)的兩倍 for(int i = 0; i < SystemInfo.dwNumberOfProcessors * 2; i++) { HANDLE ThreadHandle; // 創(chuàng)建一個工作線程,并將完成端口作為參數(shù)傳遞給它。 if ((ThreadHandle = CreateThread(NULL, 0, WorkerThread, hCompletionPort, 0, &ThreadID)) == NULL) { printf("CreateThread() failed with error %d\n", GetLastError()); return; } // 關(guān)閉線程句柄 CloseHandle(ThreadHandle); } 然后需要將一個句柄與已經(jīng)創(chuàng)建的完成端口關(guān)聯(lián)起來,這里主要指套接字AcceptSocket,以后針對這個套接字的I/O操作都交給與之關(guān)聯(lián)的完成端口處理。 這需要再次調(diào)用CreateIoCompletionPort函數(shù)。參數(shù)四NumberOfConcurrentThreads依舊填0,參數(shù)一一般就是AcceptSocket,參數(shù)二為上面創(chuàng)建的完成端口hCompletionPort。參數(shù)三即“完成鍵”,一般存放套接字句柄的背景信息,也就是所謂的“單句柄數(shù)據(jù)”。之所以把它叫作“單句柄數(shù)據(jù)”,因為它是用來保存參數(shù)一套接字句柄的關(guān)聯(lián)信息。一般可簡單定義如下: typedef struct { SOCKET Socket; } PER_HANDLE_DATA, * LPPER_HANDLE_DATA; 下面的代碼片段演示了每次Accept返回時,調(diào)用CreateIoCompletionPort使返回的AcceptSocket與完成端口關(guān)聯(lián),并傳遞一個PerHandleData。 AcceptSocket = WSAAccept(Listen, NULL, NULL, NULL, 0); PerHandleData->Socket = AcceptSocket; CreateIoCompletionPort((HANDLE) AcceptSocket, hCompletionPort, (DWORD) PerHandleData, 0) 這個類比重疊I/O事件通知模型中設(shè)置(WSA)OVERLAPPED結(jié)構(gòu)中的hEvent字段,使一個事件對象句柄同一個文件/套接字關(guān)聯(lián)起來。 將套接字句柄與一個完成端口關(guān)聯(lián)在一起后,便可以套接字句柄為基礎(chǔ),投遞發(fā)送與接收請求,開始對I/O請求的處理。接下來,可開始依賴完成端口,來接收有關(guān)I/O操作完成情況的通知。從本質(zhì)上說,完成端口模型利用了Win32重疊I/O機制。在這種機制中,像WSASend和WSARecv這樣的Winsock API調(diào)用會立即返回。此時,需要由我們的應用程序負責在以后的某個時間,通過一個OVERLAPPED結(jié)構(gòu),來接收調(diào)用的結(jié)果。在完成端口模型中,要想做到這一點,工作者線程WorkerThread需要調(diào)用GetQueuedCompletionStatus函數(shù),在完成端口上等待。 GetQueuedCompletionStatus函數(shù)原型如下: WINBASEAPI BOOL WINAPI GetQueuedCompletionStatus( HANDLE CompletionPort, LPDWORD lpNumberOfBytesTransferred, LPDWORD lpCompletionKey, LPOVERLAPPED *lpOverlapped, DWORD dwMilliseconds ); When
you perform an input/output operation with a file handle that has an
associated input/output completion port, the I/O system sends a completion notification packet
to the completion port when the I/O operation completes. The completion
port places the completion packet in a first-in-first-out queue. The GetQueuedCompletionStatus function retrieves these queued completion packets. —MSDN 這個類比重疊I/O事件通知模型中的WSAWaitForMultipleEvents/WSAGetOverlappedResult 獲得I/O操作結(jié)果。 參數(shù)一為創(chuàng)建線程池時傳遞的參數(shù)hCompletionPort,參數(shù)二提供一個DWORD指針,用來接收當I/O完成時實際傳輸?shù)淖止?jié)數(shù)。參數(shù)三即上一步所說的單句柄完成鍵。參數(shù)四即為套接字AcceptSocket分配的(WSA)OVERLAPPED結(jié)構(gòu),實際操作中往往提供一個(WSA)OVERLAPPED擴展結(jié)構(gòu),這就是常說的“單I/O數(shù)據(jù)”。一種定義如下: typedef struct{ OVERLAPPED Overlapped; WSABUF DataBuf; CHAR Buffer[DATA_BUFSIZE]; DWORD BytesSEND; DWORD BytesRECV; } PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA; 這里的最后兩個參數(shù)BytesSEND和BytesRECV與GetQueuedCompletionStatus函數(shù)返回時的ByteTransfered參數(shù)一起可用來同步接發(fā)操作。 一般在調(diào)用CreateIoCompletionPort將套接字句柄與完成端口hCompletionPort關(guān)聯(lián)后,還需要為AcceptSocket創(chuàng)建PerIOData,以便為后面調(diào)用WSARecv提供(WSA)OVERLAPPED結(jié)構(gòu)和緩沖區(qū)。 下面的是Accept返回,調(diào)用CreateIoCompletionPort之后的代碼片段。 ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED)); PerIoData->BytesSEND = 0; PerIoData->BytesRECV = 0; PerIoData->DataBuf.len = DATA_BUFSIZE; PerIoData->DataBuf.buf = PerIoData->Buffer; 然后調(diào)用WSARecv,投遞一個等待接收數(shù)據(jù)的I/O請求。 WSARecv(AcceptSocket, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags, &(PerIoData->Overlapped), NULL) 注意參數(shù)一、參數(shù)二和參數(shù)六,實際上完成了每個AcceptSocket與PerIoData的捆綁。 由于調(diào)用CreateIoCompletionPort將套接字句柄與完成端口hCompletionPort關(guān)聯(lián)起來了,所以針對AcceptSocket這個套接字句柄上的I/O請求(WSARecv)完成時,一個完成通知包將被投遞到完成端口hCompletionPort消息隊列中。GetQueuedCompletionStatus函數(shù)是用來獲取排隊完成狀態(tài),它使調(diào)用線程掛起,直到收到一個完成通知包才返回。 If
the function dequeues a completion packet for a successful I/O
operation from the completion port, the return value is nonzero. The
function stores information in the variables pointed to by the lpNumberOfBytesTransferred, lpCompletionKey, and lpOverlapped parameters. If *lpOverlapped
is NULL and the function does not dequeue a completion packet from the
completion port, the return value is zero. The function does not store
information in the variables pointed to by the lpNumberOfBytesTransferred and lpCompletionKey parameters. —MSDN 在工作者線程WorkerThread中調(diào)用GetQueuedCompletionStatus: while(TRUE) { GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (LPDWORD)&PerHandleData, (LPOVERLAPPED *) &PerIoData, INFINITE) if (BytesTransferred == 0) // 出錯 { printf("Closing socket %d\n", PerHandleData->Socket); if (closesocket(PerHandleData->Socket) == SOCKET_ERROR) { printf("closesocket() failed with error %d\n", WSAGetLastError()); return 0; } GlobalFree(PerHandleData); GlobalFree(PerIoData); continue; } // 根據(jù)lpNumberOfBytesTransferred, lpCompletionKey, and lpOverlapped參數(shù)進行處理 // …… } 給GetQueuedCompletionStatus傳遞的參數(shù)三將PerIOData強制轉(zhuǎn)換為(LPOVERLAPPED *) 結(jié)構(gòu),后面又要引用PerIOData的其他字段,這體現(xiàn)了“擴展”二字的含義。 如前面所言,完成端口模型利用了Win32重疊I/O機制,它是在利用完成端口隊列對象來管理線程池。下面總結(jié)一下編寫基于完成端口的Winsock服務器程序的要點。 (1)首先,當然要調(diào)用CreateIoCompletionPort創(chuàng)建一個完成端口,一般一個應用程序只創(chuàng)建一個完成端口。 (2)然后,創(chuàng)建一個線程池,把完成端口作為參數(shù)傳給線程參數(shù),以使工作線程調(diào)用GetQueuedCompletionStatus在完成端口上等待I/O完成,收到完成通知后提供I/O數(shù)據(jù)處理服務。 (3) 每當Accept(Ex)成功返回后,調(diào)用CreateIoCompletionPort將AcceptSocket與完成端口關(guān)聯(lián)起來,并傳遞 AcceptSocket的上下文信息(即“單句柄數(shù)據(jù)”)給完成鍵參數(shù)。同時為AcceptSocket創(chuàng)建一個I/O緩沖區(qū)(即“單I/O數(shù)據(jù)”,擴展OVERLAPPED結(jié)構(gòu))。 (4)接著,AcceptSocket調(diào)用異步I/O操作函數(shù),如WSARecv和WSASend,拋出重疊的I/O請求。這時需要將單I/O數(shù)據(jù)的第一個字段—OVERLAPPED結(jié)構(gòu)—傳遞給WSARecv和WSASend,以表示它們投遞的是“重疊”的I/O請求,需要等待系統(tǒng)的I/O完成通知。 (5) 至此,當上一步拋出的重疊I/O操作完成時,完成端口上會有一個完成通知包,工作線程收到完成通知,從 GetQueuedCompletionStatus返回。通過完成鍵即單句柄數(shù)據(jù)提供的客戶套接字上下文信息、重疊結(jié)構(gòu)參數(shù)以及實際I/O的字節(jié)數(shù),就 可以正式提供I/O數(shù)據(jù)服務了。 簡言之,涉及兩個重要的數(shù)據(jù)結(jié)構(gòu):“單句柄數(shù)據(jù)”和“單I/O數(shù)據(jù)”(擴展的OVERLAPPED結(jié)構(gòu));涉及兩個重要的API: CreateIoCompletionPort和GetQueuedCompletionStatus;當然,不要忘記重疊請求的投遞者WSARecv和WSASend,它們是導火索—通信程序的本質(zhì)工作就是“通信”。 因為完成端口模型本質(zhì)上利用了Win32重疊I/O機制,故(擴展的)OVERLAPPED結(jié)構(gòu)提供的溝通機制依然是數(shù)據(jù)通信重要的線索。另外,要理解完成端口內(nèi)部機制和工作原理及其在通信中的作用。 參考: 《Network Programming for Microsoft Windows》 Anthony Jones,Jim Ohlund 《Write Scalable Winsock Apps Using Completion Ports》 http://msdn.microsoft.com/en-us/magazine/cc302334.aspx 《A simple application using I/O Completion Ports and WinSock》 http://www./KB/IP/SimpleIOCPApp.aspx 《Design Issues When Using IOCP in a Winsock Server》 http://support.microsoft.com/kb/192800/en-us 《IOCP本質(zhì)論》http:///post/The-Essence-of-IOCP.php |
|
|
來自: shaolong007 > 《我的圖書館》