WinSock WSAEventSelect 模型总结
前言
本文配套代码:https://github.com/TTGuoying/WSAEventSelect-model
由于篇幅原因,本文假设你已经熟悉了利用Socket进行TCP/IP编程的基本原理,并且也熟练的掌握了多线程编程技术,太基本的概念我这里就略过不提了,网上的资料应该遍地都是。
上一篇文章介绍的IOCP模型主要用于服务器,客户端的话一般用WSAEventSelect模型,下面介绍 WSAEventSelect 模型。
由于网络编程中数据何时到来的不可预知,如果我们在线程中用recv()函数一直等待数据的到来会造成cpu的极大浪费,事件选择(WSAEventSelect)模型可以避免这个问题。事件选择(WSAEventSelect)模型原理是:
- 初始化网络环境,创建一个监听的socket,然后进行connect操作。接下来WSACreateEvent()创建一个网络事件对象,其声明如下:
WSAEVENT WSACreateEvent(void); //返回一个手工重置的事件对象句柄
- 再调用WSAEventSelect,来将监听的socket与该事件进行一个关联,其声明如下:
int WSAEventSelect(
SOCKET s, //套接字
WSAEVENT hEventObject, //网络事件对象
long lNetworkEvents //需要关注的事件
);我们客户端只关心FD_READ和FD_CLOSE操作,所以第三个参数传FD_READ | FD_CLOSE。
- 启动一个线程调用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
);由于我们是客户端,所以只等待一个事件。
- 当事件发生,我们需要调用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 模型总结的更多相关文章
- WinSock WSAEventSelect 模型
在前面我们说了WSAAsyncSelect 模型,它相比于select模型来说提供了这样一种机制:当发生对应的IO通知时会立即通知操作系统,并调用对应的处理函数,它解决了调用send和 recv的时机 ...
- winsock编程WSAEventSelect模型
winsock编程WSAEventSelect模型 WSAEventSelect模型和WSAAsyncSelec模型类似,都是用调用WSAXXXXXSelec函数将socket和事件关联并注册到系统, ...
- WSAEventSelect模型详解
WSAEventSelect 是 WinSock 提供的一种异步事件通知I/O模型,与 WSAAsyncSelect模型有些类似. 该模型同样是接收 FD_XXX 之类的网络事件,但是是通 ...
- WinSock IOCP 模型总结(附一个带缓存池的IOCP类)
前言 本文配套代码:https://github.com/TTGuoying/IOCPServer 由于篇幅原因,本文假设你已经熟悉了利用Socket进行TCP/IP编程的基本原理,并且也熟练的掌握了 ...
- WSAEventSelect模型编程 详解
转自:http://blog.csdn.net/wangjieest/article/details/7042108 WSAEventSelect模型编程 WSAEventSelect模型编程这个模型 ...
- WSAEventSelect模型
WSAEventSelect模型 EventSelect WSAEventSelect function The WSAEventSelect function specifies an event ...
- WinSock IO模型 -- WSAEventSelect模型事件触发条件说明
FD_READ事件 l 调用WSAEventSelect函数时,如果当前有数据可读 l 有数据到达时,并且没有发送过FD_READ事件 l 调用recv/recvfrom函数后,仍然有数据可读时 ...
- Winsock IO模型之select模型
之所以称其为select模型是因为它主要是使用select函数来管理I/O的.这个模型的设计源于UNIX系统,目的是允许那些想要避免在套接字调用上阻塞的应用程序有能力管理多个套接字. int sele ...
- 三.Windows I/O模型之事件选择(WSAEventSelect )模型
1.事件选择模型:和异步选择模型类似的是,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知.对于异步选择模型采用的网络事件来说,它们均可原封不动地移植到事件选择模型.事件选择模型和 ...
随机推荐
- 错误:Unsupported major.minor version 51.0的解决
问题: 在电脑上双击打开一个可执行的jar时报错:Unsupported major.minor version 51.0.原因是版本问题,该jar使用jdk1.7编译,而我电脑装的是jdk1.6. ...
- fread和fwrite的使用
fread和fwrite的使用 fread和fwrite一般用于二进制文件的输入/输出,要不然你打开fwrite写入的文件就是乱码. 1.fread和fwrite函数 数据块I/O fread与fwr ...
- Redis进阶实践之十 Redis主从复制的集群模式
一.引言 Redis的基本数据类型,高级特性,与Lua脚本的整合等相关知识点都学完了,说是学完了,只是完成了当前的学习计划,在以后的时间还需继续深入研究和学习.从今天开始来讲一下有关Re ...
- JavaBean,List,Map,json格式之间转化方式
public class TestBean { private String id; private String name; private String password; public Stri ...
- selenium模拟浏览器对搜狗微信文章进行爬取
在上一篇博客中使用redis所维护的代理池抓取微信文章,开始运行良好,之后运行时总是会报501错误,我用浏览器打开网页又能正常打开,调试了好多次都还是会出错,既然这种方法出错,那就用selenium模 ...
- ansible playbook实践(四)-如何调试写好的playbook文件
有时,我们写了一个长长,功能很强悍的yaml文件,但是,我们有可能会担心,写的yaml文件是否正确,是否有漏洞危机,毕竟是要修改线上的机器,那么,有可能我们可以从以下几个检查维度来进行,确保在大规模应 ...
- java 世界中Annotation
java 世界中Annotation 在github上开始汇总一些自己学习,收集,总结,经验的一些信息,有利于跟踪,修改,提升.如果你感兴趣 可以关注一下,也可以提供自己的内容进来. https:// ...
- Hadoop学习笔记四
一.fsimage,edits和datanode的block在本地文件系统中位置的配置 fsimage:hdfs-site.xml中的dfs.namenode.name.dir 值例如file:// ...
- 夏令营讲课内容整理 Day 7.
Day7是夏令营的最后一天,这一天主要讲了骗分技巧和往年经典的一些NOIP试题以及比赛策略. 这天有个小插曲,上午的day7T3是一道和树有关的题,我是想破脑袋也想不出来,正解写不出来就写暴力吧,暴力 ...
- Xposed快速hook关键点
0x01 导读 工作中,常常需要针对各种app进行快速代码定位,找到代码修改和调试各种功能,就不得不面对xposed来进行一系列快速定位关键代码的问题了.那么问题来了,如何快速找到代码呢? 这里值这针 ...