Windows socket之WSAEventSelect模型_wsaeventselect模型的socket编程和客户端应用的功能(1)

收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
img
img

如果你需要这些资料,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

如果hEvent不为NULL,则该事件被重置。如果为NULL,需要调用WSAResetEvent函数设置事件为非触发状态。

该结构中包含发生网络事件的记录和相关错误代码。

调用成功返回0,否则为SOCKETS_ERROR。

WSANETWORKEVENTS结构如下:

typedef struct _WSANETWORKEVENTS



{



   long lNetworkEvents,



   int iErrorCode[FD_MAX_EVENTS];



 }WSANETWORKEVENTS,*LPWSANETWORKEVENTS;




lNetworkEvents指示发生的网络事件。一个对象再变为触发态时,可能在套接字上发生了多个网络事件。

iErrorCode为包含网络事件错误代码的数组。错误代码与lNetworkEvents字段中的网络事件对应。

在应用程序中,使用网络事件事件错误标识符对iErrorCode数组进行索引,检查是否发生了网络错误。这些标识符的命名规则是对应的网络事件后面添加_BIT.例如,对于FD_READ事件的网络事件错误标识符为FD_READ_BIT。

下面的代码演示了,如何判断FD_READ网络事件的发生:

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;

					}

					//为新接受的套接字重新注册网络事件,重新关联事件对象。不使用与监听套接字同样的属性,这点要注意!!!!。



![img](https://img-blog.csdnimg.cn/img_convert/a2137bf3be3dadd04c0ae1dfd5460416.png)
![img](https://img-blog.csdnimg.cn/img_convert/9ca1673ee66564e26be0e29f7ab28d2a.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**

ocketArray[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;

					}

					//为新接受的套接字重新注册网络事件,重新关联事件对象。不使用与监听套接字同样的属性,这点要注意!!!!。



[外链图片转存中...(img-J78vpaOz-1715687757192)]
[外链图片转存中...(img-M9EH1Uwj-1715687757193)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**

  • 16
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值