您的位置:首页 > 其它

windiows 下 WSAEventSelect模型

2017-07-31 11:11 381 查看
     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

int WSAEventSelect(  

  

     SOCKET s,  //套接字句柄

  

     WSAEVENT hEvent,  //为事件对象句柄

  

     Long lNetworkEvents); //应用程序感兴趣的网络事件集合 

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

 

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

[cpp] view
plain copy

WSAEVENT  WSACreateEvent(void);
 

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

[cpp] view
plain copy

bool WSAResetEvent(WSAEVENT hEvent);  

 

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

[cpp] view
plain copy

bool WSACloseEvent(WSAEVENT hEvent);  

 

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

[cpp] view
plain copy

DWORD WSAWaitForMultipleEvents(  

  

        DWORD cEvents,
 //事件对象句柄个数。至少为1,最多WSA_MAXIMUM_WAIT_EVENTS,64个。

  

        const WSAEVENT *plhEvent,
 //为指向对象句柄数组指针

  

        BOOL fWaitAll,
 //如果为TRUE,则该函数在所有事件对象都转变为已触发时才返回。如为false,只要有一个对象被触发,函数即返回。

  

        DWORD dwTimeOUT,
 //函数阻塞事件。单位为毫秒。在时间用完后函数会返回。如果为WSA_INFINITE则函数会一直等待下去。如果超时返回,函数返回WSA_WAIT_TIMEOUT。

  

        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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: