《Windows網路與通訊程式設計》讀書筆記----重疊(Overlapped)I/O模型

來源:互聯網
上載者:User

重疊(Overlapped) I/O模型

與介紹過的其他模型相比,重疊I/O模型提供了更好的系統效能。這個模型的基本設計思想是允許應用程式使用重疊資料結構一次投遞一個或者多個非同步I/O請求(即所謂的重疊I/O)。提交的I/O請求完成之後,與之關聯的重疊資料中事件對象觸發,應用程式便可使用WSAGetOverlappedResult函數擷取重疊操作的結果。這和使用重疊結構調用ReadFile和WriteFile函數操作檔案類似。

 

具體編程流程

1、建立一個事件控制代碼表和緩衝區對象表

2、每建立一個通訊端,就建立一個緩衝區對象和一個事件對象,將事件對象與緩衝區對象中ol.hEvent成員相關聯,並將通訊端和事件對象的控制代碼分別放入上面的兩個表中。

3、建立一個監聽通訊端和監聽事件對象,先投遞幾個非同步Accept的請求。(引子,必須先投遞幾個,否則不能引發後續的非同步I/O請求)

4、調用WSAWaitForMultipleEvents在所有事件對象上等待,此函數返回後,我們對事件控制代碼表中每個事件調用WSAWaitForMultipleEvents函數和HandleIO函數,以便確認在哪些通訊端上發生了網路事件。

5、在HandleIO函數中處理髮生的網路事件,並繼續投遞非同步I/O請求。(繼續投遞非同步I/O請求作為引子)

 

書上原始碼的設計思路

先聲明一個通訊端對象。

//通訊端對象typedef      struct_SOCKET_OBJ{       SOCKET s ;                                    //通訊端控制代碼       intnOutstandingOps ;                     //記錄此通訊端上的重疊I/O數量,要等待這個成員為0,說明通訊端上面的I/O操作全部完成才可以釋放通訊端對象       LPFN_ACCEPTEXlpfnAcceptEx ;   //擴充函數AcceptEx的指標(僅對監聽通訊端而言)} SOCKET_OBJ , *PSOCKET_OBJ ;

 

然後聲明一個緩衝區對象

//緩衝區對象typedef struct _BUFFER_OBJ{       OVERLAPPEDol ;                               //重疊結構       char *buff;                                  //send/recv/AcceptEx所使用的緩衝區       int nLen ;                                       //buff的長度       PSOCKET_OBJpSocket ;                  //此I/O所屬的通訊端對象       intnOperation ;                          //提交的操作類型 #define OP_ACCEPT    1                          //操作類型#define       OP_READ         2#define       OP_WRITE       3       SOCKETsAccept ;                       //用來儲存AccetpEx接受的用戶端通訊端(僅對監聽通訊端而言)       _BUFFER_OBJ*pNext ;} BUFFER_OBJ, *PBUFFER_OBJ ;

通訊端對象與緩衝區對象的關係:


 

具體流程:

程式只有單線程。

線程一開始先建立一個監聽通訊端和監聽事件對象、監聽事件緩衝區,用於先行投遞幾個非同步Accept I/O請求作為引發後續操作的引子。

然後調用WSAWaitForMultipleEvents函數等待相應的事件觸發。

事件觸發後,調用HandleIO函數處理相應的網路事件,並再次投遞相應的非同步I/O請求作為引子。

 需要注意的是,程式中所有非同步Accept I/O請求中的s成員均共用程式一開始建立監聽通訊端sListen。且如果用戶端通訊端不關閉的情況下,每完成一個非同步Accept I/O請求,就會繼續投遞一個新的非同步Accpet I/O請求和一個非同步Send請求,每完成一個非同步Read I/O請求就會投遞一個新的非同步Write I/O請求,每完成非同步Write
I/O請求就會投遞一個新的Read I/O請求。如果用戶端通訊端關閉,則除了保留程式一開始投遞N個的非同步Accept I/O請求所需的資源外,其餘的通訊端對象和緩衝區對象都會被釋放,程式回到初始化狀態。



書上原始碼

#define _WIN32_WINNT 0x0400   #include<windows.h>#include<cstdio>#include"InitSocket.h"#define BUFFER_SIZE 2048CInitSock InitSock ; //進入main函數前已經進行了初始化//通訊端對象typedefstruct _SOCKET_OBJ{SOCKET s ;//通訊端控制代碼int nOutstandingOps ;//記錄此通訊端上的重疊I/O數量LPFN_ACCEPTEX lpfnAcceptEx ;//擴充函數AcceptEx的指標(僅對監聽通訊端而言)} SOCKET_OBJ , *PSOCKET_OBJ ;//緩衝區對象//ol成員變數必須處於第一個位置typedef struct _BUFFER_OBJ{OVERLAPPED ol ;//重疊結構char *buff ;//send/recv/AcceptEx所使用的緩衝區int nLen ;//buff的長度PSOCKET_OBJ pSocket ;//此I/O所屬的通訊端對象int nOperation ;//提交的操作類型 #define OP_ACCEPT1//操作類型#defineOP_READ2#defineOP_WRITE3SOCKET sAccept ;//用來儲存AccetpEx接受的用戶端通訊端(僅對監聽通訊端而言)_BUFFER_OBJ *pNext ;} BUFFER_OBJ, *PBUFFER_OBJ ;//申請通訊端對象PSOCKET_OBJ GetSocketObj(SOCKET s) ;//釋放通訊端對象void FreeSocketObj(PSOCKET_OBJ pSocket) ;//申請緩衝區對象PBUFFER_OBJ GetBufferObj(PSOCKET_OBJ pSocket ,ULONG nLen)  ;//釋放緩衝區對象void FreeBufferObj(PBUFFER_OBJ pBuffer) ;//尋找已觸發的對象PBUFFER_OBJ FindBufferObj(HANDLE hEvent) ;//因為是數組與鏈表,所以需要重建對齊void RebuildArray() ;//提交接受串連BOOL PostAccept(PBUFFER_OBJ pBuffer)  ;//提交接收BOOL PostRecv(PBUFFER_OBJ pBuffer) ;//投遞發送的請求BOOL PostSend(PBUFFER_OBJ pBuffer) ;//I/O處理函數BOOL HandleIO(PBUFFER_OBJ pBuffer) ;//全域變數HANDLE g_events[WSA_MAXIMUM_WAIT_EVENTS] ;//I/O事件控制代碼數組 int g_nBufferCount ;//上數組中有效控制代碼計數PBUFFER_OBJ g_pBufferHead ,g_pBufferTail ;//記錄緩衝區對象組成的表的地址//主函數int main(void){//建立監聽通訊端,綁定到本地連接埠,進入監聽模式int nPort = 4567 ;SOCKET sListen = WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,NULL,0,WSA_FLAG_OVERLAPPED) ;SOCKADDR_IN si ;si.sin_family = AF_INET ;si.sin_port = ntohs(nPort) ;si.sin_addr.s_addr = INADDR_ANY ;bind(sListen,(sockaddr*)&si,sizeof(si)) ;listen(sListen ,200) ;//為監聽通訊端建立一個SOCKET_OBJ對象PSOCKET_OBJ pListen = GetSocketObj(sListen) ;//載入擴充函數AcceptExGUID GuidAcceptEx = WSAID_ACCEPTEX ;DWORD dwBytes ;WSAIoctl(pListen->s,SIO_GET_EXTENSION_FUNCTION_POINTER,&GuidAcceptEx,sizeof(GuidAcceptEx),&pListen->lpfnAcceptEx,sizeof(pListen->lpfnAcceptEx),&dwBytes,NULL,NULL) ;//建立用來重建立立g_events數組的事件對象g_events[0] = WSACreateEvent() ;//在此可以投遞多個接受I/O請求,程式始終保持5個非同步Accept I/Ofor(int i = 0 ; i< 5 ; ++i){PostAccept(GetBufferObj(pListen,BUFFER_SIZE)) ; //GetBufferObj裡面有增加事件對象的計數}while(TRUE){//int nIndex = WSAWaitForMultipleEvents(g_nBufferCount+1,g_events,FALSE,WSA_INFINITE,FALSE) ;int nIndex = WSAWaitForMultipleEvents(g_nBufferCount+1,g_events,FALSE,5000,FALSE) ; //等待5秒,用於列印串連數 if(nIndex == WSA_WAIT_FAILED){printf("WSAWaitForMultipleEvents() failed\n") ;break ;}else if(nIndex == WSA_WAIT_TIMEOUT)  //逾時,我自己添加的,測試所用{printf("現在串連數: %d\n",g_nBufferCount ) ;continue ;}nIndex = nIndex - WSA_WAIT_EVENT_0 ;for(int i = 0 ; i <= nIndex ; ++i){int nRet = WSAWaitForMultipleEvents(1,&g_events[i],TRUE,0,FALSE) ; //逐個查看觸發的對象if(nRet == WSA_WAIT_TIMEOUT){continue ;}else{WSAResetEvent(g_events[i]) ;//重建立立g_events數組if(0 == i){RebuildArray() ;continue ;}//處理這個I/OPBUFFER_OBJ pBuffer = FindBufferObj(g_events[i]) ;if(pBuffer != NULL){if(!HandleIO(pBuffer)){RebuildArray() ;}}}}}return 0 ;}//申請緩衝區對象PBUFFER_OBJ GetBufferObj(PSOCKET_OBJ pSocket ,ULONG nLen) {if(g_nBufferCount > WSA_MAXIMUM_WAIT_EVENTS-1){return NULL ;}PBUFFER_OBJ pBuffer = (PBUFFER_OBJ)GlobalAlloc(GPTR,sizeof(BUFFER_OBJ))  ;if(pBuffer != NULL){pBuffer->buff = (char*)GlobalAlloc(GPTR,nLen) ;pBuffer->ol.hEvent = WSACreateEvent() ;pBuffer->pSocket = pSocket ;//共用一個監聽通訊端pBuffer->sAccept = INVALID_SOCKET ;//將新的BUFFER_OBJ添加到列表中,單線程無需要同步if(NULL == g_pBufferHead){g_pBufferHead = g_pBufferTail = pBuffer ;}else{g_pBufferTail->pNext = pBuffer ;g_pBufferTail = pBuffer ;}g_events[++g_nBufferCount] = pBuffer->ol.hEvent ; //這裡有增加計數}return pBuffer ;}//釋放緩衝區對象void FreeBufferObj(PBUFFER_OBJ pBuffer){//從列表中移除BUFFER_OBJ對象PBUFFER_OBJ pTest = g_pBufferHead ;BOOL bFind = FALSE ;if(pTest == pBuffer) //釋放的是頭結點{g_pBufferHead = g_pBufferTail = NULL ;bFind = TRUE ;}else{while(pTest != NULL && pTest->pNext != pBuffer){pTest = pTest->pNext ;}if(pTest != NULL) //pTest為被刪結點的前一個結點{pTest->pNext = pBuffer->pNext ;if(NULL == pTest->pNext) //刪除的是尾結點{g_pBufferTail = pTest ;}bFind = TRUE ;}}//釋放它佔用的記憶體空間if(bFind){g_nBufferCount-- ;CloseHandle(pBuffer->ol.hEvent) ;GlobalFree(pBuffer->buff) ;GlobalFree(pBuffer) ;}}//申請通訊端對象PSOCKET_OBJ GetSocketObj(SOCKET s) {PSOCKET_OBJ pSocket = (PSOCKET_OBJ)GlobalAlloc(GPTR,sizeof(SOCKET_OBJ)) ;if(pSocket != NULL){pSocket->s = s ;}return pSocket ;}//釋放通訊端對象void FreeSocketObj(PSOCKET_OBJ pSocket) {if(pSocket->s != INVALID_SOCKET){closesocket(pSocket->s) ;}GlobalFree(pSocket) ;}//尋找已經觸發的緩衝區對象PBUFFER_OBJ FindBufferObj(HANDLE hEvent) {PBUFFER_OBJ pBuffer = g_pBufferHead ;while(pBuffer != NULL){if(pBuffer->ol.hEvent == hEvent){break ;}pBuffer = pBuffer->pNext ;}return pBuffer ;}//因為是數組與鏈表,所以需要重建對齊void RebuildArray(){PBUFFER_OBJ pBuffer = g_pBufferHead ;int i = 1 ;while(pBuffer != NULL){g_events[i++] = pBuffer->ol.hEvent ;pBuffer = pBuffer->pNext ;}}//提交接受串連BOOL PostAccept(PBUFFER_OBJ pBuffer) {PSOCKET_OBJ pSocket = pBuffer->pSocket ;if(pSocket->lpfnAcceptEx != NULL){//設定I/O類型,增加通訊端上的重疊I/O計數pBuffer->nOperation = OP_ACCEPT ;pSocket->nOutstandingOps++ ;//投遞此重疊I/ODWORD dwBytes ;pBuffer->sAccept = WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);BOOL b = pSocket->lpfnAcceptEx(pSocket->s,//已經調用過listen的監聽通訊端pBuffer->sAccept,//新串連將在這個通訊端上面產生,一定為不能用的`pBuffer->buff,//第一塊資料的接收緩衝區,如果為0則,新串連則不等待第一塊資料BUFFER_SIZE-((sizeof(sockaddr_in)+16)*2),//第一塊資料緩衝區的大小,不計入兩個地址的大小。sizeof(sockaddr_in)+16,//本地地址結構的大小,必須比16還要大sizeof(sockaddr_in)+16,//遠端地址結構的大小,必須比16還要大&dwBytes,//實際接收資料的大小,&pBuffer->ol) ;//Overlapped結構if(!b){if(WSAGetLastError() != WSA_IO_PENDING){return FALSE ;}return TRUE ;}}return FALSE ;}//提交接受BOOL PostRecv(PBUFFER_OBJ pBuffer){//設定I/O類型,增加通訊端上的重疊I/O計數pBuffer->nOperation = OP_READ ;pBuffer->pSocket->nOutstandingOps++ ;//投遞此重疊I/ODWORD dwBytes ;DWORD dwFlags  = 0 ;WSABUF buf ;buf.buf = pBuffer->buff ; //方便引用而已buf.len = pBuffer->nLen ;if(WSARecv(pBuffer->pSocket->s,&buf,1,&dwBytes,&dwFlags,&pBuffer->ol,NULL) != NO_ERROR){if(WSAGetLastError() != WSA_IO_PENDING){return FALSE ;}}return TRUE ;}//投遞發送的請求BOOL PostSend(PBUFFER_OBJ pBuffer){//設定I/O類型,增加通訊端上的重疊I/O計數pBuffer->nOperation = OP_WRITE ;pBuffer->pSocket->nOutstandingOps++ ;//投遞此重疊I/ODWORD dwBytes ;DWORD dwFlags = 0 ;WSABUF buf ;buf.buf = pBuffer->buff ;buf.len = pBuffer->nLen ;if(WSASend(pBuffer->pSocket->s,&buf,1,&dwBytes,dwFlags,&pBuffer->ol,NULL) != NO_ERROR){if(WSAGetLastError() != WSA_IO_PENDING){return FALSE ;}}return TRUE ;} //I/O處理函數BOOL HandleIO(PBUFFER_OBJ pBuffer){PSOCKET_OBJ pSocket = pBuffer->pSocket ;//從BUFFER_OBJ對象中提取SOCKET_OBJ對象指標,為的是方便引用 ;pSocket->nOutstandingOps-- ;//擷取重疊操作結果DWORD dwTrans ;DWORD dwFlags ;BOOL bRet = WSAGetOverlappedResult(pSocket->s,&pBuffer->ol,&dwTrans,FALSE,&dwFlags) ; if(!bRet){//在此通訊端上有錯誤發生,因此,關閉通訊端,移除此緩衝區對象。//如果沒有其它拋出的I/O請求了,釋放此緩衝區對象,否則,等待此通訊端上的其它I/O也完成if(pSocket->s != INVALID_SOCKET){closesocket(pSocket->s) ;pSocket->s = INVALID_SOCKET ;}if(0 == pSocket->nOutstandingOps){FreeSocketObj(pSocket) ;}FreeBufferObj(pBuffer) ;return FALSE ;}//沒有錯誤發生,處理已完成的I/Oswitch(pBuffer->nOperation){case OP_ACCEPT ://接收到一個新的串連,並接收到了對方發來的第一個封包{//為新客戶建立一個SOCKET_OBJ對象PSOCKET_OBJ pClient = GetSocketObj(pBuffer->sAccept) ;//為發送資料建立一個BUFFER_OBJ對象,這個對象在通訊端出錯或者關閉時釋放PBUFFER_OBJ pSend = GetBufferObj(pClient,BUFFER_SIZE) ;if(NULL == pSend){printf("Too much connections!\n") ;FreeSocketObj(pClient) ;return FALSE ;}RebuildArray() ;//將資料複製到發送緩衝區pSend->nLen = dwTrans ;memcpy(pSend->buff,pBuffer->buff,dwTrans) ;//列印接收到的資料,自己添加的printf("接收到的資料:%s\n",pSend->buff) ;//投遞此發送I/O(將資料回顯給客戶)if(!PostSend(pSend)){//萬一出錯的話,釋放上面剛申請的兩個對象FreeSocketObj(pSocket) ;FreeBufferObj(pSend) ;return FALSE ;}//繼續投遞接受I/OPostAccept(pBuffer);}break ;case OP_READ : // 接收資料完成{if(dwTrans > 0){//建立一個緩衝區,以發送資料。這裡就使用原來的緩衝區PBUFFER_OBJ pSend = pBuffer ;pSend->nLen = dwTrans ;//列印接收到的資料,自己添加的printf("接收到的資料:%s\n",pSend->buff) ;//投遞發送I/O(將資料回顯給客戶)PostSend(pSend) ;}else //通訊端關閉{//關閉一個串連printf("關閉一個串連\n") ;//必須先關閉通訊端,以便在此通訊端上投遞的其它I/O也返回if(pSocket->s != INVALID_SOCKET){closesocket(pSocket->s) ;pSocket->s = INVALID_SOCKET ;}if(pSocket->nOutstandingOps == 0){FreeSocketObj(pSocket) ;}FreeBufferObj(pBuffer) ;return FALSE ;}}break ;case OP_WRITE : //發送資料完成{if(dwTrans > 0){//列印發送資料訊息printf("發送資料完成\n") ;//繼續使用這個緩衝區投遞接收資料的請求pBuffer->nLen = BUFFER_SIZE ;PostRecv(pBuffer) ;}else //通訊端關閉{//同樣,要先關閉通訊端if(pSocket->s != INVALID_SOCKET){closesocket(pSocket->s ) ;pSocket->s = INVALID_SOCKET ;}if(pSocket->nOutstandingOps == 0){FreeSocketObj(pSocket) ;}FreeBufferObj(pBuffer) ;return FALSE ;}}break ;}return TRUE ;}


總結:

基於重疊(Overlapped)I/O模型的伺服器,雖然能夠一次投遞多個重疊的I/O請求,但是程式的伸縮性感覺和WSAEventSelect等模型差不多。因為當發出非同步I/O請求之後,程式依然需要在某個時間點上面調用WSAWaitForMultipleEvents來等待非同步I/O的完成,這裡又會出現了WSAEventSelect模型上的問題。






相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.