四.Windows I/O模型之重疊IO(overlapped)模型


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 }
View Code

注意:對於接受客戶端連接的函數,還有一個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     );
View Code

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     )
View Code

第二個參數是否置於警覺狀態,如果為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 );
View Code

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     ); 
View Code

    //注意:綁定動作就是通過填充最后一個參數進行實現的。
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               ); 
View Code

(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     }
View Code

(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 }
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM