前言

 本文配套代码: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. awk取每行最大值

    需求 有一个数字文本,每行都是数字,以空格分开:现在需要将每行中最大值取出来 文本如下: [root@localhost ~]#cat urfile 1 1 2 1 2 1 1 3 1 使用awk解决 ...

  2. Git远程管理[五]

    标签(linux): git 笔者Q:972581034 交流群:605799367.有任何疑问可与笔者或加群交流 相关命令 git clone https://github.com/guohongz ...

  3. Electron 打包Mac安装包代码签名问题解决方案Could not get code signature for running application

    最近一直在做electron应用的打包,集成mac版本的自动更新时出现了问题. Error: Could not get code signature for running application ...

  4. rabbitmq配置文件和站点管理(二)

    前面介绍了erlang环境的安装和rabbitmq环境安装,接下来对rabbitmq详细配置和管理: 启用后台管理插件 创建目录 mkdir /etc/rabbitmq 启用插件 rabbitmq-p ...

  5. LINUX改变文件大小

    body, table{font-family: 微软雅黑; font-size: 10pt} table{border-collapse: collapse; border: solid gray; ...

  6. JVM基础篇(一)

    JVM简介 JVM(Java虚拟机)是一个虚拟的机器,在实际的计算机上通过软件模拟来实现.JVM有自己的硬件,如处理器.堆栈.寄存器等,还具有相应的指令系统. JVM包括一套字节码指令集.一组寄存器. ...

  7. Timer类的schedule和scheduleAtFixedRate 简单应用

    Timer类可以用作定时任务,主要的方法有schedule和scheduleAtFixedRate. schedule(TimerTask task, Date time) 安排在指定的时间执行指定的 ...

  8. 51NOD 1238 最小公倍数之和 V3 [杜教筛]

    1238 最小公倍数之和 V3 三种做法!!! 见学习笔记,这里只贴代码 #include <iostream> #include <cstdio> #include < ...

  9. E 洛谷 P3598 Koishi Loves Number Theory[数论]

    题目描述 Koishi十分喜欢数论. 她的朋友Flandre为了检测她和数论是不是真爱,给了她一个问题. 已知 给定和个数,求对取模. 按照套路,呆萌的Koishi当然假装不会做了,于是她来向你请教这 ...

  10. BZOJ 2752: [HAOI2012]高速公路(road) [线段树 期望]

    2752: [HAOI2012]高速公路(road) Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 1219  Solved: 446[Submit] ...