WSAEventSelect模型是也稱異步事件選擇模型Windows Sockets提供的一個有用異步I/O模型。該模型允許在一個或者多個套接字上接收以事件爲基礎的網絡事件通知。Windows Sockets應用程序在創建套接字後,調用WSAEventSelect()函數,將一個事件對象與網絡事件集合關聯在一起。當網絡事件發生時,應用程序以事件的形式接收網絡事件通知。
初始化網絡環境,創建一個監聽的socket,然後進行connect操作。接下來WSACreateEvent()創建一個網絡事件對象,其聲明如下:
1、WSAEVENT WSACreateEvent(void); //返回創建的事件,該事件和後面的客戶端套接字關聯
2、再調用WSAEventSelect,來將監聽的socket與該事件進行一個關聯,其聲明如下:
int WSAEventSelect(
SOCKET s, //套接字
WSAEVENT hEventObject, //網絡事件對象
long lNetworkEvents //需要關注的事件
);
啓動一個線程在線程函數中調用WSAWaitForMultipleEvents等待1中的event事件,其聲明如下:
DWORD WSAWaitForMultipleEvents(
DWORD cEvents, //指定了事件對象數組裏邊的個數,最大值爲64
const WSAEVENT FAR *lphEvents, //事件對象數組
BOOL fWaitAll, //等待類型,TRUE表示要數組裏全部事件都有信號才返回,FALSE表示至少有一個就返回,這裏必須爲FALSE
DWORD dwTimeout, //等待的超時時間 INFINTY 一直等待阻塞在這裏 可以設置具體的時間單位ms 在指定的時間超時返回
BOOL fAlertable //當系統的執行隊列有I/O例程要執行時,是否返回,TRUE執行例程返回,FALSE不返回不執行,這裏爲FALSE
);
4、當事件發生,我們需要調用WSAEnumNetworkEvents,來檢測指定的socket上的網絡事件是否有錯誤發生。其聲明如下:
int WSAEnumNetworkEvents
(
SOCKET s, //指定的socket 客戶端套接字
WSAEVENT hEventObject, //事件對象 第一步創建的事件對象
LPWSANETWORKEVENTS lpNetworkEvents //檢測到的信息會存在這個結構體裏
);
typedef struct _WSANETWORKEVENTS {
long lNetworkEvents;//指定了哪個已經發生的網絡事件
int iErrorCodes[FD_MAX_EVENTS]; </span>//錯誤碼
} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;
根據這個結構體我們就可以判斷是否是我們所關注的網絡事件已經發生了。如果是我們的讀的網絡事件發生了,那麼我們就調用recv函數進行操作。若是關閉的事件發生了,就調用closesocket將socket關掉,在數組裏將其置零等操作。
下面是示例代碼(客戶端代碼、icop作爲服務器 wsaeventselect模型一般用作客戶端)
#pragma once #include "stdafx.h" #include <WinSock2.h> #include <Windows.h> // 釋放指針的宏 #define RELEASE(x) {if(x != NULL) {delete x; x = NULL;}} // 釋放句柄的宏 #define RELEASE_HANDLE(x) {if(x != NULL && x != INVALID_HANDLE_VALUE) { CloseHandle(x); x = INVALID_HANDLE_VALUE; }} // 釋放Socket的宏 #define RELEASE_SOCKET(x) {if(x != INVALID_SOCKET) { closesocket(x); x = INVALID_SOCKET; }} class ClientBase { public: ClientBase(); ~ClientBase(); // 啓動通信 BOOL Start(const char *IPAddress, USHORT port); // 關閉通信 BOOL Stop(); // 發送數據 BOOL Send(const BYTE* buffer, int len); // 是否已啓動 BOOL HasStarted(); // 事件通知函數(派生類重載此族函數) // 連接關閉 virtual void OnConnectionClosed() = 0; // 連接上發生錯誤 virtual void OnConnectionError() = 0; // 讀操作完成 virtual void OnRecvCompleted(BYTE* buffer, int len) = 0; // 寫操作完成 virtual void OnSendCompleted() = 0; private: // 接收線程函數 static DWORD WINAPI RecvThreadProc(LPVOID lpParam); // socket是否存活 BOOL IsSocketAlive(SOCKET sock); SOCKET clientSock; WSAEVENT socketEvent; HANDLE stopEvent; HANDLE thread; };
----------.cpp
#include "ClientBase.h"
#include <WS2tcpip.h>
#pragma comment(lib, "WS2_32.lib")
ClientBase::ClientBase()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
}
ClientBase::~ClientBase()
{
WSACleanup();
}
BOOL ClientBase::Start(const char *IPAddress, USHORT port)
{
clientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (clientSock == INVALID_SOCKET)
return false;
socketEvent = WSACreateEvent(); //1創建異步事件
第二參數 是否手動重置 否 第三參數 初始有無信號 無信號 (手動重置在信號經過waitforsingleObject後,不會重置事件,事件還是會有信號,而設置自動重置後,事件會自動置爲無信號)
stopEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(port);
inet_pton(AF_INET, IPAddress, &serAddr.sin_addr);
//serAddr.sin_addr.S_un.S_addr = inet_addr(IPAddress);
if (connect(clientSock, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{ //連接失敗
closesocket(clientSock);
return false;
}
//2.關聯事件和套接字
if (0 != WSAEventSelect(clientSock, socketEvent, FD_READ | FD_CLOSE))
return false;
//創建線程
thread = CreateThread(0, 0, RecvThreadProc, (void *)this, 0, 0);
return true;
}
BOOL ClientBase::Stop()
{
//WSA類型的不僅可以檢測網絡事件信號 也可以檢測一般的事件 這裏給StopEvent信號 可以控制程序正常退出線程
SetEvent(stopEvent);
WaitForSingleObject(thread, INFINITE); //等待線程退出
RELEASE_SOCKET(clientSock);
WSACloseEvent(socketEvent);
RELEASE_HANDLE(stopEvent);
return true;
}
BOOL ClientBase::Send(const BYTE * buffer, int len)
{
if (SOCKET_ERROR == send(clientSock, (char*)buffer, len, 0))
{
return false;
}
return true;
}
BOOL ClientBase::HasStarted()
{
return 0;
}
DWORD ClientBase::RecvThreadProc(LPVOID lpParam)
{
if (lpParam == NULL)
return 0;
ClientBase *client = (ClientBase *)lpParam;
DWORD ret = 0;
int index = 0;
WSANETWORKEVENTS networkEvent;
HANDLE events[2];
events[0] = client->socketEvent; //網絡事件
events[1] = client->stopEvent; //線程退出事件
while (true)
{
ret = WSAWaitForMultipleEvents(2, events, FALSE, INFINITE, FALSE);
if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT) //超時或者失敗
continue;
index = ret - WSA_WAIT_EVENT_0; //相減可以獲得響應的事件對應的事件數組下標,來確定是哪個網絡事件
if (index == 0)
{
WSAEnumNetworkEvents(client->clientSock, events[0], &networkEvent);//根據這個結構體我們就可以判斷是否是 我們所關注的網絡事件已經發生了
if (networkEvent.lNetworkEvents & FD_READ) //這裏有讀FD_WRITE 和 寫 FD_READ 接收消息用FD_READ
{
if (networkEvent.iErrorCode[FD_READ_BIT != 0])
{
//Error
continue;
}
char *buff = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 4096);
ret = recv(client->clientSock, buff, 4096, 0);
if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
{
client->OnConnectionClosed();
break; //錯誤
}
client->OnRecvCompleted((BYTE*)buff, ret);
}
if (networkEvent.lNetworkEvents & FD_CLOSE)//斷開連接? 這裏應該是指服務器主動斷連
{
client->OnConnectionClosed();
break; //關閉
}
}
else //stopEvent 控制退出
{
client->OnConnectionClosed();
break;
}
}
return 1;
}
BOOL ClientBase::IsSocketAlive(SOCKET sock)
{
return 0;
}
-----調用過程
#include "ClientBase.h"
#include <stdio.h>
class Client : public ClientBase
{
public:
// 連接關閉
virtual void OnConnectionClosed()
{
printf(" Close\n");
}
// 連接上發生錯誤
virtual void OnConnectionError()
{
printf(" Error\n");
}
// 讀操作完成
virtual void OnRecvCompleted(BYTE* buffer, int len)
{
printf("recv[%d]:%s\n", len, (char*)buffer);
}
// 寫操作完成
virtual void OnSendCompleted()
{
printf("*Send success\n");
}
};
int main()
{
Client client;
if (!client.Start("127.0.0.1", 10240))
{
printf(" start error\n");
}
int i = 0;
while (true)
{
char buff[128];
//scanf_s("%s", &buff, 128);
sprintf_s(buff, 128, "第%d條Msg", i++);
Sleep(500);
client.Send((BYTE*)buff, strlen(buff)+1);
}
}