前言

 本文配套代码: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. Windows ftp脚本和RSCD agent自动安装脚本

    Windows ftp脚本 和bladelogic RSCD Agent自动安装脚本 比较简单的命令是msiexec /I "C:\RSCD85-SP1-WIN64.msi" /Q ...

  2. asp.net core 部署到服务器之后外网访问不了

    部署发现问题 今天在部署.net core的时候,发现访问http://localhost:xxxx可以,但是用外网访问并不行! 开始尝试解决问题 一开始以为是nginx的问题.各种折腾,各种改配置文 ...

  3. SQL Server错误严重性级别和异常处理

    关于SQL Server的错误严重性级别的说明,强烈认真看一下下面的两个链接 脱机帮助 ms-help://MS.SQLCC.v9/MS.SQLSVR.v9.zh-CHS/sqlerrm9/html/ ...

  4. 邪恶的PLS

    今天碰到一个存储过程编译错误,提示PLS-00103错误,关于这个错误网上能搜到一大把,原因很多,我碰到的错误提示如下: Compilation errors for PROCEDURE ETL.PR ...

  5. redis数据类型-散列类型

    Redis数据类型 散列类型 Redis是采用字典结构以键值对的形式存储数据的,而散列类型(hash)的键值也是一种字典结构,其存储了字段(field)和字段值的映射,但字段值只能是字符串,不支持其他 ...

  6. C# 类型基础(上)

    C#类型都派生自System.Object 祖先的优良传统:Object的公共方法 Equals: 对象的同一性而非相等性 GetHashCode:返回对象的值的哈希码 ToString:默认返回类型 ...

  7. 网页窗口logo图标设置

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

  8. 主备(keepalived+nginx)

    实验环境 系统: centos 6.9 mini 机器名   ip                                   虚拟ip kn1     192.168.126.10 kn2  ...

  9. vue项目中关于axios的简单使用

    axios介绍 Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中 官方仓库:https://github.com/axios/axios 中文文档:htt ...

  10. 浅谈计算机中的IO模型

    IO模型一共有5种: blocking IO #阻塞IO nonblocking IO #非阻塞IO IO myltiplexing #IO多路复用 signal driven IO #信号驱动IO ...