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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

重叠I/O之事件对象通知

發布時間:2024/4/11 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 重叠I/O之事件对象通知 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

重疊I/O,Overlapped I/O。


一、 重疊I/O的優點
?
1. 可以運行在支持Winsock2的所有Windows平臺 ,而不像完成端口只是支持NT系統。
2. 比起阻塞、select、WSAAsyncSelect以及WSAEventSelect等模型,重疊I/O使應用程序能達到更佳的系統性能。因為它和這4種模型不同的是,使用重疊I/O的應用程序通知緩沖區收發系統直接使用數據,也就是說,如果應用程序投遞了一個10KB大小的緩沖區來接收數據,且數據已經到達套接字,則該數據將直接被拷貝到投遞的緩沖區。而這4種模型種,數據到達并拷貝到單套接字接收緩沖區中,此時應用程序會被告知可以讀入的容量。當應用程序調用接收函數之后,數據才從單套接字緩沖區拷貝到應用程序的緩沖區,差別就體現出來了。
3. 從《windows網絡編程》中提供的試驗結果中可以看到,在使用了P4 1.7G Xero處理器(CPU很強啊)以及768MB的回應服務器中,最大可以處理4萬多個SOCKET連接,在處理1萬2千個連接的時候CPU占用率才40% 左右 ―― 非常好的性能,已經直逼完成端口了。


二、重疊I/O的基本原理
?
概括一點說,重疊I/O是讓應用程序使用重疊數據結構(WSAOVERLAPPED),一次投遞一個或多個Winsock I/O請求。針對這些提交的請求,在它們完成之后,應用程序會收到通知,于是就可以通過自己另外的代碼來處理這些數據了。
?
需要注意的是,有兩個方法可以用來管理重疊I/O請求的完成情況(就是說接到重疊操作完成的通知):
?
1. 事件對象通知(Event Object Notification) 。
2. 完成例程(Completion Routines)(注意,這里并不是完成端口)
?
本文只是講述如何來使用事件通知的的方法實現重疊I/O,如沒有特殊說明,本文的重疊I/O默認就是指基于事件通知的重疊I/O。
?
既然是基于事件通知,就要求將Windows事件對象與WSAOVERLAPPED結構關聯在一起(WSAOVERLAPPED結構中專門有對應的參數),既然要使用重疊結構,我們常用的send, sendto, recv, recvfrom也都要被WSASend,WSASendto, WSARecv, WSARecvFrom替換掉了,它們的用法我后面會講到,這里只需要注意一點,它們的參數中都有一個Overlapped參數,我們可以假設是把我們的WSARecv這樣的操作操作“綁定”到這個重疊結構上,提交一個請求,其他的事情就交給重疊結構去操心,而其中重疊結構又要與Windows的事件對象“綁定”在一起,這樣我們調用完WSARecv以后就可以“坐享其成”,等到重疊操作完成以后,自然會有與之對應的事件來通知我們操作完成,然后我們就可以來根據重疊操作的結果取得我們想要德數據了。
?
三、關于重疊I/O的基礎知識
?
下面來介紹并舉例說明一下編寫重疊I/O的程序中將會使用到的幾個關鍵數據結構和函數。
?
1. WSAOVERLAPPED結構
?
這個結構自然是重疊I/O里的核心,它是這么定義的:
?
typedef struct _WSAOVERLAPPED

{
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
WSAEVENT hEvent; //?
唯一需要關注的參數,用來關聯WSAEvent對象
} WSAOVERLAPPED, *LPWSAOVERLAPPED;
我們需要把WSARecv等操作投遞到一個重疊結構上,而我們又需要一個與重疊結構“綁定”在一起的事件對象來通知我們操作的完成,看到了和hEvent參數,不用我說你們也該知道如何來來把事件對象綁定到重疊結構上吧?大致如下:
WSAEVENT event; // 定義事件
WSAOVERLAPPED AcceptOverlapped ; // 定義重疊結構
event = WSACreateEvent(); // 建立一個事件對象句柄
ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED)); // 初始化重疊結構
AcceptOverlapped.hEvent = event;
?
2. WSARecv系列函數
?
在重疊I/O中,接收數據就要靠它了,它的參數也比recv要多,因為要用刀重疊結構嘛,它是這樣定義的:
?
int WSARecv

(
SOCKET s, //?
當然是投遞這個操作的套接字
LPWSABUF lpBuffers, //?
接收緩沖區,與Recv函數不同這里需要一個由WSABUF結構構成的數組
DWORD dwBufferCount, // 數組中WSABUF結構的數量
LPDWORD lpNumberOfBytesRecvd, // 如果接收操作立即完成,這里會返回所接收到的字節數
LPDWORD lpFlags, // 說來話長了,我們這里設置為0 即可
LPWSAOVERLAPPED lpOverlapped, // “綁定”的重疊結構
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // 完成例程中將會用到的參數,我們這里設置為 NULL
);

返回值:
WSA_IO_PENDING?
: 最常見的返回值,這是說明我們的WSARecv操作成功了。
?
舉個例子:(變量的定義順序和上面的說明的順序是對應的,下同)
SOCKET s;
WSABUF DataBuf; // 定義WSABUF結構的緩沖區
// 初始化一下DataBuf
#define DATA_BUFSIZE 5096
char buffer[DATA_BUFSIZE];
ZeroMemory(buffer, DATA_BUFSIZE);
DataBuf.len = DATA_BUFSIZE;
DataBuf.buf = buffer;
DWORD dwBufferCount = 1, dwRecvBytes = 0, Flags = 0;
// 重疊結構,如果要處理多個操作,這里當然需要一個WSAOVERLAPPED數組。
WSAOVERLAPPED AcceptOverlapped ;

//?事件,如果要多個事件,這里當然也需要一個WSAEVENT數組。需要注意的是,可能一個SOCKET同時會有一個以上的重疊請求,也就會對應一個以上的WSAEVENT。
WSAEVENT event;?

Event = WSACreateEvent();
ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED));
AcceptOverlapped.hEvent = event; // 關鍵的一步,把事件句柄“綁定”到重疊結構上。

作了這么多工作,終于可以使用WSARecv來把我們的請求投遞到重疊結構上了,呼…
WSARecv(s, &DataBuf, dwBufferCount, &dwRecvBytes, &Flags, &AcceptOverlapped, NULL);
其他的函數我這里就不一一介紹了,因為我們畢竟還有MSDN這么個好幫手,而且在講后面的完成例程和完成端口的時候我還會講到一些 ^_^
?
3. WSAWaitForMultipleEvents函數
?
熟悉WSAEventSelect模型的朋友對這個函數肯定不會陌生,不對,其實大家都不應該陌生,這個函數與線程中常用的WaitForMultipleObjects函數有些地方還是比較像的,因為都是在等待某個事件的觸發嘛。
?
因為我們需要事件來通知我們重疊操作的完成,所以自然需要這個等待事件的函數與之配套。
?
DWORD WSAWaitForMultipleEvents(
DWORD cEvents, // 等候事件的總數量
const WSAEVENT* lphEvents, // 事件數組的指針
BOOL fWaitAll, // 這個要多說兩句:如果設置為 TRUE,則事件數組中所有事件被傳信的時候函數才會返回;FALSE則任何一個事件被傳信函數都要返回。我們這里肯定是要設置為FALSE的。
DWORD dwTimeout, // 超時時間,如果超時,函數會返回。
WSA_WAIT_TIMEOUT? // 如果設置為0,函數會立即返回;如果設置為 WSA_INFINITE只有在某一個事件被傳信后才會返回。在這里不建議設置為WSA_INFINITE,因為…后面再講吧。
?
BOOL fAlertable // 在完成例程中會用到這個參數,這里我們先設置為FALSE。
);


返回值:
WSA_WAIT_TIMEOUT :最常見的返回值,我們需要做的就是繼續等待。
WSA_WAIT_FAILED : 出現了錯誤,請檢查cEvents和lphEvents兩個參數是否有效如果事件數組中有某一個事件被傳信了,函數會返回這個事件的索引值,但是這個索引值需要減去預定義值 WSA_WAIT_EVENT_0才是這個事件在事件數組中的位置。具體的例子就先不在這里舉了,后面還會講到。
注意:WSAWaitForMultipleEvents函數只能支持由WSA_MAXIMUM_WAIT_EVENTS對象定義的一個最大值,是 64,就是說WSAWaitForMultipleEvents只能等待64個事件,如果想同時等待多于64個事件,就要創建額外的工作者線程,就不得不去管理一個線程池,這一點就不如下一篇要講到的完成例程模型了。
?
4. WSAGetOverlappedResult函數
?
既然我們可以通過WSAWaitForMultipleEvents函數來得到重疊操作完成的通知,那么我們自然也需要一個函數來查詢一下重疊操作的結果,定義如下:
?
BOOL WSAGetOverlappedResult(
SOCKET s, // 套接字,不用說了
LPWSAOVERLAPPED lpOverlapped, // 要查詢的那個重疊結構的指針
LPDWORD lpcbTransfer, // 本次重疊操作的實際接收(或發送)的字節數
… //其他參數暫略,以后補充。
);
如果WSAGetOverlappedResult完成以后,第三個參數返回是 0 ,則說明通信對方已經關閉連接,我們這邊的SOCKET, Event之類的也就可以關閉了。
?
四、實現重疊I/O的步驟
?
下面我們配合代碼,來一步步的講解如何親手完成一個重疊I/O。
?
第一步:定義變量
?
#define DATA_BUFSIZE 4096 // 接收緩沖區大小
SOCKET ListenSocket, // 監聽套接字
SOCKET AcceptSocket; // 與客戶端通信的套接字
WSAOVERLAPPED AcceptOverlapped; // 重疊結構一個
WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS]; // 用來通知重疊操作完成的事件句柄數組
WSABUF DataBuf[DATA_BUFSIZE] ;?
DWORD dwEventTotal = 0, // 程序中事件的總數
dwRecvBytes = 0, // 接收到的字符長度
Flags = 0; // WSARecv的參數
?
?
第二步:創建一個套接字,開始在指定的端口上監聽連接請求。
?
和其他SOCKET初始化一樣,直接照搬即可。
?
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2),&wsaData);
?
ListenSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); //創建TCP套接字
SOCKADDR_IN ServerAddr; //分配端口及協議族并綁定
ServerAddr.sin_family=AF_INET;?
ServerAddr.sin_addr.S_un.S_addr =htonl(INADDR_ANY);?
ServerAddr.sin_port=htons(11111);
bind(ListenSocket,(LPSOCKADDR)&ServerAddr, sizeof(ServerAddr)); // 綁定套接字
listen(ListenSocket, 5); //開始監聽
?
第三步:接受一個入站的連接請求
?
AcceptSocket = accept (ListenSocket, NULL,NULL) ;?
當然,這里是偷懶,如果想要獲得連入客戶端的信息(記得論壇上也常有人問到),accept的后兩個參數就不要用NULL,而是這樣:
?
SOCKADDR_IN ClientAddr; // 定義一個客戶端得地址結構作為參數
int addr_length=sizeof(ClientAddr);
AcceptSocket = accept(ListenSocket,(SOCKADDR*)&ClientAddr, &addr_length);
// 于是乎,我們就可以輕松得知連入客戶端的信息了
LPCTSTR lpIP = inet_ntoa(ClientAddr.sin_addr); // IP
UINT nPort = ClientAddr.sin_port; // Port
?
第四步:建立并初始化重疊結構
?
為連入的這個套接字新建立一個WSAOVERLAPPED重疊結構,并且象前面講到的那樣,為這個重疊結構從事件句柄數組里挑出一個空閑的對象句柄“綁定”上去。
?
// 創建一個事件,dwEventTotal可以暫時先作為Event數組的索引。
EventArray[dwEventTotal] = WSACreateEvent();?
?
ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED)); // 置零
AcceptOverlapped.hEvent = EventArray[dwEventTotal]; // 關聯事件
?
char buffer[DATA_BUFSIZE];
ZeroMemory(buffer, DATA_BUFSIZE);
DataBuf.len = DATA_BUFSIZE;
DataBuf.buf = buffer; // 初始化一個WSABUF結構
dwEventTotal ++; // 總數加一
?
第五步:以WSAOVERLAPPED結構為參數,在套接字上投遞WSARecv請求。
?
各個變量都已經初始化OK以后,我們就可以開始Socket操作了,然后讓WSAOVERLAPPED結構來替我們管理I/O 請求,我們只須等待事件的觸發就可以了。
?
if(SOCKET_ERROR==WSARecv(AcceptSocket ,&DataBuf,1,&dwRecvBytes,&Flags,&AcceptOverlapped, NULL))
{?
// 返回WSA_IO_PENDING是正常情況,表示IO操作正在進行,不能立即完成。如果不是WSA_IO_PENDING錯誤,就大事不好了!
if(WSAGetLastError() != WSA_IO_PENDING)?
{
// 那就只能關閉大吉了
closesocket(AcceptSocket);
WSACloseEvent(EventArray[dwEventTotal]);
}
}
?
第六步:用WSAWaitForMultipleEvents函數等待重疊操作返回的結果
?

因為前面已經把事件和Overlapped綁定在一起,重疊操作完成后我們會接到事件通知。
DWORD dwIndex;

//?等候重疊I/O調用結束
dwIndex=WSAWaitForMultipleEvents(dwEventTotal,EventArray ,FALSE ,WSA_INFINITE,FALSE);
// 注意這里返回的Index并非是事件在數組里的Index,而是需要減去WSA_WAIT_EVENT_0
dwIndex = dwIndex – WSA_WAIT_EVENT_0;
?
第七步:使用WSAResetEvent函數重設當前這個用完的事件對象
?
事件已經被觸發了之后,它對于我們來說已經沒有利用價值了,所以要將它重置一下留待下一次使用,很簡單,就一步,連返回值都不用考慮。
WSAResetEvent(EventArray[dwIndex]);
?
第八步:使用WSAGetOverlappedResult函數取得重疊調用的返回狀態
?
這是我們最關心的事情,費了那么大勁投遞的這個重疊操作究竟是個什么結果呢?其實對于本模型來說,唯一需要檢查一下的就是對方的Socket連接是否已經關閉了。
?
DWORD dwBytesTransferred;
WSAGetOverlappedResult( AcceptSocket,AcceptOverlapped,&dwBytesTransferred, FALSE, &Flags);
// 先檢查通信對方是否已經關閉連接,如果dwBytesTransferred等于0則表示連接已經關閉,就關閉套接字。
if(dwBytesTransferred == 0)
{
closesocket(AcceptSocket);
WSACloseEvent(EventArray[dwIndex]); // 關閉事件
return;
}
?
?
第九步:“享受”接收到的數據
?
如果程序執行到了這里,那么就說明一切正常,WSABUF結構里面就存有我們WSARecv來的數據了,終于到了盡情享用成果的時候了!喝杯茶,休息一下吧~~~^_^
?
DataBuf.buf就是一個char*字符串指針,聽憑你的處理吧,這里就不多說了。
?
?
第十步:同第五步一樣,在套接字上繼續投遞WSARecv請求,然后重復步驟 6 ~ 9。
?
這樣一路下來,我們終于可以從客戶端接收到數據了,但是回想起來,呀~~~~~,這豈不是只能收到一次數據,然后程序不就Over了?所以我們接下來不得不重復一遍第五步到第九步的工作,再次在這個套接字上投遞另一個WSARecv請求,并且使整個過程循環起來,are u clear?
?
五、多客戶端情況的注意事項
?
完成了上面的循環以后,重疊I/O就已經基本上搭建好了80%了,為什么不是100%呢?因為仔細一回想起來,呀~~~~~~~,這樣豈不是只能連接一個客戶端?是的,如果只處理一個客戶端,那重疊I/O就半點優勢也沒有了,我們正是要使用重疊I/O來處理多個客戶端。
?
所以我們不得不再對結構作一些改動。
?
首先需要一個SOCKET數組 ,分別用來和每一個SOCKET通信。其次,因為重疊I/O中每一個SOCKET操作都是要“綁定”一個重疊結構的,所以需要為每一個SOCKET操作搭配一個WSAOVERLAPPED結構,但是這樣說并不嚴格,因為如果每一個SOCKET同時只有一個操作,比如WSARecv,那么一個SOCKET就可以對應一個WSAOVERLAPPED結構,但是如果一個SOCKET上會有WSARecv 和WSASend兩個操作,那么一個SOCKET肯定就要對應兩個WSAOVERLAPPED結構,所以有多少個SOCKET操作就會有多少個WSAOVERLAPPED結構。
?
然后,同樣是為每一個WSAOVERLAPPED結構都要搭配一個WSAEVENT事件,所以說有多少個SOCKET操作就應該有多少個WSAOVERLAPPED結構,有多少個WSAOVERLAPPED結構就應該有多少個WSAEVENT事件,最好把SOCKET、WSAOVERLAPPED 、WSAEVENT三者的關聯起來,到了關鍵時刻才會臨危不亂。
?
須要開兩個線程。一個線程用來循環監聽端口,接收請求的連接,然后給在這個套接字上配合一個WSAOVERLAPPED結構投遞第一個WSARecv請求。另一個個線程用來不停的對WSAEVENT數組WSAWaitForMultipleEvents,等待任何一個重疊操作的完成,然后根據返回的索引值進行處理,處理完畢以后再繼續投遞下一個WSARecv請求。
?
這里需要注意一點的是,前面是把WSAWaitForMultipleEvents函數的參數設置為WSA_INFINITE的,但是在多客戶端的時候這樣就不好了,需要設定一個超時時間,如果等待超時了再重新WSAWaitForMultipleEvents。因為WSAWaitForMultipleEvents函數在沒有觸發的時候是阻塞在那里的,我們可以設想一下,這時如果監聽線程中接入了新的連接,自然也會為這個連接增加一個Event,但是WSAWaitForMultipleEvents還是阻塞在那里就不會處理這個新連接的Event了。

?

總結

以上是生活随笔為你收集整理的重叠I/O之事件对象通知的全部內容,希望文章能夠幫你解決所遇到的問題。

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