WSAEventSelect(事件選擇)模型
http://joychou.org/index.php/Misc/WSAEventSelect.html
首先需要用WSACreateEvent建立一個事件物件,其初始化狀態為狀態為“未受信” 並且人工重置。
函式原型
WSAEVENT WSACreateEvent(void);
然後利用WSAEventSelect繫結“監聽”套接字到剛建立的事件物件中,並設定感興趣的網路事件。
int WSAEventSelect(
__in SOCKET s, //套接字
__in WSAEVENT hEventObject, //事件物件
__in long lNetworkEvents // 網路事件
);
再利用WSAWaitForMultipleEvents等待事件物件為“受信”狀態。
問:怎麼才是成為“受信”狀態?
當事件物件繫結的套接字進行了I/O處理,
比如客戶端進行了連線或者客戶端傳送了資料,再或者客戶端關閉套接字,
事件物件都會變成“受信”狀態。
DWORD WSAWaitForMultipleEvents(
__in DWORD cEvents, // 事件物件個數
__in const WSAEVENT* lphEvents, // 事件物件陣列
__in BOOL fWaitAll, // 是否全部等待
__in DWORD dwTimeout, // 等待時間
__in BOOL fAlertable // 重疊I/O中使用,這裡設定為FALSE
);
最後用WSAEnumNetworkEvents檢查發生了什麼網路事件,最後一個引數返回發生了什麼網路事件,
這個API如果第二個引數如果不設定為NULL,那麼api呼叫完後,事件物件會變成“未受信”狀態。
如果設定為NULL,就必須用WSAResetEvent設定為“未受信”。
int WSAEnumNetworkEvents(
__in SOCKET s, // 套接字,不過必須使用陣列
__in WSAEVENT hEventObject, // 事件物件 不過必須使用陣列
__out LPWSANETWORKEVENTS lpNetworkEvents // 網路事件結構體指標
);
WSANETWORKEVENTS結構體
typedef struct _WSANETWORKEVENTS
{
long lNetworkEvents;
int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;
最後的主要程式碼:
// 建立一個事件物件,狀態為“未受信” 並且人工重置
WSAEVENT m_listenEvent = WSACreateEvent();
// 繫結 “監聽”套接字到事件物件中,並設定感興趣的網路事件
WSAEventSelect(pThis->m_SockListen, m_listenEvent, FD_ACCEPT | FD_CLOSE);
DWORD dwEventTotal = 0, dwIndex = 0, dwRet = 0;
SOCKET ArrSocket[WSA_MAXIMUM_WAIT_EVENTS] = {0};
WSAEVENT ArrEvent[WSA_MAXIMUM_WAIT_EVENTS] = {0};
WSANETWORKEVENTS m_NetWorkEvents = {0};
TCHAR szBuf[1024] = {0};
ArrSocket[dwEventTotal] = pThis->m_SockListen;
ArrEvent[dwEventTotal] = m_listenEvent;
++dwEventTotal;
while(TRUE)
{
// 等待事件物件為受信,不受信的話會返回超時
// 當事件物件繫結的套接字進行了I/O處理,事件物件會變成“受信”狀態
dwRet = WSAWaitForMultipleEvents(dwEventTotal, ArrEvent, FALSE, 100, FALSE);
// 0x102 WSA_WAIT_TIMEOUT
if (dwRet == WSA_WAIT_TIMEOUT || dwRet == WSA_WAIT_FAILED)
{
continue; //如果返回值是錯誤或是超時,那麼繼迴圈
}
// 獲取索引
dwIndex = dwRet - WSA_WAIT_EVENT_0;
// 檢查是發生了什麼網路事件
// 第二個引數傳入事件物件控制代碼,將其設為“未受信”
WSAEnumNetworkEvents(ArrSocket[dwIndex], ArrEvent[dwIndex], &m_NetWorkEvents);
if (m_NetWorkEvents.lNetworkEvents == FD_ACCEPT)
{
// 如果發生錯誤
if (m_NetWorkEvents.iErrorCode[FD_ACCEPT_BIT] != 0)
{
continue;
}
// 接收客戶端的連線
// 如果第二個填入一個sockaddr_in結構體,可以獲取客戶端的ip
pThis->m_SockClient = accept(ArrSocket[dwIndex], NULL,NULL);
if (pThis->m_SockClient == INVALID_SOCKET)
{
continue;
}
// 建立一個新的事件物件用來繫結和客戶端連線的套接字
WSAEVENT NewEvent = WSACreateEvent();
// 繫結 “連線”套接字 到NewEvent事件物件中,監聽FD_READ和FD_CLOSE
WSAEventSelect(pThis->m_SockClient, NewEvent, FD_READ | FD_CLOSE);
ArrSocket[dwEventTotal] = pThis->m_SockClient;
ArrEvent[dwEventTotal] = NewEvent;
++dwEventTotal;
++pThis->m_ClientNum;
}
if (m_NetWorkEvents.lNetworkEvents == FD_READ)
{
if (m_NetWorkEvents.iErrorCode[FD_READ_BIT] != 0)
{
continue;
}
recv(ArrSocket[dwIndex], (char *)szBuf, 1024, 0);
pThis->ShowMsg(szBuf);
}
// 當連線被關閉時
if (m_NetWorkEvents.lNetworkEvents & FD_CLOSE)
{
if (m_NetWorkEvents.iErrorCode[FD_CLOSE_BIT] != 0)
{
continue;
}
pThis->ShowMsg(_T("客戶端已退出"));
closesocket(ArrSocket[dwIndex]);
WSACloseEvent(ArrEvent[dwIndex]);
//將此套節字和事件物件從陣列中清除。
for (UINT i = dwIndex; i < dwEventTotal-1; i++)
{
ArrSocket[i] = ArrSocket[i+1]; //[i+1]為0
ArrEvent[i] = ArrEvent[i+1]; //[i+1]為0
}
dwEventTotal--;
}
}
注意:
在FD_CLOSE這裡,我總是遇到一個問題,m_NetWorkEvents.iErrorCode[FD_CLOSE_BIT]總是不等於0,即發生了錯誤,剛開始不知道為什麼,一直以為是服務端的問題。不過發生的錯誤碼是0x2745,網上搜索下錯誤碼的意思是
[10053(也就是0x2745的十進位制)] 您的主機中的軟體放棄了一個已建立的連線。
說明已經建立的連線在無意間關閉了,而且服務端不知道。
我們需要做的,只是在客戶端點選x的時候(MFC中過載對話方塊類的OnCancle函式),
關閉掉套接字即可。
特點:
多個客戶端連線
利用WSAWaitForMultipleEvents監聽是否有網路事件發生。有就處理,沒有就一直監聽