日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

WinSock完成端口I/O模型

發布時間:2024/4/11 编程问答 50 豆豆
生活随笔 收集整理的這篇文章主要介紹了 WinSock完成端口I/O模型 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

關于重疊I/O,參考《WinSock重疊I/O模型》;關于完成端口的概念及內部機制,參考譯文《深度探索I/O完成端口》。

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

IOCP內部機制如下圖所示:

???在WinSock中編寫完成端口程序,首先要調用CreateIoCompletionPort函數創建完成端口。其原型如下:

WINBASEAPI?HANDLE?WINAPI

CreateIoCompletionPort(

???????HANDLE?FileHandle,

???????HANDLE?ExistingCompletionPort,

???????DWORD?CompletionKey,

???????DWORD?NumberOfConcurrentThreads?);

第一次調用此函數創建一個完成端口時,通常只關注NumberOfConcurrentThreads,它定義了在完成端口上同時允許執行的線程數量。一般設為0,表示系統內安裝了多少個處理器,便允許同時運行多少個線程為完成端口提供服務。每個處理器各自負責一個線程的運行,避免了過于頻繁的線程上下文切換。

hCompletionPort?=?CreateIoCompletionPort(INVALID_HANDLE_VALUE,?NULL, 0, 0)

這個類比重疊I/O事件通知模型中(WSA)CreateEvent

然后再調用GetSystemInfo(&SystemInfo);取得系統安裝的處理器的個數SystemInfo.dwNumberOfProcessors,根據CPU數創建線程池,在完成端口上,為已完成的I/O請求提供服務。一般線程池的規模,即線程數?= CPU數*2+2。

下面的代碼片段演示了線程池的創建。

//?創建線程池,規模為CPU數的兩倍

??for(int?i?= 0;?i?<?SystemInfo.dwNumberOfProcessors?* 2;?i++)

???{

??????HANDLE?ThreadHandle;

??????//?創建一個工作線程,并將完成端口作為參數傳遞給它。

??????if ((ThreadHandle?=?CreateThread(NULL, 0,?WorkerThread,?hCompletionPort,

?????????0, &ThreadID)) ==?NULL)

??????{

?????????printf("CreateThread() failed with error %d/n",?GetLastError());

?????????return;

??????}

??????//?關閉線程句柄

??????CloseHandle(ThreadHandle);

?? }

然后需要將一個句柄與已經創建的完成端口關聯起來,這里主要指套接字AcceptSocket,以后針對這個套接字的I/O完成狀態交由完成端口通知,程序接到完成通知后做善后處理。

這需要再次調用CreateIoCompletionPort函數(囧)。參數四NumberOfConcurrentThreads依舊填0,參數一一般就是AcceptSocket,參數二為上面創建的完成端口hCompletionPort。參數三即“完成鍵”,一般存放套接字句柄的背景信息,也就是所謂的“單句柄數據”。之所以把它叫作“單句柄數據”,因為它是用來保存參數一套接字句柄的關聯信息。一般可簡單定義如下:

typedef struct {

????????SOCKET?Socket;

}?PER_HANDLE_DATA, *?LPPER_HANDLE_DATA;

下面的代碼片段演示了每次Accept返回時,調用CreateIoCompletionPort使返回的AcceptSocket與完成端口關聯,并傳遞一個PerHandleData。

AcceptSocket?=?WSAAccept(Listen,?NULL,?NULL,?NULL, 0);

PerHandleData->Socket?=?AcceptSocket;

CreateIoCompletionPort((HANDLE)?AcceptSocket,?hCompletionPort, (DWORD)PerHandleData, 0)

這個類比重疊I/O事件通知模型中設置(WSAOVERLAPPED結構中的hEvent字段,使一個事件對象句柄同一個文件/套接字關聯起來。

將套接字句柄與一個完成端口關聯在一起后,便可以套接字句柄為基礎,投遞發送與接收請求,開始對I/O請求的處理。接下來,可開始依賴完成端口,來接收有關I/O操作完成情況的通知。從本質上說,完成端口模型利用了Win32重疊I/O機制。在這種機制中,像WSARecv()和WSASend()這樣的WinSock API調用會立即返回。此時,需要由我們的應用程序負責在以后的某個時間,通過一個OVERLAPPED結構,來接收調用的結果。在完成端口模型中,要想做到這一點,工作者線程WorkerThread需要調用GetQueuedCompletionStatus函數,在完成端口上等待。

GetQueuedCompletionStatus函數原型如下:

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. TheGetQueuedCompletionStatus?function retrieves these queued completion packets. —MSDN

這個類比重疊I/O事件通知模型中的WSAWaitForMultipleEvents/WSAGetOverlappedResult獲得I/O操作結果。

參數一為創建線程池時傳遞的參數hCompletionPort,參數二提供一個DWORD指針,用來接收當I/O完成時實際傳輸的字節數。參數三即第二次調用CreateIoCompletionPort時傳入的單句柄完成鍵,這里用于確定與CompletionPort綁定的具體哪個(套接字)句柄完成了I/O操作導致該函數返回。參數四即第二次調用CreateIoCompletionPort時傳入的(套接字)句柄(AcceptSocket)投遞重疊I/O請求(WSARecv/WSASend)時指定的(WSA)OVERLAPPED結構。實際操作中往往提供一個(WSA)OVERLAPPED擴展結構,這就是常說的“單I/O數據”。一種定義如下:

typedef struct{

???OVERLAPPED?Overlapped;

???WSABUF?DataBuf;

???CHAR?Buffer[DATA_BUFSIZE];

???DWORD?BytesSEND;

???DWORD?BytesRECV;

}OVERLAPPEDPLUS,PER_IO_OPERATION_DATA,*LPPER_IO_OPERATION_DATA;

這里的最后兩個參數BytesSEND和BytesRECV與GetQueuedCompletionStatus函數返回時的ByteTransfered參數一起同步收發操作。

一般在調用CreateIoCompletionPort將套接字句柄與完成端口hCompletionPort關聯后,還需要為AcceptSocket創建PerIOData,以便為后面調用WSARecv()/WSASend()提供(WSA)OVERLAPPED結構和緩沖區。為確保單I/O數據的生存周期延續到I/O完成,一般動態分配,待I/O完成再回收。對于I/O頻繁的系統,則可以使用內存池,每次只是回收到空閑池,最后再真正釋放。這樣,可避免頻繁的內存分配。可參考《MFC基于CPlex結構的內存池化管理》。

下面的是Accept返回,調用CreateIoCompletionPort之后的代碼片段。

ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));

????PerIoData->BytesSEND?= 0;

????PerIoData->BytesRECV?= 0;

????PerIoData->DataBuf.len?=?DATA_BUFSIZE;

PerIoData->DataBuf.buf?=?PerIoData->Buffer;

然后調用WSARecv,投遞一個等待接收數據的I/O請求。

WSARecv(AcceptSocket, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags,??&(PerIoData->Overlapped),?NULL)

注意參數一、參數二和參數六,實際上完成了每個AcceptSocket與PerIoData的捆綁。

由于調用CreateIoCompletionPort將套接字句柄與完成端口hCompletionPort關聯起來了,所以針對AcceptSocket這個套接字句柄上的I/O請求(WSARecv)完成時,一個完成通知包將被投遞到完成端口hCompletionPort消息隊列中。GetQueuedCompletionStatus函數是用來獲取排隊完成狀態,它使調用線程掛起,直到收到一個完成通知包才返回。

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, andlpOverlapped?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?andlpCompletionKey?parameters. —MSDN

在工作者線程WorkerThread中調用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;

?}

//?根據lpNumberOfBytesTransferred, lpCompletionKey, and lpOverlapped參數進行處理

// ……

}

GetQueuedCompletionStatus傳遞的參數三將PerIOData強制轉換為(LPOVERLAPPED *)?結構,后面又要配合使用PerIOData的其他字段,這體現了“擴展”二字的用意。

GetQueuedCompletionStatus返回FALSE,可以調用(WSA)GetOverlappedResult/(WSA)GetLastError獲知具體錯誤。

如前面所言,完成端口模型利用了Win32重疊I/O機制,它是在利用完成端口隊列對象來管理線程池。下面總結一下編寫基于完成端口的Winsock服務器程序的要點。

(1)首先,當然要調用CreateIoCompletionPort創建一個完成端口,一般一個應用程序只創建一個完成端口。

(2)然后,創建一個線程池,把完成端口作為參數傳給線程參數,以使工作線程調用GetQueuedCompletionStatus在完成端口上等待I/O完成,收到完成通知后提供I/O數據處理服務。

(3)每當Accept(Ex)成功返回后,調用CreateIoCompletionPort將AcceptSocket與完成端口關聯起來,并傳遞AcceptSocket的上下文信息(即“單句柄數據”)給完成鍵參數。同時為AcceptSocket創建一個I/O緩沖區(即“單I/O數據”,擴展OVERLAPPED結構)。

(4)接著,AcceptSocket調用異步I/O操作函數,如WSARecvWSASend,拋出重疊的I/O請求。這時需要將單I/O數據的第一個字段—OVERLAPPED結構—傳遞給WSARecvWSASend,以表示它們投遞的是“重疊”的I/O請求,需要等待系統的I/O完成通知。

(5)至此,當上一步拋出的重疊I/O操作完成時,完成端口上會有一個完成通知包,工作線程收到完成通知,從GetQueuedCompletionStatus返回。通過完成鍵即單句柄數據提供的客戶套接字上下文信息、重疊結構參數以及實際I/O的字節數,就可以正式提供I/O數據服務了。

簡言之,涉及兩個重要的數據結構:“單句柄數據”和“單I/O數據”(擴展的OVERLAPPED結構);涉及兩個重要的API:?CreateIoCompletionPortGetQueuedCompletionStatus;當然,不要忘記重疊請求的投遞者WSARecvWSASend,它們是導火索—通信程序的本質工作就是“通信”。

因為完成端口模型本質上利用了Win32重疊I/O機制,故(擴展的)OVERLAPPED結構提供的溝通機制依然是數據通信重要的線索。另外,要理解完成端口內部機制和工作原理及其在通信中的作用。

?

下面補充完成端口的項目應用實例。

Windows下的IIS采用了完成端口模型,參考《完成端口與高性能服務器程序開發》、《I/O完成端口(Windows核心編程)》、《A simple application using I/O Completion Ports and WinSock》。

Apache Httpd/httpd/server/mpm/winnt/child.c中的winnt_accept()(AcceptEx)和winnt_get_connection()使用了完成端口ThreadDispatchIOCP,但并沒有關聯套接字,而是自己構造完成包(mpm_post_completion_contextPostQueuedCompletionStatus),完成鍵為枚舉io_state_e,單句柄為PCOMP_CONTEXT

// Apache Httpd/httpd/server/mpm/winnt/mpm_winnt.h

typedef?enum?{

????IOCP_CONNECTION_ACCEPTED?= 1,

????IOCP_WAIT_FOR_RECEIVE?= 2,

????IOCP_WAIT_FOR_TRANSMITFILE?= 3,

????IOCP_SHUTDOWN?= 4

}?io_state_e;

Apache源碼中只使用到IOCP_CONNECTION_ACCEPTEDIOCP_SHUTDOWN兩種狀態。除此之外,Apache里面沒有真正的完成端口成分,ntmpm似乎依然是線程池加進程池來處理。具體I/O過程參考Apache源碼Apache Httpd/httpd-2.2.15/srclib/apr/file_io/win32/readwrite.c和Apache Httpd/httpd/srclib/apr/network_io/win32/sendrecv.c。

Nginx是由Igor Sysoev為俄羅斯訪問量第二的Rambler.ru站點開發的,其特點是占有內存少,并發能力強,Nginx的并發能力確實在同類型的網頁伺服器中表現較好。新浪、網易、騰訊、迅雷、CSDN等大型網站都采用了Nginx。Nginx每一個客戶端請求進來以后會通過事件處理機制,在Linux是Epoll,在FreeBSD下是KQueue放到空閑的連接里。相關源碼參考nginx/src/event/modules下的ngx_epoll_module.c、ngx_kqueue_module.h(c)。參考《基于IO完成端口與WSAEventSelect的nginx事件處理模塊:ngx_iocp_module》、《Windows下完成端口移植Linux下的epoll》。

?

參考:

《Network Programming for Microsoft Windows》??Anthony Jones,Jim Ohlund

《Windows Internals》??Mark E.?Russinovich,David A.?Solomon

《Windows 2000 Systems Programming Black Book》??Al Williams

《Multithreading Applications in?Win32》??Jim Beveridge,Robert Wiener.

《Windows網絡與通信程序設計》??王艷平

?

IOCP完成端口原理

Write Scalable Winsock Apps Using Completion Ports

Design Issues When Using IOCP in a Winsock Server

總結

以上是生活随笔為你收集整理的WinSock完成端口I/O模型的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。