windows socket網絡編程–事件選擇模型
事件選擇模型概述
Winsock提供了另一種有用的異步事件通知I/O模型——WSAEventSelect模型。這個模型與WSAAsyncSelect模型類似,允許應用程序在一個或者多個套接字上接收基於事件的網絡通知。它與 WSAAsyncSelect模型類似是因為它也接收FDXXX類型的網絡事件,不過並不是依靠Windows的消息驅動機制,而是經由事件對象句柄通知
API詳解
WSAEVENT WSAAPI WSACreateEvent();
返回值
如果未發生錯誤, WSACreateEvent 將返回事件對象的句柄。 否則,返回值WSA_INVALID_EVENT。
作用
創建新的事件對象
int WSAAPI WSAEventSelect(
SOCKET s, //標識套接字的描述符。
WSAEVENT hEventObject, //標識要與指定FD_XXX網絡事件集關聯的事件對象的句柄。
long lNetworkEvents//一個位掩碼,指定應用程序感興趣的FD_XXX網絡事件的組合。
);
返回值
如果應用程序的網絡事件的規範和關聯的事件對象成功,則返回值為零。 否則,返回值SOCKET_ERROR。
作用
給事件綁上socket與操作碼,並投遞給操作系統,應用程序便可以在事件上等待了。
事件類型 | 含義 |
---|---|
FD_READ | 應用程序想接收是否有可讀的通知 |
FD_WRITE | 應用程序想接收是否有可寫的通知 |
FD_OOB | 應用程序想接收是否有OOB數據抵達通知 |
FD_ACCEPT | 應用程序想接收與傳入連接有關的通知 |
FD_CONNECT | 應用程序想接收一個已完成連接的通知或者一個多點join操作的通知 |
FD_CLOSE | 應用程序想接收與套接字關閉有關的通知 |
DWORD WSAAPI WSAWaitForMultipleEvents(
DWORD cEvents,
const WSAEVENT *lphEvents,
BOOL fWaitAll,
DWORD dwTimeout,
BOOL fAlertable
);
cEvents
lphEvents 指向的數組中的事件對象句柄數。 事件對象句柄的最大數目 是WSA_MAXIMUM_WAIT_EVENTS。 必須指定一個或多個事件。
lphEvents
指向事件對象句柄數組的指針。 數組可以包含不同類型的對象的句柄。 如果 fWaitAll 參數設置為 TRUE,則它可能不包含同一句柄的多個副本。 如果在等待仍在掛起時關閉其中一個句柄,則未定義 WSAWaitForMultipleEvents 的行為。
fWaitAll
一個指定等待類型的值。 如果為 TRUE,則當 發出 lphEvents 數組中所有對象的狀態時,函數將返回。 如果為 FALSE,則函數在發出任何事件對象的信號時返回。 在後一種情況下,返回值減 去WSA_WAIT_EVENT_0 指示導致函數返回其狀態的事件對象的索引。 如果在調用期間發出了多個事件對象的信號,則這是信號事件對象的數組索引,其索引值為所有信號事件對象的最小索引值。
dwTimeout
超時間隔(以毫秒為單位)。 WSAWaitForMultipleEvents 如果超時間隔過期,即使 不滿足 fWaitAll 參數指定的條件,也會返回。 如果 dwTimeout 參數為零, WSAWaitForMultipleEvents 將測試指定事件對象的狀態並立即返回。 如果 dwTimeoutWSA_INFINITE, WSAWaitForMultipleEvents 將永遠等待;也就是說,超時間隔永遠不會過期。
fAlertable
一個值,該值指定線程是否處於可警報的等待狀態,以便系統可以執行I/O完成例程。 如果為TRUE,則線程處於可警報的等待狀態,當系統執行 I/O 完成例程時, WSAWaitForMultipleEvents 可以返回。 在這種情況下,將返回 WSA_WAIT_IO_COMPLETION ,並且等待的事件尚未發出信號。 應用程序必須再次調用 WSAWaitForMultipleEvents 函數。 如果為FALSE,則線程不會處於可警報的等待狀態,並且不會執行 I/O 完成例程。
int WSAAPI WSAEnumNetworkEvents(
SOCKET s, //標識套接字
WSAEVENT hEventObject, //用於標識要重置的關聯事件對象的可選句柄
LPWSANETWORKEVENTS lpNetworkEvents //指向 WSANETWORKEVENTS 結構的指針,該結構填充了發生的網絡事件記錄和任何關聯的錯誤代碼
);
返回值
如果操作成功,則返回值為零。 否則,返回值SOCKET_ERROR
作用
枚舉出與事件對象相關聯的套接字發生了哪些信號,結果放在WSANETWORKEVENTS結構體中
工作原理
流程大致是這樣:
- 定義一個socket數組和event數組
- 每一個socket操作關聯一個event對象
- 調用WSAWaitForMultipleEvents函數等待事件的觸發
- 調用WSAEnumNetworkEvents函數查看是哪個一個事件,根據事件找到相應的socket,然後進行相應的處理:比如數據顯示等,同時,記得要將那個event重置為無信號狀態。
- 循環步驟3和4,直到服務器退出。
流程圖
代碼實現
服務端
UINT CMFCWSAEventDlg::ThreadProc(LPVOID lparam) {
// TODO: 在此添加控件通知處理程序代碼
CMFCWSAEventDlg* p = (CMFCWSAEventDlg*)lparam;
SocketInit socketInit;
SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socketServer == INVALID_SOCKET) {
AfxMessageBox(_T("套接字創建失敗"));
closesocket(socketServer);
WSACleanup();
}
sockaddr_in sock;
sock.sin_family = AF_INET;
sock.sin_port = htons(5678);
sock.sin_addr.S_un.S_addr = INADDR_ANY;
int n = sizeof(sock);
if (bind(socketServer, (sockaddr*)&sock, sizeof(sock)) == SOCKET_ERROR) {
AfxMessageBox(_T("監聽失敗"));
closesocket(socketServer);
WSACleanup();
}
if (listen(socketServer, SOMAXCONN) == SOCKET_ERROR) {
AfxMessageBox(_T("監聽失敗"));
closesocket(socketServer);
WSACleanup();
}
p->showText.SetWindowText("開始監聽\r\n");
// 創建事件對象,並關聯到新的套節字
WSAEVENT event = ::WSACreateEvent();
::WSAEventSelect(socketServer, event, FD_ACCEPT | FD_CLOSE);
// 添加到表中
p->eventArray[p->nEventTotal] = event;
p->sockArray[p->nEventTotal] = socketServer;
p->nEventTotal++;
CString str;
sockaddr_in addrRemote;
while (1){
// 在所有事件對象上等待
int nIndex = ::WSAWaitForMultipleEvents(p->nEventTotal, p->eventArray, FALSE, WSA_INFINITE, FALSE);
// 對每個事件調用WSAWaitForMultipleEvents函數,以便確定它的狀態
nIndex = nIndex - WSA_WAIT_EVENT_0;
for (int i = nIndex; i < p->nEventTotal; i++)
{
nIndex = ::WSAWaitForMultipleEvents(1, &p->eventArray[i], TRUE, 1000, FALSE);
if (nIndex == WSA_WAIT_FAILED || nIndex == WSA_WAIT_TIMEOUT)
{
continue;
}
else
{
// 獲取到來的通知消息,WSAEnumNetworkEvents函數會自動重置受信事件
WSANETWORKEVENTS event;
::WSAEnumNetworkEvents(p->sockArray[i], p->eventArray[i], &event);
if (event.lNetworkEvents & FD_ACCEPT) // 處理FD_ACCEPT通知消息
{
if (event.iErrorCode[FD_ACCEPT_BIT] == 0)
{
if (p->nEventTotal > WSA_MAXIMUM_WAIT_EVENTS)
{
p->showText.SetSel(-1);
p->showText.ReplaceSel("時間太長");
continue;
}
int nAddrLen = sizeof(addrRemote);
SOCKET sNew = ::accept(p->sockArray[i], (SOCKADDR*)&addrRemote, &nAddrLen);
//MessageBox("已連接");
int nLen = p->showText.GetWindowTextLengthA();
//p->showText.SetWindowText()
str.Format("%s建立連接\r\n", ::inet_ntoa(addrRemote.sin_addr));
p->showText.SetSel(-1);
p->showText.ReplaceSel(str);
WSAEVENT event = ::WSACreateEvent();
::WSAEventSelect(sNew, event, FD_READ | FD_CLOSE | FD_WRITE);
// 添加到表中
p->eventArray[p->nEventTotal] = event;
p->sockArray[p->nEventTotal] = sNew;
p->nEventTotal++;
}
}
else if (event.lNetworkEvents & FD_READ) // 處理FD_READ通知消息
{
if (event.iErrorCode[FD_READ_BIT] == 0)
{
//char szText[256];
char szText[1024] = { 0 };
//memset(szText, 0, sizeof(szText));
int nlen = strlen(szText);
int nRecv = ::recv(p->sockArray[i], szText,1024, 0);
//AfxMessageBox(nRecv);
if (nRecv > 0)
{
szText[nRecv] = '\0';
str.Format("%s發來了一條消息:%s\r\n", ::inet_ntoa(addrRemote.sin_addr), szText);
p->showText.SetSel(-1);
p->showText.ReplaceSel(str);
//szText[0] = '\0';
// 向客戶端發送數據
char *sendText = getallprime(1000);
if (::send(p->sockArray[i], sendText, strlen(sendText), 0) > 0)
{
p->showText.SetSel(-1);
p->showText.ReplaceSel("已發送結果\r\n");
}
}
}
}
else if (event.lNetworkEvents & FD_CLOSE) // 處理FD_CLOSE通知消息
{
if (event.iErrorCode[FD_CLOSE_BIT] == 0)
{
::closesocket(p->sockArray[i]);
for (int j = i; j < p->nEventTotal - 1; j++)
{
p->eventArray[j] = p->eventArray[j + 1];
p->sockArray[j] = p->sockArray[j + 1];
}
p->nEventTotal--;
}
p->showText.SetSel(-1);
p->showText.ReplaceSel("關閉連接\r\n");
}
}
}
}
}