前言

 本文配套代码:https://github.com/TTGuoying/WSAEventSelect-model

  由于篇幅原因,本文假设你已经熟悉了利用Socket进行TCP/IP编程的基本原理,并且也熟练的掌握了多线程编程技术,太基本的概念我这里就略过不提了,网上的资料应该遍地都是。

  上一篇文章介绍的IOCP模型主要用于服务器,客户端的话一般用WSAEventSelect模型,下面介绍 WSAEventSelect 模型。

  由于网络编程中数据何时到来的不可预知,如果我们在线程中用recv()函数一直等待数据的到来会造成cpu的极大浪费,事件选择(WSAEventSelect)模型可以避免这个问题。事件选择(WSAEventSelect)模型原理是:

  WSAEventSelect模型是Windows Sockets提供的一个有用异步I/O模型。该模型允许在一个或者多个套接字上接收以事件为基础的网络事件通知。Windows Sockets应用程序在创建套接字后,调用WSAEventSelect()函数,将一个事件对象与网络事件集合关联在一起。当网络事件发生时,应用程序以事件的形式接收网络事件通知。
 
基本流程 
  1. 初始化网络环境,创建一个监听的socket,然后进行connect操作。接下来WSACreateEvent()创建一个网络事件对象,其声明如下:

    WSAEVENT WSACreateEvent(void); //返回一个手工重置的事件对象句柄
  2. 再调用WSAEventSelect,来将监听的socket与该事件进行一个关联,其声明如下:
    int WSAEventSelect(
    SOCKET s, //套接字
    WSAEVENT hEventObject, //网络事件对象
    long lNetworkEvents //需要关注的事件
    );

    我们客户端只关心FD_READ和FD_CLOSE操作,所以第三个参数传FD_READ | FD_CLOSE。

  3. 启动一个线程调用WSAWaitForMultipleEvents等待1中的event事件,其声明如下:
    DWORD WSAWaitForMultipleEvents(
    DWORD cEvents, //指定了事件对象数组里边的个数,最大值为64
    const WSAEVENT FAR *lphEvents, //事件对象数组
    BOOL fWaitAll, //等待类型,TRUE表示要数组里全部有信号才返回,FALSE表示至少有一个就返回,这里必须为FALSE
    DWORD dwTimeout, //等待的超时时间
    BOOL fAlertable //当系统的执行队列有I/O例程要执行时,是否返回,TRUE执行例程返回,FALSE不返回不执行,这里为FALSE
    );

    由于我们是客户端,所以只等待一个事件。

  4. 当事件发生,我们需要调用WSAEnumNetworkEvents,来检测指定的socket上的网络事件。其声明如下:
    int WSAEnumNetworkEvents
    (
    SOCKET s, //指定的socket
    WSAEVENT hEventObject, //事件对象
    LPWSANETWORKEVENTS lpNetworkEvents //WSANETWORKEVENTS<span style="font-family:Arial, Helvetica, sans-serif;">结构地址</span>
    );

    当我们调用这个函数成功后,它会将我们指定的socket和事件对象所关联的网络事件的信息保存到WSANETWORKEVENTS这个结构体里边去,我们来看下这个结构体的声明:

    typedef struct _WSANETWORKEVENTS {
    long lNetworkEvents;<span style="white-space:pre"> </span>//指定了哪个已经发生的网络事件
    int iErrorCodes[FD_MAX_EVENTS];<span style="white-space:pre"> </span>//错误码
    } WSANETWORKEVENTS, *LPWSANETWORKEVENTS;

    根据这个结构体我们就可以判断是否是我们所关注的网络事件已经发生了。如果是我们的读的网络事件发生了,那么我们就调用recv函数进行操作。若是关闭的事件发生了,就调用closesocket将socket关掉,在数组里将其置零等操作。

  整个模型的流程图如下:

实现(配合IOCP服务器类测试更佳)

 #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() = ;
// 连接上发生错误
virtual void OnConnectionError() = ;
// 读操作完成
virtual void OnRecvCompleted(BYTE* buffer, int len) = ;
// 写操作完成
virtual void OnSendCompleted() = ; private:
// 接收线程函数
static DWORD WINAPI RecvThreadProc(LPVOID lpParam);
// socket是否存活
BOOL IsSocketAlive(SOCKET sock);
SOCKET clientSock;
WSAEVENT socketEvent;
HANDLE stopEvent;
HANDLE thread;
};
 #include "ClientBase.h"
#include <WS2tcpip.h> #pragma comment(lib, "WS2_32.lib") ClientBase::ClientBase()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(, ), &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();
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;
}
if ( != WSAEventSelect(clientSock, socketEvent, FD_READ | FD_CLOSE))
return false; thread = CreateThread(, , RecvThreadProc, (void *)this, , );
return true;
} BOOL ClientBase::Stop()
{
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, ))
{
return false;
}
return true;
} BOOL ClientBase::HasStarted()
{
return ;
} DWORD ClientBase::RecvThreadProc(LPVOID lpParam)
{
if (lpParam == NULL)
return ; ClientBase *client = (ClientBase *)lpParam;
DWORD ret = ;
int index = ;
WSANETWORKEVENTS networkEvent;
HANDLE events[];
events[] = client->socketEvent;
events[] = client->stopEvent; while (true)
{
ret = WSAWaitForMultipleEvents(, events, FALSE, INFINITE, FALSE);
if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
continue;
index = ret - WSA_WAIT_EVENT_0;
if (index == )
{
WSAEnumNetworkEvents(client->clientSock, events[], &networkEvent);
if (networkEvent.lNetworkEvents & FD_READ)
{
if (networkEvent.iErrorCode[FD_READ_BIT != ])
{
//Error
continue;
}
char *buff = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, );
ret = recv(client->clientSock, buff, , );
if (ret == || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
{
client->OnConnectionClosed();
break; //错误
}
client->OnRecvCompleted((BYTE*)buff, ret);
}
if (networkEvent.lNetworkEvents & FD_CLOSE)
{ client->OnConnectionClosed();
break; //关闭
}
}
else
{
client->OnConnectionClosed();
break; // 退出
} }
return ;
} BOOL ClientBase::IsSocketAlive(SOCKET sock)
{
return ;
}
 #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", ))
{
printf(" start error\n");
} int i = ;
while (true)
{
char buff[];
//scanf_s("%s", &buff, 128); sprintf_s(buff, , "第%d条Msg", i++);
Sleep();
client.Send((BYTE*)buff, strlen(buff)+);
}
}
 

WinSock WSAEventSelect 模型总结的更多相关文章

  1. WinSock WSAEventSelect 模型

    在前面我们说了WSAAsyncSelect 模型,它相比于select模型来说提供了这样一种机制:当发生对应的IO通知时会立即通知操作系统,并调用对应的处理函数,它解决了调用send和 recv的时机 ...

  2. winsock编程WSAEventSelect模型

    winsock编程WSAEventSelect模型 WSAEventSelect模型和WSAAsyncSelec模型类似,都是用调用WSAXXXXXSelec函数将socket和事件关联并注册到系统, ...

  3. WSAEventSelect模型详解

    WSAEventSelect 是 WinSock 提供的一种异步事件通知I/O模型,与 WSAAsyncSelect模型有些类似.       该模型同样是接收 FD_XXX 之类的网络事件,但是是通 ...

  4. WinSock IOCP 模型总结(附一个带缓存池的IOCP类)

    前言 本文配套代码:https://github.com/TTGuoying/IOCPServer 由于篇幅原因,本文假设你已经熟悉了利用Socket进行TCP/IP编程的基本原理,并且也熟练的掌握了 ...

  5. WSAEventSelect模型编程 详解

    转自:http://blog.csdn.net/wangjieest/article/details/7042108 WSAEventSelect模型编程 WSAEventSelect模型编程这个模型 ...

  6. WSAEventSelect模型

    WSAEventSelect模型 EventSelect WSAEventSelect function The WSAEventSelect function specifies an event ...

  7. WinSock IO模型 -- WSAEventSelect模型事件触发条件说明

    FD_READ事件 l  调用WSAEventSelect函数时,如果当前有数据可读 l  有数据到达时,并且没有发送过FD_READ事件 l  调用recv/recvfrom函数后,仍然有数据可读时 ...

  8. Winsock IO模型之select模型

    之所以称其为select模型是因为它主要是使用select函数来管理I/O的.这个模型的设计源于UNIX系统,目的是允许那些想要避免在套接字调用上阻塞的应用程序有能力管理多个套接字. int sele ...

  9. 三.Windows I/O模型之事件选择(WSAEventSelect )模型

    1.事件选择模型:和异步选择模型类似的是,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知.对于异步选择模型采用的网络事件来说,它们均可原封不动地移植到事件选择模型.事件选择模型和 ...

随机推荐

  1. 一、Html简介

    html什么东西呢? hypertext markup language(超文本标记语言).超级文本标记语言是标准通用标记语言下的一个应用,也是一种规范,一种标准, 它通过标记符号来标记要显示的网页中 ...

  2. linkin大话面向对象--枚举

    枚举类(enum) 其实我们使用到枚举的地方还是很多的,其实我们可以完全人工的来实现枚举的功能.比如说我现在手里的项目我就是自己实现的枚举,说白了,枚举就是一个类的多例模式. 1,使用enum声明,默 ...

  3. 网页窗口logo图标设置

    网站上的logo实际上是一个“**.ico”图片,比如说favicon.ico.实现步骤:第一步:制作favicon.ico,大小一般为16*16毫米(ico图片制作网址http://www.ico. ...

  4. 极其蛋疼的if else 中的break用法

    主要原因是if不是循环语句 像这样的: while(...) { ==res) { break; } printf("A"); } 跳出的就是while循环.而不是if判断语句 补 ...

  5. Ajax异步信息抓取方式

    淘女郎模特信息抓取教程 源码地址: cnsimo/mmtao 网址:https://0x9.me/xrh6z   判断一个页面是不是Ajax加载的方法: 查看网页源代码,查找网页中加载的数据信息,如果 ...

  6. (三)surging 微服务框架使用系列之我的第一个服务(审计日志)

    前言:前面准备了那么久的准备工作,现在终于可以开始构建我们自己的服务了.这篇博客就让我们一起构建自己的第一个服务---审计日志. 首先我们先创建两个项目,一个控制台的服务启动项目,一个业务的实现项目. ...

  7. Python CRM项目三

    1.分页: 分页使用Django内置的分页模块来实现 官方的分页案例 from django.core.paginator import Paginator, EmptyPage, PageNotAn ...

  8. gulp最简示例

    全局安装 $ npm gulp -g 作为项目的开发依赖安装 $ npm gulp --save-dev 脚本文件 在根目录创建gulpfile.js文件 const gulp = require(' ...

  9. python字符串常用内置方法

    python字符串常用内置方法 定义: 字符串是一个有序的字符的集合,用与存储和表示基本的文本信息. python中引号中间包含的就是字符串. # s1='hello world' # s2=&quo ...

  10. notify丢失、虚假唤醒

    notify丢失: 假设线程A因为某种条件在条件队列中等待,同时线程B因为另外一种条件在同一个条件队列中等待,也就是说线程A/B都被同一个Object.wait()挂起,但是等待的条件不同. 现在假设 ...