电竞比分网-中国电竞赛事及体育赛事平台

分享

基于完成端口的Winsock程序設(shè)計

 shaolong007 2009-08-03

 

關(guān)于完成端口的概念及內(nèi)部機制,參考譯文《深度探索I/O完成端口》。

完成端口對象取代了WSAAsyncSelect中的消息驅(qū)動和WSAEventSelect中的事件對象,當然完成端口模型的內(nèi)部機制要比WSAAsyncSelectWSAEventSelect模型復雜得多。

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è)置(WSAOVERLAPPED結(jié)構(gòu)中的hEvent字段,使一個事件對象句柄同一個文件/套接字關(guān)聯(lián)起來。 

將套接字句柄與一個完成端口關(guān)聯(lián)在一起后,便可以套接字句柄為基礎(chǔ),投遞發(fā)送與接收請求,開始對I/O請求的處理。接下來,可開始依賴完成端口,來接收有關(guān)I/O操作完成情況的通知。從本質(zhì)上說,完成端口模型利用了Win32重疊I/O機制。在這種機制中,像WSASendWSARecv這樣的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ù)六,實際上完成了每個AcceptSocketPerIoData的捆綁。

由于調(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 CreateIoCompletionPortGetQueuedCompletionStatus;當然,不要忘記重疊請求的投遞者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

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多