WSAEventSelect模型 记录分享

WSAEventSelect模型也称异步事件选择模型 是Windows Sockets提供的一个有用异步I/O模型。该模型允许在一个或者多个套接字上接收以事件为基础的网络事件通知。Windows Sockets应用程序在创建套接字后,调用WSAEventSelect()函数,将一个事件对象与网络事件集合关联在一起。当网络事件发生时,应用程序以事件的形式接收网络事件通知。

优点相比 accept阻塞 ,虽然WSAWaitForMultipleEvents也是阻塞的,但是可以等待多种不同类型事件,可以是recv 可以是accept 可以是其他自定义事件的业务操作等等。

初始化网络环境,创建一个监听的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];      //错误码  
} WSANETWORKEVENTS, *LPWSANETWORKEVENTS; 
根据这个结构体我们就可以判断是否是我们所关注的网络事件已经发生了。如果是我们的读的网络事件发生了,那么我们就调用recv函数进行操作。若是关闭的事件发生了,就调用closesocket将socket关掉,在数组里将其置零等操作。

832024-20180304231122015-1681972784.pnguploading.4e448015.gif正在上传…重新上传取消

 

下面是示例代码(客户端代码、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);
     }
 }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值