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.事件选择模型:和异步选择模型类似的是,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知.对于异步选择模型采用的网络事件来说,它们均可原封不动地移植到事件选择模型.事件选择模型和 ...
随机推荐
- junit断言总结
我们平时编写自己的测试类,如果没有断言,那么就没写测试的必要了. JUnit框架用一组assert方法封装了最常见的测试任务.这些assert方法可以极大地简化单元测试的编写. Assert类包含了一 ...
- SQL模板资源管理器,你用了吗?
SQL Server Management Studio 有个模板资源管理器,不知你用过没有?使用模板创建脚本.自定义模板等功能能大大提高你的工作效率,如果没有尝试过,赶紧去试试吧.很多时候,我们习惯 ...
- 用户 'IIS APPPOOL\.NET v4.5 Classic' 登录失败。
我在win8.1系统下用vs2013+SqlServer08编写完项目后,挪到另一台win8.1系统(安装了Vs2010+SqlServer08)中,把网址挂到IIs中时,出现如下错误 : 解决方案: ...
- 洛谷 [P2764]最小路径覆盖问题
二分图应用模版 #include <iostream> #include <cstdio> #include <algorithm> #include <cs ...
- 夏令营讲课内容整理 Day 6 Part 3.
第三部分主要讲的是倍增思想及其应用. 在Day3的整理中,我简要提到了倍增思想,我们来回顾一下. 倍增是根据已经得到的信息,将考虑的范围扩大一倍,从而加速操作的一种思想,它在变化规则相同的情况下,加速 ...
- BZOJ 2419: 电阻 [高斯消元 物理]
http://www.lydsy.com/JudgeOnline/problem.php?id=2419 题意: n个点m个电阻构成一张图,求1到n的等效电阻 第一节课看一道题弃疗,于是来做这道物理题 ...
- getHibernateTemplate() VS getSession()
如题所示,对于这个问题,官网文档已给出答案,详见: /** * Obtain a Hibernate Session, either from the current transaction or * ...
- LeetCode - 540. Single Element in a Sorted Array
Given a sorted array consisting of only integers where every element appears twice except for one el ...
- mysql 密码过期问题
问题描述: Your password has expired. To log in you must change it using a client that supports expired p ...
- MySQL创建带有编码的数据库
mysql> create database chao default character set utf8 collate utf8_general_ci;