既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新
SOCKET s;
WSAEVENT hNetworkEvent;
WSANETWORKEVENT networkEvents;
if(0==WSAEnumNetworkEvents(h,hNetworkEvent,&networkEvents);
{
//发生FD_WRITE网络事件。
if(networkEvents.lNetworkEvents&FD_READ)
{
if(0==networkEvent.iErrorCode[FD_READ_BIT])
{
//接收数据。
}
else
{
//获取错误代码。
int nErrorCode=networkEvents.iErrorCode[FD_READ_BIT];
//处理错误。
}
}
}
本例演示利用WSAEventSelect模型开发一个服务器应用程序的步骤。
主要步骤:
程序开始时会创建监听套接字,利用WSAEventSelect函数为套接字注册FD_ACCEPT和FD_CLOSE事件,然后套接字进入监听状态。在while循环内,循环调用WSAWaitForMultipleEvents函数等待网络事件的发生,当网络事件发生时函数返回,并通过该函数的返回值得到发生网络事件的套接字。调用WSAEnumNetworkEvents函数检查在该套接字上到底发生什么网络事件。
如果发生FD_ACCEPT网络事件,则调用accept函数接受客户端连接。将该套接字加入套接字数组。创建事件对象并加入事件数组。事件对象数量加一。然后调用WSAEventSelect函数为该套接字关联事件对象,注册FD_READ,FD_WRITE和FD_CLOSE网络事件。如果发生FD_READ网络事件,则调用recv函数接收数据。
如果发生FD_WRITE网络事件,则调用send函数发送数据。
如果发生FD_CLOSE网络事件,将该套接字从套接字数组清除,同时将对应事件从事件数组删除。事件对象数量减一,并关闭该套接字。
在应用程序中,对发生的每种网络事件,都首先判断是否发生了网络错误。如果发生错误,则服务器退出。
步骤一:定义事件对象数组和套接字数组。
这两个数组的最大程度为WSA_MAXIMUM_WAIT_EVENTS。这两个数组的成员存在一一对应关系。
DWORD totalEvent;//事件对象数量。
WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];
SOCKETS socketArray[WSA_MAXIMUM_WAIT_EVENTS];
步骤二:创建套接字:
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2),&wsaData);
if((sListen==socket(AF_INET,SOCK_STREAM,0))
{
//创建失败。
}
步骤三:为监听套接字注册网络事件:
if(eventArray[totalEvent]=WSACreateEvent()==WSA_INVALID_EVENT)
{
//调用失败。
}
//为监听套接字注册FD_READ,和FD_CLOSE网络事件。
if(WSAEventSelect(sListen,eventArray[totalEvent],FD_ACCEPT|FD_CLOSE)==SOCKETS_ERROR)
{
//调用失败。
}
步骤四:开始监听:
sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_addr.S_addr=htons(INADDR_ANY);
addr.sin_port=htons(4000);
if(bind(sListen,(SOCKADDR*)&addr,sizeof(addr))==SOCKETS_ERROR)
{
//绑定失败。
}
if(!listen(sListen,10))
{
//监听失败。
}
步骤五:等待网络事件。
while(true)
{
if(dwIndex=WSAWaitForMultipleEvents(totalEvent,eventArray,
false,WSA_INFINITE,false)==
WSA_WAIT_FAILED)
{
//等待失败。
}
步骤六:获取发生的网络事件。
当网络事件发生时WSAWaitForMultipleEvents函数返回。调用WSAEnumNetworkEvents函数获取发生在套接字上的网络事件。
socketArray[dwIndex-WSA_WAIT_EVENT_0]为当前发生网络事件的套接字。
eventArray[dwIndex-WSA_WAIT_EVENT_0]为当前被投递网络事件的事件对象。
当函数返回时networkEvents变量中保存了网络事件的记录。同时事件对象的工作状态,由未触发态变为触发态。
WSANETWORKEVENTS networkEvents;
if(WSAEnumNetworkEvents(socketArray[dwIndex-WSA_WAIT_EVENT_0],eventArray[dwIndex-WSA_WAIT_EVENT_0],&networkEvents)==SOCKETS_ERROR)
{
//调用失败。
}
步骤七:判断是否是各网络事件发生。
WSAEnumNetworkEvents函数返回时,首先检查是否发生了FD_ACCEPT网络事件。如果该网络事件发生,则说明此时客户端的连接请求被等待。检查是否发生了网络错误,如果没有错误发生,则执行下面的步骤:
1:调用accept接受客户端请求。
2:判断当前套接字数量是否超过了最大值。如果超过则关闭该套接字。
3:将客户端套接字介入套接字数组。
4:创建套接字事件对象,并将该事件对象加入事件对象数组。
5:为该套接字注册FD_READ,FD_WRITE和FD_CLOSE网络事件。
6:事件对象数量加一,将该套接字加入管理客户端套接字链表中。
使用WSAEventSelect应该注意的问题。
1:如果在一个套接字上多次调用WSAEventSelect函数,那么最后一次函数调用将会取消前一次的调用效果。
2:一个套接字不要关联多个事件对象 。在一个套接字上为不同网络事件注册不同的事件对象是不可能的。一个套接字关联一个对象,当该对象被触发时,获得对应的套接字,然后调用WSAEnumNetworkEvents来获得发生在此套节字上的事件。
3:如果要取消事件对象与网络事件的关联,以及为套接字注册的网络事件。应用程序可以在调用WSAEventSelect时将lNetworkEvent设置为0。另外,调用closesocket关闭套接字时,也会取消这种关联和为套接字注册的网络事件。
4:调用accept接受的套接字与监听套接字具有同样的属性。
如:在创建监听套接字时为其设置感兴趣的网络事件为FD_ACCEPT和FD_CLOSE。那么accept返回的套接字同样具有这些属性。它与监听套接字感兴趣的网络事件相同且使用同一个事件对象。一般情况下,我们都会为新套接字重新调用WSAEventSelect。后面的代码中在accept后会新套接字调用WSAEventSelect函数就不足为奇了!!
5:接收FD_CLOSE网络事件时,错误代码指出套接字是从容关闭还是硬关闭。如果错误代码为0,则为从容关闭;若错误代码为WSAECONNRESET错误,则是硬关闭。当应用程序接收到该网络事件时,说明对方在该套接字上执行了shutdown或者是closesocket函数调用。
WSAEventSelect模型的优势和不足。
WSAEventSelect模型的优势是可以应用在一个非窗口的Windows sockets程序中,实现对多个套接字的管理。
不足是:每个WSAEventSelect模型最多只能管理64个套接字。当应用程序需要管理多于64个套接字时,就需要额外创建线程。由于该模型需要调用多个函数,这增加了开发的难度。
以下为详细代码:
#include<iostream>
#include<windows.h>
#include"winsock2.h"
SOCKET sListen;
#pragma comment(lib,"WS2_32.lib")
#define MAX_NUM_SOCKET 20
u_int totalEvent=0;
//构造事件对象数组和套接字数组。
WSAEVENT eventArray[MAX_NUM_SOCKET];
SOCKET socketArray[MAX_NUM_SOCKET];
bool InitSocket()
{
WSAData wsa;
WSAStartup(MAKEWORD(2,2),&wsa);
sListen=socket(AF_INET,SOCK_STREAM,0);
if(sListen==INVALID_SOCKET)
{
return false;
}
WSAEVENT hEvent=WSACreateEvent();
eventArray[totalEvent]=hEvent;
//可用事件加一。
totalEvent++;
int ret=WSAEventSelect(sListen,hEvent,FD_CLOSE|FD_ACCEPT);//监听套接字只能收到这两种消息。
if(!ret)
{
return false;
}
sockaddr_in addr;
addr.sin_addr.S_un=inet_addr("192.168.1.100");
addr.sin_family=AF_INET;
addr.sin_port=htons(4000);
ret=bind(sListen,(SOCKADDR*)&addr,sizeof(addr));
if(ret==SOCKET_ERROR)
{
return false;
}
ret=listen(sListen,10);
if(SOCKET_ERROR==ret)
{
return false;
}
return true;
}
int main(int argc,char**argv)
{
InitSocket();
while(true)
{
//有一个事件被触发等待函数即返回。
int dwIndex=WSAWaitForMultipleEvents(totalEvent,eventArray,false,WSA_INFINITE,false);
if(dwIndex==WSA_WAIT_FAILED)
{
break;
}
else
{
//有网络事件发生。
WSANETWORKEVENTS wsanetwork;
SOCKET s=socketArray[dwIndex-WSA_WAIT_EVENT_0];
//传hEventObject为被触发的套接字,WSAEnumNetworkEvents函数,会将其设置为非触发态。无需手工设置。
int ret=WSAEnumNetworkEvents(s,eventArray[dwIndex-WSA_WAIT_EVENT_0],&wsanetwork);
if(ret==SOCKET_ERROR)//函数调用失败。
{
break;
}
//发生FD_ACCEPT网络事件。
else if(wsanetwork.lNetworkEvents&FD_ACCEPT)
{
if(wsanetwork.iErrorCode[FD_ACCEPT_BIT]!=0)//发生网络错误。
{
break;
}
else //接受连接请求。
{
SOCKET sAccept;
if((sAccept=accept(socketArray[dwIndex-WSA_WAIT_EVENT_0],NULL,NULL))==INVALID_SOCKET)
{
break;
}
//超过最大值。
if(totalEvent>WSA_MAXIMUM_WAIT_EVENTS)
{
closesocket(sAccept);
break;
}
//将新接受的套接字加入套接字数组。
socketArray[totalEvent]=sAccept;
//创建套接字事件对象。
if((eventArray[totalEvent]=WSACreateEvent())==WSA_INVALID_EVENT)
{
break;
}
//为新接受的套接字重新注册网络事件,重新关联事件对象。不使用与监听套接字同样的属性,这点要注意!!!!。
if(WSAEventSelect(sAccept,eventArray[totalEvent],FD_READ|FD_WRITE|FD_CLOSE)==SOCKET_ERROR)//接受的套接字,用于收发数据。
{
break;
}
totalEvent++;//总数加一。
//将套接字加入链表。
}
}
//发生FD_CLOSE网络事件。
else if(wsanetwork.lNetworkEvents&FD_CLOSE)
{
if(wsanetwork.iErrorCode[FD_CLOSE_BIT]!=0)//发生网络错误。
{
break;
}
else //连接关闭。
{
//删除链表中的该套接字。
//关闭网络事件对象。
WSACloseEvent(eventArray[dwIndex-WSA_WAIT_EVENT_0]);
//将此套节字和事件对象从数组中清除。
for(int i=dwIndex-WSA_WAIT_EVENT_0;i<totalEvent-1;i++)
{
eventArray[i]=eventArray[i+1];
socketArray[i]=socketArray[i+1];
}
totalEvent--;//总数减一。
}
}
//发生FD_READ网络事件。
else if(wsanetwork.lNetworkEvents&FD_READ)
{
if(wsanetwork.iErrorCode[FD_READ_BIT]!=0)//发生网络错误。
![img](https://img-blog.csdnimg.cn/img_convert/d54a75674d237c5e434bff2667206389.png)
![img](https://img-blog.csdnimg.cn/img_convert/db36a44993667963e96fac807f7a3ddb.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**
闭。
{
//删除链表中的该套接字。
//关闭网络事件对象。
WSACloseEvent(eventArray[dwIndex-WSA_WAIT_EVENT_0]);
//将此套节字和事件对象从数组中清除。
for(int i=dwIndex-WSA_WAIT_EVENT_0;i<totalEvent-1;i++)
{
eventArray[i]=eventArray[i+1];
socketArray[i]=socketArray[i+1];
}
totalEvent--;//总数减一。
}
}
//发生FD_READ网络事件。
else if(wsanetwork.lNetworkEvents&FD_READ)
{
if(wsanetwork.iErrorCode[FD_READ_BIT]!=0)//发生网络错误。
[外链图片转存中...(img-HAoDdpOr-1715687795774)]
[外链图片转存中...(img-6fvY76Vq-1715687795774)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**