windiows 下 WSAEventSelect模型
来源:互联网 发布:小世界网络模型的例子 编辑:程序博客网 时间:2024/06/05 00:13
WSAEventSelect模型是Windows socekts提供的另一个有用异步IO模型。 该模型同样是接收 FD_XXX 之类的网络事件,但是是通过事件对象句柄通知,而非像 WSAAsyncSelect一样依靠Windows的消息驱动机制。 与WSAAsyncSelect模型很相似 ,它们都是异步的,通知应用程序的形式不同,WSAAsyncSelect以消息的形式通知,而WSAEventSelect以事件的形式通知。
与select模型相比较,select模型是主动的,应用程序主动调用select函数来检查是否发生了网络事件。WSAAsyncSelect与WSAEventSelect模型都是被动接受的。网络事件发生时,系统通知应用程序。
与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事件后加入事件对象数组。
- int WSAEventSelect(
- SOCKET s, //套接字句柄
- WSAEVENT hEvent, //为事件对象句柄
- Long lNetworkEvents); //应用程序感兴趣的网络事件集合
如果应用程序为套接字注册网络事件成功,函数返回0。否则返回SOCKET_ERROR。可以调用WSAGetLastError来获取具体的错误代码。调用该函数后,套接字自动被设为非阻塞的工作模式。如果应用程序要将套接字设置为阻塞模式,必须将lNetwork参数设为0,再次调用WSAEventSelect函数。
调用WSACreateEvent函数创建一个事件对象
当网络事件到来时,与套接字关联的事件对象由未触发变为触发态。由于它是手工重置事件,应用程序需要手动将事件的状态设置为未触发态。这可以调用WSAResetEvent函数:
不再使用事件对象时要将其关闭。
WSAWaitForMultipleEvents函数可以等待网络事件的发生。它的目的是等待一个或是所有的事件对象变为已触发状态。
WSAWaitForMultipleEvents返回时,返回值会指出它返回的原因。
当fWaitAll为TRUE时:
如果返回值为WSA_TIMEOUT则表明等待超时。
WSA_EVENT_0表明所有对象都已变成触发态。等待成功。
WAIT_IO_COMPLETION说明一个或多个完成例程已经排队等待执行。
如果fWaitAll为false时:
WSA_WAIT_EVENT_0 到 WSA_WAIT_EVENT_0 + cEvent-1 范围内的值,说明有一个对象变为触发态。它在数组中的下标为: (返回值 - WSA_EVENT_0 )
如果函数调用失败,则返回WSA_WAIT_FAILED。
_In_ SOCKET s, // 发生事件的SOCKET
_In_ WSAEVENT hEventObject, // 发生事件的事件对象
_Out_ LPWSANETWORKEVENTS lpNetworkEvents // 发生的网络事件
);
如果该函数执行成功将会返回0,然后可以通过查询网络事件判断到底发生了什么事件。
WSANETWORKEVENTS的定义如下:
long lNetworkEvents; // 发生的网络事件类型
int iErrorCode[FD_MAX_EVENTS]; // 网络事件对应的错误代码
} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;
比如当发生 FD_READ 事件时, 那么 networkEvent.lNetworkEvents&FD_READ 将为真,同时 networkEvent.iErrorCode[FD_READ_BIT]
标明了此时的错误代码。
服务端
#include <WINSOCK2.H> #include <iostream>/*#include <windows.h>*/ #pragma comment(lib,"WS2_32") using namespace std;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(6000); 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 main(int argc, char * argv[]){ WSADATA wsaData; int error; WORD wVersionRequested; wVersionRequested = WINSOCK_VERSION; error = WSAStartup( wVersionRequested , &wsaData ); if ( error != 0 ) { WSACleanup(); return 0; } WSAEventServerSocket(); WSACleanup(); return 0;}
客户端
#include<stdlib.h>#include<WINSOCK2.H> #include <windows.h> #include <process.h> #include<iostream>#include<string>using namespace std;#define BUF_SIZE 64#pragma comment(lib,"WS2_32.lib")void recv(PVOID pt) { SOCKET sHost= *((SOCKET *)pt); while(true){ char buf[BUF_SIZE];//清空接收数据的缓冲区memset(buf,0 , BUF_SIZE);int retVal=recv(sHost,buf,sizeof(buf),0);if(SOCKET_ERROR==retVal){int err=WSAGetLastError();//无法立即完成非阻塞Socket上的操作if(err==WSAEWOULDBLOCK){Sleep(1000);printf("\nwaiting reply!");continue;}else if(err==WSAETIMEDOUT||err==WSAENETDOWN|| err==WSAECONNRESET)//已建立连接{printf("recv failed!");closesocket(sHost);WSACleanup();return ;}}Sleep(100); printf("\n%s", buf); //break;} } int main(){WSADATA wsd;SOCKET sHost;SOCKADDR_IN servAddr;//服务器地址int retVal;//调用Socket函数的返回值char buf[BUF_SIZE];//初始化Socket环境if(WSAStartup(MAKEWORD(2,2),&wsd)!=0){printf("WSAStartup failed!\n");return -1;}sHost=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);//设置服务器Socket地址servAddr.sin_family=AF_INET;servAddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");//在实际应用中,建议将服务器的IP地址和端口号保存在配置文件中servAddr.sin_port=htons(6000);//计算地址的长度int sServerAddlen=sizeof(servAddr); //调用ioctlsocket()将其设置为非阻塞模式 int iMode=1;retVal=ioctlsocket(sHost,FIONBIO,(u_long FAR*)&iMode); if(retVal==SOCKET_ERROR){printf("ioctlsocket failed!");WSACleanup();return -1;}//循环等待while(true){//连接到服务器retVal=connect(sHost,(LPSOCKADDR)&servAddr,sizeof(servAddr));if(SOCKET_ERROR==retVal){int err=WSAGetLastError();//无法立即完成非阻塞Socket上的操作if(err==WSAEWOULDBLOCK||err==WSAEINVAL){Sleep(1);printf("check connect!\n");continue;}else if(err==WSAEISCONN)//已建立连接{break;}else{printf("connection failed!\n");closesocket(sHost);WSACleanup();return -1;}}} unsigned long threadId=_beginthread(recv,0,&sHost);//启动一个线程接收数据的线程 while(true){//向服务器发送字符串,并显示反馈信息printf("input a string to send:\n");std::string str;//接收输入的数据std::cin>>str;//将用户输入的数据复制到buf中ZeroMemory(buf,BUF_SIZE);strcpy(buf,str.c_str());if(strcmp(buf,"quit")==0){printf("quit!\n");break;}while(true){retVal=send(sHost,buf,strlen(buf),0);if(SOCKET_ERROR==retVal){int err=WSAGetLastError();if(err==WSAEWOULDBLOCK){//无法立即完成非阻塞Socket上的操作Sleep(5);continue;}else{printf("send failed!\n");closesocket(sHost);WSACleanup();return -1;}}break;} }return 0;}
- windiows 下 WSAEventSelect模型
- Windows平台下 WSAEventSelect模型 服务器
- WSAEventSelect模型
- WSAEventSelect模型
- WSAEventSelect模型
- WSAEventSelect模型
- WSAEventSelect模型
- WSAEventSelect模型
- WSAEventSelect模型
- WSAEventSelect模型
- WSAEventSelect模型
- WSAEventSelect模型
- WSAEventSelect模型
- WSAEventSelect模型
- WSAEventSelect模型
- WSAEventSelect模型
- WSAEventSelect模型
- WSAEventSelect模型
- Java中String和byte[]互转问题
- ES6 module export和import
- strcpy的参数
- 注册表
- SSAS对称维度与非对称维度
- windiows 下 WSAEventSelect模型
- Java中一个类里面有两个用synchronized修饰的非静态方法,不同的线程中的实例访问这两个方法时会发生什么?
- 被误解的明朝——中国的"文艺复兴"(转)
- 启动oracle数据库配置
- Android View
- Maven阿里云仓库
- 程序员必备技术网站及刷题网站 | Java我最强
- Android 5.0后截屏,无需root
- ceph中使用sgdisk