立即学习:https://edu.csdn.net/course/play/6082/113758?utm_source=blogtoedu
WSAEventSelect模型
WSAEventSelect模型是一个不主动去轮询所以客户端套接字是否有数据到来的模型,客户端有数据到来时,系统发送事件通知我们的程序,这就解决了WSAAysncSelect模型只能用在windows窗口程序的限制。
WSAEventSelect模型,流程如下
1、创建一个事件对象数组,用于存放所有的事件对象
2、创建一个事件对象(WSACreateEvent)
3、将一组感兴趣的SOCKET事件与事件对象关联(WSAEventSelect),然后加入事件对象数组。
4、等待事件对象发生有一个你感兴趣的网络事件
5、对发生事件的事件对象查询具体发生的事件类型(WSAEnumNetworkEvents)
6、针对不同的事件类型进行不同的处理。
WSACreateEvent函数:
WSAEVENT WSACreateEvent(void)
调用WSACreateEveent函数创建一个事件对象
返回值:成功返回事件对象的句柄,失败返回WSA_INVALID_EVENT ,通过WSAGetLastError(),获取错误信息
WSAEventSelect函数
int WSAEventSelect(SOCKET s,WSAEVENT hEvent,Long INetworkEvents);
参数:
第一个参数:套接字句柄
第二个参数:为事件对象句柄
第三个参数:应用程序感兴趣的网络事件集合,应用程序为套接字注册网络事件成功
返回值:
成功返回0 , 失败返回SOCKET_ERROR,通过WSAGetLastError获取错误信息
WSAWaitForMultipleEvents函数
DWORD WSAWatiForMultipleEvent(DWORD cEvent,const WSAEVENT *plhEvent,BOOL fWaitAll,DWORD dwTimeOut,BOOL fAltertable)
参数:
第一个参数:事件句柄个数,至少为1,最多64个。
第二个参数:为指向对象句柄数组指针
第三个参数:如果为TRUE,则该函数在所有事件对象都转为已触发时才返回,如为false,只要有一个对象呗触发,函数即返回。
第四个参数:单位为毫秒,在事件用完后函数会返回,如果为WSA_INFINITE,则函数会一直等待下去,如果超时返回,函数返回WSA_WAIT_TIMEOUT
第五个参数:参数说明当完成例程在系统队列中排队等待执行时,该函数是否返回,这主要应用于重叠IO模型,在完成例程中会用到这个参数,这里我们先设置为false
当fWaitAll为true时:
返回WSA_TIMEOUT 则表明等待超时
返回WSA_EVENT_0 表明所有对象都已变成触发态,等待成功
返回WAIT_IO_COMPLETION说明一个或多个完成例程已经排队等待执行
如果fWaitAll为false时;
WSA_WAIT_EVENT_0到WSA_WAIT_EVENT_0+cEvent-1范围的值,说明有一个对象变为触发态,他在数组为:返回值 WSA_WAIT_EVENT_0
如果函数调用失败:返回WSA_WAIT-FAILED
WSAEnumNetwordEvent函数:监测所指定的套接字上网络事件的发生
int WSAEnumNetworkEvents(_In_ SOCKET s , _In_ WSAEVENT hEventObject,_Out_ LPWSANETWORDEVENTS lpNetwordEvents);
参数:
第一个参数:发生事件的socket
第二个参数:发生事件的事件对象
第三个参数:发生的网络事件
返回值:
成功返回0 , 失败返回SOCKET_ERROR错误,通过WSAGetLastError()来获取错误代码
typedef struct _WSANETWORKEVENTS
{
long INetworkEvent;发生的网络事件类型
int iErrorCode[FD_MAX_EVENTS] 网络事件错误代码
} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;
比如当发生FD_READ 事件时,那么INetworkEvent & FD_READ 将为真,同事 iErrorCode[FD_READ_BIT] 表明了此时的错误代码,为0则没有错误。
WSAResetEvent函数
bool WSAResetEvent(WSAEVENT hEvent)
当网络事件到来时,与套接字关联的事件对象由未触发变为出发状态,由于它是手工重置事件,应用程序需要手动将事件的状态设置为为触发态
返回值:
成功返回真TRUE
失败返回FALSE,可调用WSAGetLastError来获取错误信息。
WSACloseEvent函数:
bool WSACloseEvent(WSAEVENT hEvent)
不再使用对象时要将其关闭
返回值:
成功返回TRUE
失败返回FALSE ,可调用WSAGetLastError来获取错误信息。
服务端代码:
#include <iostream>
#include<WinSock2.h>
#include<cstdlib>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
WSADATA wd;
if (WSAStartup(MAKEWORD(2, 2), &wd) != 0)
{
cout << "wsastartup error " << WSAGetLastError() << endl;
exit(EXIT_FAILURE);
}
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s == INVALID_SOCKET)
{
cout << "socket error " << WSAGetLastError() << endl;
exit(EXIT_FAILURE);
}
sockaddr_in addr;
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addr.sin_family = AF_INET;
addr.sin_port = htons(8000);
int bindRet = bind(s, (sockaddr*)&addr, sizeof sockaddr);
if (bindRet == SOCKET_ERROR)
{
cout << "bind error " << WSAGetLastError() << endl;
exit(EXIT_FAILURE);
}
int listenRet = listen(s, 0);
if (listenRet == SOCKET_ERROR)
{
cout << "listen error " << WSAGetLastError() << endl;
exit(EXIT_FAILURE);
}
//定义事件数组
WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];
//定义套接字数组
SOCKET socketArray[WSA_MAXIMUM_WAIT_EVENTS];
int N = 0;
//创建事件对象
WSAEVENT event = WSACreateEvent();
//将套接字和事件对象以及事件的类型通过WSAEventSelect方法“绑定”
WSAEventSelect(s, event, FD_ACCEPT | FD_CLOSE);
socketArray[N] = s;
eventArray[N++] = event;
while (true)
{
//线程将在这里阻塞,直到有一个socket触发事件
int ret = WSAWaitForMultipleEvents(N, eventArray, false, WSA_INFINITE, false);
if (ret == WSA_WAIT_FAILED)
{
cout << "WSAWaitFroMultipleEvents error " << WSAGetLastError() << endl;
continue;
}
int index = ret - WSA_WAIT_EVENT_0;
cout << "WSA_WAIT_EVENT_O" << WSA_WAIT_EVENT_0 << endl;
WSANETWORKEVENTS netEvents;
SOCKET sock = socketArray[index];
//枚举出网络事件的具体类型
WSAEnumNetworkEvents(sock, eventArray[index], &netEvents);
if (netEvents.lNetworkEvents & FD_ACCEPT)
{
if (netEvents.iErrorCode[FD_ACCEPT_BIT] == 0)
{
if (N >= WSA_MAXIMUM_WAIT_EVENTS)
{
cout << "事件对象数组已满" << endl;
continue;
}
SOCKET client = accept(sock, NULL, NULL);
if (client != INVALID_SOCKET)
{
cout << client << "进入聊天室" << endl;
char temp[64];
sprintf_s(temp, "欢迎%d进入聊天室", client);
send(client, temp, strlen(temp), 0);
WSAEVENT e = WSACreateEvent();
WSAEventSelect(client, e, FD_READ | FD_WRITE | FD_CLOSE);
socketArray[N] = client;
eventArray[N++] = e;
}
}
}
else if (netEvents.lNetworkEvents & FD_READ)
{
if (netEvents.iErrorCode[FD_READ_BIT] == 0)
{
char buf[64];
ZeroMemory(buf, 64);
int ret = recv(sock, buf, 64, 0);
if (ret > 0)
{
cout << sock << "说:" << buf << endl;
}
}
}
else if (netEvents.lNetworkEvents & FD_CLOSE)
{
WSACloseEvent(eventArray[index]);
closesocket(sock);
cout << sock << "离开了" << endl;
for (int j = index; j < N - 1; j++)
{
eventArray[j] = eventArray[j + 1];
socketArray[j] = socketArray[j + 1];
}
N--;
}
}
WSACleanup();
}
客户端代码
#include <iostream>
#include<WinSock2.h>
#include<cstdlib>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
WSADATA wd;
if (WSAStartup(MAKEWORD(2, 2), &wd) != 0)
{
cout << "wsastartup error " << WSAGetLastError() << endl;
exit(EXIT_FAILURE);
}
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s == INVALID_SOCKET)
{
cout << "socket error " << WSAGetLastError() << endl;
exit(EXIT_FAILURE);
}
sockaddr_in addr;
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(8000);
addr.sin_family = AF_INET;
int connRet = connect(s, (sockaddr*)&addr, sizeof sockaddr_in);
if (connRet == SOCKET_ERROR)
{
cout << "connect error " << WSAGetLastError() << endl;
exit(EXIT_FAILURE);
}
int ret;
char str[100];
while (true)
{
memset(str, 0, 100);
cin >> str;
ret = send(s, str, strlen(str), 0);
if (ret == SOCKET_ERROR)
{
cout << "recv error " << WSAGetLastError() << endl;
break;
}
}
WSACleanup();
return 0;
}