1.適用於除Windows CE之外的各種Windows平台.在使用這個模型之前應該確保該系統安裝了Winsock2.重疊模型的基本設計原理是使用一個重疊的數據結構,一次投遞一個或多個Winsock I/O請求。在重疊模型中,收發數據使用WSA開頭的函數。
2.WSA_FLAG_OVERLAPPED標志:要使用重疊模型。在創建套接字的時候,必須加上該標志。
SOCKET s=WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
假如使用的是socket函數,而非WSASocket函數,那么會默認設置WSA_FLAG_OVERLAPPED標志。
若隨一個WSAFLAGOVERLAPPED結構一起調用這些以WSA開頭的函數(AcceptEx和TRansmiteFile函數例外),函數會立即完成並返回,無論套接字是否設為阻塞模式
3.重疊模型在網絡事件完成后,可以有兩種方式通知應用程序:事件通知和完成例程
3.事件通知:事件對象與WSAOVERLAPPED進行綁定實現網絡事件完成后通過事件進行通知。
4.WSAOVERLAPPED結構:
typedef struct WSAOverlapped
{
DWORD Internal;
DOWRD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
WSAEVENT hEvent;
}WSAOVERLAPPED,FAR* LPWSAOVERLAPPED;
此處,程序員可以使用的是最后一個參數hEvent,其余的不用管。通過該參數,重疊結構可以與事件對象進行綁定,以實現網絡事件完成后,通過事件對象進行通知應用程序。事件對象通知方式是通過創建一個事件對象,把該對象賦值給重疊結構的hEvent參數即可實現綁定。在此再次提醒大家注意:WSAWaitForMultipleEvents函數一次最多只能等待64個事件對象。
5.WSAGetOverlappedResult函數:重疊請求完成后,接着需要調用WSAGetOverlappedResult(取得重疊結構)函數,判斷那個重疊調用到底是成功,還是失敗.
BOOL WSAGetOverlappedResult(
SOCKET s,//套接字
LPWSAOVERLAPPED lpOverlapped,//重疊結構
LPDWORD lpcbTransfer,//對應一個DWORD(雙字節)變量,一次重疊實際傳輸(接收或者發送)的字節數
BOOL fWait,//參數用於決定函數是否應該等待一次重疊操作完成。
LPWORD lpdwFlags
);
重疊操作完成,函數返回TRUE,否則,返回FALSE。返回FALSE通常都是有一下幾種情況造成的.
(1)重疊I/O操作仍處在未完成狀態
(2)重疊操作已經完成,但含有錯誤
(3)重疊操作的完成狀態不可判決,因為在提供給函數WSAGetOverlappedResult的一個或多個參數中,存在着錯誤。
失敗后,由lpcbTransfer參數指向的值不會進行更新,而且我們的應用程序應調用WSAGetLastError函數
6.基於事件通知的重疊模型編程步驟如下:
(1) 創建一個套接字,綁定本機端口,在指定的端口上監聽連接請求。
(2)接受連接請求。
(3)為接受的套接字新建一個WSAOVERLAPPED結構,並為該結構分配一個事件對象句柄。也將事件對象句柄分配給一個事件數組,以便稍后由函數WSAWaitForMultipleEvents使用。
(4)在套接字上投遞一個異步WSARecv請求,指定參數為WSAOVERLAPPED結構。注意函數通常會以失敗告終,返回SOCKETERROR錯誤狀態WSAIOPENDING(I/O操作尚未完成)。
(5)使用步驟3)的事件數組,調用WSAWaitForMultipleEvents函數,並等待與重疊調用關聯在一起的事件進入“已傳信”狀態(換言之,等待那個事件的“觸發”)。
(6)WSAWaitForMultipleEvents函數完成后,針對事件數組,調用WSAResetEvent(重設事件)函數,從而重設事件對象,並對完成的重疊請求進行處理。
(7)使用WSAGetOverlappedResult函數,判斷重疊調用的返回狀態是什么。
(8)在套接字上投遞另一個重疊WSARecv請求。
(9)重復步驟5 ) ~ 8 )。
示例代碼:
1 void main(void) 2 { 3 WSABUF databuf; 4 DWORD eventTotal=0; 5 WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS]; 6 WSAOVERLAPPED acceptOverlapped; 7 SOCKET listensock,acceptsock; 8 9 10 //初始化工作和一般socket通信相同 11 ... 12 13 //接收連接 14 acceptsock=accept(listensock,NULL,NULL); 15 16 //創建事件,綁定事件對象與重疊結構 17 eventArray[eventTotal]=WSACreateEvent(); 18 ZeroMemory(&acceptOverlapped,sizeof(WSAOVERLAPPED)); 19 acceptOverlapped.hEvent=eventArray[eventTotal]; 20 21 //數據緩沖區初始化 22 databuf.len=DATA_BUFSIZE; 23 databuf.buf=buffer; 24 25 eventTotal++; 26 27 //投遞接收請求 28 WSARecv(acceptsock,&databuf,1,&recvBytes,&flags,&acceptOverlapped,NULL); 29 30 while(1) 31 { 32 //監視事件對象狀態 33 index=WSAWaitForMultipleEvents(eventTotal,eventArray,FALSE,WSA_INFINITE,FALSE); 34 35 //人工充值事件 36 WSAResetEvent(eventArray[eventTotal-WSA_WAIT_EVENT_0]); 37 38 //獲取I/O操作的完成情況 39 WSAGetOverlappedResult(acceptsock,&acceptOverlapped,&bytesTransferred,FALSE,&flag); 40 41 if(bytesTransferred==0) 42 { 43 closesocket(acceptsock); 44 WSACloseEvent(eventArray[eventTotal-WSA_WAIT_EVENT_0]); 45 return; 46 } 47 48 //處理接收過來的數據 49 ... 50 51 //再再次發送一個WSARecv請求 52 flag=0; 53 ZeroMemory(&acceptOverlapped,sizeof(WSAOVERLAPPED)); 54 55 databuf.LEN=DATA_BUFSIZE; 56 databuf.buf=buf; 57 WSARecv(acceptsock,&databuf,1,&recvbytes,&flag,&acceptOverlapped,NULL); 58 } 59 60 }
注意:對於接受客戶端連接的函數,還有一個AcceptEx函數,不過這個函數太過麻煩且對性能的提升沒有太大的作用,暫時不打算學習
接下來學習基於完成例程的重疊IO模型:
7.基於完成例程的重疊模型的實現:
完成例程也就是回調函數。這種方式,就是設置一個回調函數,I/O請求完成后,自動調用回調函數即可。如果希望用完成例程為重疊I/O請求提供服務,在我們的應用程序中,必須為指定一個完成例程(回調函數),同時指定一個WSAOVERLAPPED結構。一個完成例程(回調函數)必須擁有下述函數原型:
1 void CALLBACK CompletionROUTINE( 2 DWORD dwError,//表明一個重疊操作的完成狀態是什么 3 DWORD cdTransferred,//參數指定了在重疊操作期間,實際傳輸的字節量是多大 4 LPWSAOVERLAPPED lpOverlapped,//重疊結構 5 DWORD dwFlags//目前尚未使用,應設為0 6 );
8.基於完成例程和基於事件對象兩種重疊模型之間的差異:在使用完成重疊模型時,WSAOVERLAPPED結構的事件字段hEvent並未使用;也就是說,我們不可將一個事件對象同重疊請求關聯到一起。用完成例程發出了一個重疊I/O請求之后,作為我們的調用線程,一旦完成,它最終必須為完成例程提供服務。這樣一來,便要求我們將自己的調用線程置於一種“可警告的等待狀態”。並在I/O操作完成后,對完成例程加以處理。WSAWaitForMultipleEvents函數可用來將我們的線程置於一種可警告的等待狀態。這樣做的缺點在於,我們還必須有一個事件對象可用於WSAWaitForMultipleEvents函數。假定應用程序只用完成例程對重疊請求進行處理,便不可能有任何事件對象需要處理。作為一種變通方法,我們的應用程序可用Win32的SleepEx函數將自己的線程置為一種可警告等待狀態。當然,亦可創建一個偽事件對象,不將它與任何東西關聯在一起。假如調用線程經常處於繁忙狀態,而且並不處在一種可警告的等待狀態,那么根本不會有投遞的完成例
程會得到調用。
SleepEx函數的行為實際上和WSAWaitForMultipleEvents差不多,只是它不需要任何事件對象。
9.SleepEx函數:
1 DWORD SleepEx( 2 DWORD dwMillisecond,//等待時間 3 BOOL bAlertable,//指定完成例程的執行方式 4 )
第二個參數是否置於警覺狀態,如果為FALSE,則一定要等待超時時間完畢之后才會返回,這里我們是希望重疊操作一完成就能返回,所以我們要設置為TRUE
10.window下,管理重疊io有三種方式:基於事件的重疊io模型,基於完成例程的重疊io模型,完成端口模型,相比之下,在性能方面前兩種方式沒有區別,第三種完成端口模型性能最優。因為前兩種都需要自己來管理任務的分派,而完成端口是操作系統來管理任務的分派。雖然第一二中Io模型在性能上沒有區別。但是,基於事件的重疊io模型要受到最多64個等待事件的限制。完成例程則沒有這種限制。
11.完成歷程的基本原理:在基於事件的重疊io模型中,io操作完成后,系統會以事件的方式通知應用程序。而對於完成例程來說,系統會在io操作完成后,直接調用應用程序提供的回調函數。區別也就僅此而已。對於完成例程,我們可以用一種非常形象的話來進行表述:完成例程的處理過程,也就像我們告訴系統,說“我想要在網絡上接收網絡數據,你去幫我辦一下”(投遞WSARecv操作),“不過我並不知道網絡數據合適到達,總之在接收到網絡數據之后,你直接就調用我給你的這個函數(比如_CompletionProess),把他們保存到內存中或是顯示到界面中等等,全權交給你處理了”,於是乎,系統在接收到網絡數據之后,一方面系統會給我們一個通知,另外同時系統也會自動調用我們事先准備好的回調函數,就不需要我們自己操心了。
12.完成例程的函數基本上和基於事件的io模型的函數是相同的。只是在這里我們需要添加一個回調函數。回調函數原型如下:(函數名字隨便起,但是參數類型不能錯):
1 Void CALLBACK _CompletionRoutineFunc( 2 DWORD dwError, // 標志咱們投遞的重疊操作,比如WSARecv,完成的狀態是什么 3 DWORD cbTransferred, // 指明了在重疊操作期間,實際傳輸的字節量是多大 4 LPWSAOVERLAPPED lpOverlapped, // 參數指明傳遞到最初的IO調用內的一個重疊結構 5 DWORD dwFlags // 返回操作結束時可能用的標志(一般沒用) 6 7 );
13.定義了回調函數之后,還需要把回調函數與系統綁定之后,網絡操作完成后才會自動調用回調函數。例如WSARecv綁定過程如下:
1 int WSARecv( 2 SOCKET s, // 當然是投遞這個操作的套接字 3 LPWSABUF lpBuffers, // 接收緩沖區,與Recv函數不同,這里需要一個由WSABUF結構構成的數組 4 DWORD dwBufferCount, // 數組中WSABUF結構的數量,設置為1即可 5 LPDWORD lpNumberOfBytesRecvd,//所接收到的字節數 6 LPDWORD lpFlags, // 說來話長了,我們這里設置為0 即可 7 LPWSAOVERLAPPED lpOverlapped, // “綁定”的重疊結構 8 LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine //回調函數的指針 9 );
//注意:綁定動作就是通過填充最后一個參數進行實現的。
14.完成例程的實現步驟:
(1)創建一個套接字,開始在指定的端口上監聽連接請求
(2)接收連接請求即調用accept函數,生成一個新的socket套接字
(3)准備重疊結構:需要注意的是,一個套接字對應一個或者多個io請求,一個io請求對應一個重疊結構
WSAOVERLAPPED AcceptOverlapped;
ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED)); // 置零
(4)投遞io請求(此處為WSARecv請求),使用重疊結構,回調函數填充參數:
WSARecv(AcceptSocket,&DataBuf,1,&dwRecvBytes, &Flags,&AcceptOverlapped,_CompletionRoutine)
(5)重疊操作綁定完成后,接下來就是等待io請求的完成結果。有兩種方式可以實現:調用WSAWaitForMultipleEvents函數或者SleepEx函數。
1 通過WaitForMultipleEvents函數等待 2 WSAEVENT EventArray[1]; 3 EventArray[0] = WSACreateEvent();// 建立一個事件 4 // 然后就等待重疊請求完成就可以了,注意保存返回值,這個很重要 5 DWORD dwIndex = WSAWaitForMultipleEvents(1,EventArray,FALSE,WSA_INFINITE,TRUE);//注意fAlertable 參數即該函數最后 一個參數一定要設置為 TRUE 6 通過SleepEx函數進行等待,效果是一樣的: 7 DWORD SleepEx(DWORD dwMilliseconds, // 等待的超時時間,如果設置為INFINITE就會一直等待下去 8 BOOL bAlertable // 是否置於警覺狀態,如果為FALSE,則一定要等待超時時間完畢之后才會返回,這里我們是希望重疊操作一完成就能返回,所以同(1)一樣,我們一定要設置為TRUE 9 );
(6)通過等待函數的返回值取得io請求的結果:在等待函數中,正常情況下,在操作完成之后,應該是返回WAIT_IO_COMPLETION,如果返回的是 WAIT_TIMEOUT,則表示等待設置的超時時間到了,但是重疊操作依舊沒有完成,應該通過循環再繼續等待。如果是其他返回值,那就壞事了,說明網絡通信出現了其他異常,程序就可以報錯退出了
1 if(dwIndex == WAIT_IO_COMPLETION) 2 { 3 TRACE("重疊操作完成.../n"); 4 } 5 else if( dwIndex==WAIT_TIMEOUT ) 6 { 7 TRACE(“超時了,繼續調用等待函數”); 8 } 9 else 10 { 11 TRACE(“廢了…”); 12 }
(7)投遞下一個io請求,重復4-7
15.讀取io請求的最終結果數據:在WSARecv調用的時候,是傳遞了一個WSABUF的變量的,用於保存網絡數據,而在我們寫的完成例程回調函數里面,就可以取到客戶端傳送來的網絡數據了。因為系統在調用我們完成例程函數的時候,其實網絡操作已經完成了,WSABUF里面已經有我們需要的數據了,只是通過完成例程來進行后期的處理
示例代碼:
1 //完成例程實現重疊io模型偽代碼 2 SOCKET acceptSock; 3 WSABUF dataBuf; 4 5 void main() 6 { 7 WSAOVERLAPPED overlapped; 8 //1.初始化 9 //... 10 11 //2.接收連接請求 12 acceptSock=accept(listenSock,NULL,NULL); 13 14 //3.初始化重疊結構 15 UINT flag=0; 16 ZeroMemory(&overlapped,sizeof(WSAOVERLAPPED)); 17 dataBuf.len=DATA_BUFSIZE; 18 dataBuf.buf=buf; 19 20 if (WSARecv(acceptSock,&dataBuf,1,&recvBytes,&flag,&overlapped,workroutine)==SOCKET_ERROR)//最后一個參數時回調函數地址 21 { 22 if(WSAGetLastError()!=WSA_IO_PENDING) 23 { 24 printf("WSARecv() failed with error %d\n",WSAGetLastError()); 25 return; 26 } 27 } 28 29 //創建事件 30 eventArray[0]=WSACreateEvent(); 31 while (true) 32 { 33 int index=WSAWaitForMultipleEvents(1,eventArray,FALSE,WSA_INFINITE,TRUE);//最后一個參數最好為true 34 if (index==WAIT_IO_COMPLETION)//io請求完成 35 { 36 break; 37 } 38 else//io請求出錯 39 { 40 return; 41 } 42 } 43 //調用回調函開始進行處理 44 } 45 46 void CALLBACK WorkRoutine(DWORD error,DWORD bytesTransferred,LPWSAOVERLAPPED overlapped,DWORD inflag) 47 { 48 DWORD sendBytes,recvBytes; 49 DWORD flags; 50 51 if(error!=0||bytesTransferred==0) 52 { 53 closesocket(acceptSock); 54 return; 55 } 56 57 flags=0; 58 59 ZeroMemory(&overlapped,sizeof(WSAOVERLAPPED)); 60 dataBuf.len=DATA_BUFSIZE; 61 dataBuf.data=buf; 62 63 if (WSARecv(acceptSock,&dataBuf,1,&recvBytes,&flag,&overlapped,workroutine)==SOCKET_ERROR)//最后一個參數時回調函數地址 64 { 65 if(WSAGetLastError()!=WSA_IO_PENDING) 66 { 67 printf("WSARecv() failed with error %d\n",WSAGetLastError()); 68 return; 69 } 70 } 71 }
