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事件后加入事件对象数组。

 


[cpp] view plain copy
  1. int WSAEventSelect(  
  2.   
  3.      SOCKET s,  //套接字句柄
  4.   
  5.      WSAEVENT hEvent,  //为事件对象句柄
  6.   
  7.      Long lNetworkEvents); //应用程序感兴趣的网络事件集合 

如果应用程序为套接字注册网络事件成功,函数返回0。否则返回SOCKET_ERROR。可以调用WSAGetLastError来获取具体的错误代码。调用该函数后,套接字自动被设为非阻塞的工作模式。如果应用程序要将套接字设置为阻塞模式,必须将lNetwork参数设为0,再次调用WSAEventSelect函数。



 

调用WSACreateEvent函数创建一个事件对象  

[cpp] view plain copy
  1. WSAEVENT  WSACreateEvent(void);  


当网络事件到来时,与套接字关联的事件对象由未触发变为触发态。由于它是手工重置事件,应用程序需要手动将事件的状态设置为未触发态。这可以调用WSAResetEvent函数:

[cpp] view plain copy
  1. bool WSAResetEvent(WSAEVENT hEvent);  


 

不再使用事件对象时要将其关闭。 

[cpp] view plain copy
  1. bool WSACloseEvent(WSAEVENT hEvent);  

 

WSAWaitForMultipleEvents函数可以等待网络事件的发生。它的目的是等待一个或是所有的事件对象变为已触发状态。 

[cpp] view plain copy
  1. DWORD WSAWaitForMultipleEvents(  
  2.   
  3.         DWORD cEvents,  //事件对象句柄个数。至少为1,最多WSA_MAXIMUM_WAIT_EVENTS64个。
  4.   
  5.         const WSAEVENT *plhEvent,  //为指向对象句柄数组指针
  6.   
  7.         BOOL fWaitAll,  //如果为TRUE,则该函数在所有事件对象都转变为已触发时才返回。如为false,只要有一个对象被触发,函数即返回。
  8.   
  9.         DWORD dwTimeOUT,  //函数阻塞事件。单位为毫秒。在时间用完后函数会返回。如果为WSA_INFINITE则函数会一直等待下去。如果超时返回,函数返回WSA_WAIT_TIMEOUT
  10.   
  11.         BOOL fAlertable);  //参数说明当完成例程在系统队列中排队等待执行时,该函数是否返回。这主要应用于重叠IO模型,以后还会介绍。 该模型下忽略,应该设置为false


  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。



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]
标明了此时的错误代码。


服务端


#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;}



原创粉丝点击