WSAEventSelect
WSAEventSelect 是 WinSock 提供的一种异步事件通知I/O模型,与 WSAAsyncSelect模型有些类似。
该模型同样是接收 FD_XXX 之类的网络事件,但是是通过事件对象句柄通知,而非像 WSAAsyncSelect一样依靠Windows的消息驱动机制。
与WSAAsyncSelect模型相同,WSAEventSelect将所有的SOCKET事件分为如下类型:(共十种)
FD_READ , FD_WRITE , FD_OOB , FD_ACCEPT, FD_CONNECT , FD_CLOSE,
FD_QOS , FD_GROUP_QOS , FD_ROUTING_INTERFACE_CHANGE , FD_ADDRESS_LIST_CHANGE
还有一个 FD_ALL_EVENTS 代表所有的事件
其中 FD_READ 的定义如下:
#define FD_READ_BIT 0
#define FD_READ (1 << FD_READ_BIT) // = 1
其他的定义也都类似,比如: FD_ACCEPT_BIT = 3
但是并不是每一种SOCKET都能发生所有的事件,比如监听SOCKET只能发生 FD_ACCEPT 和 FD_CLOSE 事件。
在WSAEventSelect模型中,基本流程如下:
1. 创建一个事件对象数组,用于存放所有的事件对象;
2. 创建一个事件对象(WSACreateEvent);
3. 将一组你感兴趣的SOCKET事件与事件对象关联(WSAEventSelect),然后加入事件对象数组;
4. 等待事件对象数组上发生一个你感兴趣的网络事件(WSAWaitForMultipleEvents);
5. 对发生事件的事件对象查询具体发生的事件类型(WSAEnumNetworkEvents);
6. 针对不同的事件类型进行不同的处理;
7. 循环进行 .4
对于TCP服务端程序而言,在创建一个监听SOCKET,绑定至某个端口然后监听后,可以创建一个事件对象然后与 FD_ACCEPT 和 FD_CLOSE 事件
关联。在第6步时对于 FD_ACCEPT 事件可以将accept得到的SOCKET关联 FD_WRITE,FD_READ,FD_CLOSE事件后加入事件对象数组。
WSAEVENT WSACreateEvent(void);
创建一个 事件对象,实际上 WSAEVENT就是一个 HANDLE
int WSAEventSelect(
_In_ SOCKET s, // 需要关联的SOCKET
_In_ WSAEVENT hEventObject, // 需要关联的事件对象
_In_ long lNetworkEvents // 感兴趣的网络事件,不同的事件可以用 | 合并, FD_ALL_EVENTS 代表所有的事件
);
函数执行成功将返回 0 ,否则返回 SOCKET_ERROR, 可调用WSAGetLastError() 查看具体的错误代码
DWORD WSAWaitForMultipleEvents(
_In_ DWORD cEvents, // 事件对象数组的数量
_In_ const WSAEVENT *lphEvents, // 事件对象数组
_In_ BOOL fWaitAll, // 是否等待所有的事件对象受信,显然一般情况下是false
_In_ DWORD dwTimeout, // 超时时限,单位是毫秒,WSA_INFINITE 为无穷大
_In_ BOOL fAlertable // 该模型下忽略,应该设置为false
);
如果执行失败返回 WSA_WAIT_IO_COMPLETION ; 如果是超时,则返回 WSA_WAIT_TIMEOUT
如果 函数执行成功将会返回一个值,分布在 区间 [ WSA_WAIT_EVENT_0 ,(WSA_WAIT_EVENT_0+cEvents-1) ] 内
也就是说返回值 nRet-WSA_WAIT_EVENT_0 将是发生事件的对象在事件对象数组中的下标。
int WSAEnumNetworkEvents(
_In_ SOCKET s, // 发生事件的SOCKET
_In_ WSAEVENT hEventObject, // 发生事件的事件对象
_Out_ LPWSANETWORKEVENTS lpNetworkEvents // 发生的网络事件
);
如果该函数执行成功将会返回0,然后可以通过查询网络事件判断到底发生了什么事件。
WSANETWORKEVENTS的定义如下:
typedef struct _WSANETWORKEVENTS {
long lNetworkEvents; // 发生的网络事件类型
int iErrorCode[FD_MAX_EVENTS]; // 网络事件对应的错误代码
} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;
比如当发生 FD_READ 事件时, 那么 networkEvent.lNetworkEvents&FD_READ 将为真,同时 networkEvent.iErrorCode[FD_READ_BIT]
标明了此时的错误代码。
代码示例:(你还需要一个 client程序,自己写或者找吧)
#include <Windows.h>
#include <iostream>
#pragma comment(lib,"ws2_32.lib")
using std::cout;
using std::cin;
using std::endl;
using std::ends;
void WSAEventServerSocket()
{
SOCKET server = ::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(server == INVALID_SOCKET){
cout<<"创建SOCKET失败!,错误代码:"<<WSAGetLastError()<<endl;
return ;
}
int error = 0;
sockaddr_in addr_in;
addr_in.sin_family = AF_INET;
addr_in.sin_port = htons(15000);
addr_in.sin_addr.s_addr = INADDR_ANY;
error= ::bind(server,(sockaddr*)&addr_in,sizeof(sockaddr_in));
if(error == SOCKET_ERROR){
cout<<"绑定端口失败!,错误代码:"<<WSAGetLastError()<<endl;
return ;
}
listen(server,5);
if(error == SOCKET_ERROR){
cout<<"监听失败!,错误代码:"<<WSAGetLastError()<<endl;
return ;
}
cout<<"成功监听端口 :"<<ntohs(addr_in.sin_port)<<endl;
WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS]; // 事件对象数组
SOCKET sockArray[WSA_MAXIMUM_WAIT_EVENTS]; // 事件对象数组对应的SOCKET句柄
int nEvent = 0; // 事件对象数组的数量
WSAEVENT event0 = ::WSACreateEvent();
::WSAEventSelect(server,event0,FD_ACCEPT|FD_CLOSE);
eventArray[nEvent]=event0;
sockArray[nEvent]=server;
nEvent++;
while(true){
int nIndex = ::WSAWaitForMultipleEvents(nEvent,eventArray,false,WSA_INFINITE,false);
if( nIndex == WSA_WAIT_IO_COMPLETION || nIndex == WSA_WAIT_TIMEOUT ){
cout<<"等待时发生错误!错误代码:"<<WSAGetLastError()<<endl;
break;
}
nIndex = nIndex - WSA_WAIT_EVENT_0;
WSANETWORKEVENTS event;
SOCKET sock = sockArray[nIndex];
::WSAEnumNetworkEvents(sock,eventArray[nIndex],&event);
if(event.lNetworkEvents & FD_ACCEPT){
if(event.iErrorCode[FD_ACCEPT_BIT]==0){
if(nEvent >= WSA_MAXIMUM_WAIT_EVENTS){
cout<<"事件对象太多,拒绝连接"<<endl;
continue;
}
sockaddr_in addr;
int len = sizeof(sockaddr_in);
SOCKET client = ::accept(sock,(sockaddr*)&addr,&len);
if(client!= INVALID_SOCKET){
cout<<"接受了一个客户端连接 "<<inet_ntoa(addr.sin_addr)<<":"<<ntohs(addr.sin_port)<<endl;
WSAEVENT eventNew = ::WSACreateEvent();
::WSAEventSelect(client,eventNew,FD_READ|FD_CLOSE|FD_WRITE);
eventArray[nEvent]=eventNew;
sockArray[nEvent]=client;
nEvent++;
}
}
}else if(event.lNetworkEvents & FD_READ){
if(event.iErrorCode[FD_READ_BIT]==0){
char buf[2500];
ZeroMemory(buf,2500);
int nRecv = ::recv( sock,buf,2500,0);
if(nRecv>0){
cout<<"收到一个消息 :"<<buf<<endl;
char strSend[] = "I recvived your message.";
::send(sock,strSend,strlen(strSend),0);
}
}
}else if(event.lNetworkEvents & FD_CLOSE){
::WSACloseEvent(eventArray[nIndex]);
::closesocket(sockArray[nIndex]);
cout<<"一个客户端连接已经断开了连接"<<endl;
for(int j=nIndex;j<nEvent-1;j++){
eventArray[j]=eventArray[j+1];
sockArray[j]=sockArray[j+1];
}
nEvent--;
} else if(event.lNetworkEvents & FD_WRITE ){
cout<<"一个客户端连接允许写入数据"<<endl;
}
} // end while
::closesocket(server);
}
int _tmain(int argc, _TCHAR* argv[])
{
WSADATA wsaData;
int error;
WORD wVersionRequested;
wVersionRequested = WINSOCK_VERSION;
error = WSAStartup( wVersionRequested , &wsaData );
if ( error != 0 ) {
WSACleanup();
return 0;
}
WSAEventServerSocket();
WSACleanup();
return 0;
}
// 解释一下,为什么我在 socket函数前面加上 ::
因为我前面写的时候本来用了thread库准备开一个线程运行Server,另一个运行Client。
结果 用了 using namespace std; 后,正好引入了bind函数(std的那个模板)把 socket的bind给覆盖了,
然后就一直是 错误了,查下错误代码是 10022(无效参数),检查时才发现的。
WSAEventSelect的更多相关文章
- 套接字I/O模型-WSAEventSelect(转载)
和WSAAsyncSelect类似,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知. 该模型最主要的区别是在于网络事件是由对象句柄完成的,而不是通过窗口例程完成. 事件通知 事件 ...
- 套接字I/O模型之WSAEventSelect
今天我又学习了一种新的套接字I/O模型------WSAEventSelect,他与WSAAsyncSelect一样也是一种异步事件通知模型,不同的是WSAAsyncSelect是与窗口句柄关联在一起 ...
- AcceptEx与WSAEventSelect和Accept
(转自论坛的一个帖子http://bbs.csdn.net/topics/280032853) AcceptEx主要用于向完成端口 投递一个或多个的连接请求..当有连接时进来,这里分两种情况: 1.A ...
- 【网络编程】之十二、wsaeventselect+线程池 服务器实现
#include<WinSock2.h> #include<iostream> using namespace std; #pragma comment(lib, " ...
- WSAEventSelect模型详解
WSAEventSelect 是 WinSock 提供的一种异步事件通知I/O模型,与 WSAAsyncSelect模型有些类似. 该模型同样是接收 FD_XXX 之类的网络事件,但是是通 ...
- WinSock IO模型 -- WSAEventSelect模型事件触发条件说明
FD_READ事件 l 调用WSAEventSelect函数时,如果当前有数据可读 l 有数据到达时,并且没有发送过FD_READ事件 l 调用recv/recvfrom函数后,仍然有数据可读时 ...
- WSAEventSelect IO复用模型
1 今天帮一学习WSAEventSelect的网友排查一个测试用服务器端recv返回0的问题,出现这个问题直观判断一般是客户端socket关闭了,可是他的代码很简单并且是本机测试,通过wireshar ...
- winsock编程WSAEventSelect模型
winsock编程WSAEventSelect模型 WSAEventSelect模型和WSAAsyncSelec模型类似,都是用调用WSAXXXXXSelec函数将socket和事件关联并注册到系统, ...
- WinSock WSAEventSelect 模型总结
前言 本文配套代码:https://github.com/TTGuoying/WSAEventSelect-model 由于篇幅原因,本文假设你已经熟悉了利用Socket进行TCP/IP编程的基本原理 ...
随机推荐
- rtsp转发服务器设计
做一个设备实时监控.需求是这样的,一个用户有多个设备(android系统,支持摄像头),设备分布在家中或者其它地方:用户可以远程通过终端(手机.pc.ipad,etc...)管理操纵这些设备(包括实时 ...
- log4j日志输出使用教程
Log4j是帮助开发人员进行日志输出管理的API类库.它最重要的特点就可以配置文件灵活的设置日志信息的优先级.日志信息的输出目的地以及日志信息的输出格式.Log4j除了可以记录程序运行日志信息外还有一 ...
- C#中的委托(Delegate)和事件(Event)
原文地址:C#中的委托(Delegate)和事件(Event) 作者:jiyuan51 把C#中的委托(Delegate)和事件(Event)放到现在讲是有目的的:给下次写的设计模式--观察者(Obs ...
- Convert String to Long
问题: Given a string, write a routine that converts the string to a long, without using the built in f ...
- Oracle数据库之序列
Oracle数据库之序列(sequence) 序列是一个计数器,它并不会与特定的表关联.我们可以通过创建Oracle序列和触发器实现表的主键自增.序列的用途一般用来填充主键和计数. 一.创建序列 语法 ...
- Axure矩形边框重合边框变成双倍宽度解决办法
选中两个矩形,菜单栏选择项目——项目设置——边界对齐(选择中心边界沿形状的边缘或者内边界对齐.将外边界对齐改为内边界对齐),即可去掉重合效果
- 转 常用JQuery插件整理
虽然自己也写过插件,但JQuery插件种类的繁多,大多时候,我还是使用别人写好的插件,这些都是我用了同类插件里较为不错的一些,今天就整理一下公开放出来. UI: jquery.HooRay(哈哈,自己 ...
- php异步调用方法实现示例
php 异步调用方法 客户端与服务器端是通过HTTP协议进行连接通讯,客户端发起请求,服务器端接收到请求后执行处理,并返回处理结果. 有时服务器需要执行很耗时的操作,这个操作的结果并不需要返回 ...
- linux下一些很有用的命令
ps -ef | grep keepalive | grep -v grep | awk '{print $2}' | xargs kill -9 wget --random-wait -r -p - ...
- nginx 中location和root
nginx 中location和root,你确定真的明白他们关系? 2016-01-17 14:48 3774人阅读 评论(1) 收藏 举报 分类: linux(17) 版权声明:本文为博主原创文 ...