共有五種類型的通訊端I/O模型,可讓Winsock應用程式對I/O進行管理,它們包括:select(選擇)、WSAAsyncSelect(非同步選擇)、WSAEventSelect(事件選擇)、overlapped(重疊)以及completion port(完成連接埠)
1.select
最初設計該模型,主要是面向某些使用Unix作業系統的電腦.使用大概原理設定一個集合,通過一個宏的定義來查詢集合類的通訊端可否使用。做個連接埠掃描用這個還是比較開心的。
2.WSAAsyncSelect
模仿WINDOWS訊息機制來實現,使用起來很方便。個人比較喜歡。MFC中的CSOCKET也採用了這個模型。
相關函數:
int WSAAsyncSelect(SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent);
lEvent表示一個掩碼組合,比如FD_CONNECT|FD_READ
LREWULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
wParam: 發生了一個網路事件的通訊端
lParam: (低位)指定了已經發生的網路事件WSAGETSELECTEVENT (高位)包含了可能出現的任何錯誤碼 用WSAGETSELECTERROR擷取
大概使用架構如下:
建立通訊端,使用WSAAsyncSelect進行通訊端和事件綁定,在回呼函數中寫清訊息響應
#define WM_SOCKET WM_USER+1
int WINAPI WinMain(HINSTANCE hINstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
SOCKET Listen;
HWND Window;
//Create a window and assign the winproc
//Start Winsock and create a socket
WSAStartup(...);
Listen = Socket();
bind(...);
WSAAsyscSelect(Listen, Window, WM_SOCKET, FD_ACCEPT | FD_CLOSE);
listen(Listen, 5);
}
BOOL CALLBACK WinProc(HWND hDlg, WORD wMsg, WORD wParam, DWORD lParam)
{
SOCKET Accept;
switch(wMsg)
{
case WM_SOCKET:
//whether an error occurred on the socket by using the WSAGETSELECTERROR() macro
if(WSAGETSELECTERROR(lParam))
{
closesocket...
}
//what event occurred on the socket
switch( WSAGETSELECTEVENT(lParam) )
{
case FD_ACCEPT:
case FD_READ:
case FD_WRITE:
}
}
}
FD_WRITE說明:三種條件下才會發出
使用connect WSAConnect,一個通訊端首次建立串連
使用accept WSAAccept,通訊端被接受以後
若send WSASend sendto WSASendTo操作失敗,返回WSAEWOULDBLOCK錯誤,而且緩衝區空間變得可用
也就是說,收到首條FD_WRITE訊息,便應認為自己必然能在一個通訊端上發資料.
3 WSAEventSelect
你可以想象著,一排的空通訊端等著對方的串連...
如上章所述,async投遞一個視窗常式,而Event投遞一個事件物件控點.
或者更清楚解釋為,通訊端和事件對象對應著,當一個通訊端有事件發生,事件對象返回相應的值,通過這個值來索引這個通訊端。
要注意該模式裡兩種工作狀態和模式,signaled/nonsignaled manual reset/auto reset
一開始預設為未傳信(nonsignaled)和人工重設(manual reset)狀態,隨著網路事件的觸發,工作狀態從未船信到已傳信.在完成一個I/O請求處理後,要負責將工作狀態改變
首先建立一個事件對象 WSAEVENT WSACreateEvent(void);
其次將事件對象和通訊端聯絡在一起 int WSAEventSelect(SOCKET s, WSAEVENT hEventObject, long lNetworkEvents);
等待網路事件觸發事件物件控點狀態 DWORD WSAWaitForMultipleEvents(....DWORD dwTimeout, BOOL fAlertable);
dwTimeout參數規定等待一個網路事件發生有多長時間,毫秒為單位.超過立即返回,即使由fWaitAll參數規定條件未滿足也如此.如果設為0, 函數會檢測指定的事件對象狀態,然後返回.這樣造成一個"輪詢",盡量避免設為0.若設為WSA_INFINITE,那麼只有在一個網路事件傳回一個事件對象後,才會返回.
fAlertable:忽略,設為FALSE,主要用於重疊式I/O模型中
將已傳信狀態更改為未傳信 BOOL WSAResetEvent(WSAEVENT hEvent);
釋放事件控制代碼使用的系統資源 BOOL WSACloseEvent(WSAEVENT hEvent);
現在還需解決的一問題是,怎麼獲得發生事件的通訊端,async直接用一個socket變數接收,而在event裡,是通過事先的一個數組索引獲得.這個方法名為WSAEnumNetworkEvents();
代碼架構如下:
//先建立事件數目組和通訊端數組
SOCKET Socket[常量];
WSAEVENT Event[];
DWORD EvenTotal = 0;
//這裡進行通訊端準備到bind那步,建立一個事件,並且聯絡
NewEvent = WSACreateEvent();
WSAEventSelect(Listen, NewEvent, FD_ACCEPT|FD_CLOSE);
//最重要一步,加入到數組
Socket[EventTotal] = Listen;
Event[EventTotal] = NewEvent;
EventTotal++;
while(TRUE)
{
//等待一個通訊端串連
Index = WSAWaitForMultipleEvents(..);
//在EnumNetworkEvents中進行查詢,判斷在哪個通訊端上,發生什麼網路事件類型.
WSAEnumNetworkEvents(
SocketArray[Index - WSA_WAIT_EVENT_0],
EventArray[Index - WSA_WAIT_EVENT_0],
NetworkEvents);
}
對一個發生事件通訊端操作
WSAWaitForMultipleEvents傳回值:減去預定值WSA_WAIT_EVENT_0
Iindex = WSAWaitForMultipleEvents(..);
MyEvent = EventArray[Index - WSA_WAIT_EVENT_0];
接下來可調用WSAEnumNetworkEvents函數,調查發生什麼類型網路事件
4IO重疊
對一個通訊端多次利用,利用CRITICAL_SECTION來進行互斥
5。。。略