套接字I/O模型-WSAEventSelect(转载)

和WSAAsyncSelect类似,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知。
该模型最主要的区别是在于网络事件是由对象句柄完成的,而不是通过窗口例程完成。

事件通知
事件通知模型要求应用程序针对打算使用的每一个套接字,首先创建一个事件对象。创建方法是调用WSACreateEvent函数:
WSAEVENT WSACreateEvent(void);
WSACreateEvent的返回值很简单,就是一个人工重设的事件对象句柄,一旦得到了事件对象句柄之后,必须将它与某个套接字关联起来,同时注册感兴趣的网络事件类型。要做到这一点,方法是调用WSAEventSelect函数:
int WSAEventSelect(
  SOCKET s,//程序感兴趣的套接字
  WSAEVENT hEventObject,//指定要与套接字关联在一起的事件对象
  long lNetworkEvents//位掩码,用于指定应用程序感兴趣的各种网络事件类型组合
);
为WSAEventSelect创建的事件有两种工作状态和两种工作模式,其中,两种工作状态是已传信(signaled)和为传信(non-signaled)。工作模式则包括人工重设和自动重设。WSAEventSelect最初是在一种为传信的工作状态,并用一种人工重设模式,来创建事件句柄。若网络事件触发了与一个套接字关联在一起的事件对象,工作状态便会从为传信变为已传信。由于事件对象是在一种人工重设模式下创建的,所有完成了一个I/O请求处理之后,应用程序需要负责将工作模式从已传信更改为未传信,要做到这一点,可调用WSAResetEvent函数:
BOOL WSAResetEvent(WSAEVENT hEvent);
完成了对某个事件对象的处理之后,便应调用WSACloseEvent函数释放由事件句柄使用的系统资源。
BOOL WSACloseEvent(WSAEVENT hEvent);
套接字同一个事件对象句柄关联在一起后,应用程序便可开始I/O处理,这就需要应用程序等待网络事件触发事件对象句柄的工作状态,WSAWaitForMultipleEvents函数的设计宗旨就是用来等待一个或多个事件对象句柄,并在事先指定的一个或所有句柄进入已传信状态后,或在超过了一个规定的时间周期后,立即返回
DWORD WSAWaitForMultipleEvents(
  DWORD cEvents,
  const WSAEVENT FAR* lphEvents,
  BOOL fWaitAll,
  DWORD dwTimeout,
  BOOL fAlertable
);
cEvents和lphEvents定义了由WSAEVENT对象构成的一个数组,cEvents指定的是这个数组中事件对象的数量,而lphEvents是一个指针,用于直接引用该数组。要注意的是WSAWaitForMultipleEvents只能支持由WSA_MAXIMUM_WAIT_EVENTS对象规定的一个最大值,在此这个值为64。因此对于发出 WSAWaitForMultipleEvents调用的每一个线程,该I/O模型一次最多接收64个套接字。假如想让这个套接字一次管理多于64个套接字,必须创建额外的工作线程,以便等待更多的事件对象。fWaitAll指定 WSAWaitForMultipleEvents如何等待在事件数组中的对象。若将该参数设置为TRUE,那么只有等lphEvents数组内包含的所有事件对象都已进入已传信状态,函数才会返回,若设为FALSE,则任何一个事件对象进入已传信状态时,函数就返回。通常应用程序会将该参数设为FALSE,一次只为一个套接字事件提供服务。dwTimeout规定了 WSAWaitForMultipleEvents等待一个网络事件发生时,最多可等待多长时间,以毫秒为单位,超过规定时间,函数就返回。如果超时值为0,函数会检测指定的事件对象状态,并立即返回。这样,应用程序可以实现对事件对象的轮询。如果没有可处理事件, WSAWaitForMultipleEvents便会返回WSA_WAIT_TIMEOUT,如果dwTimeout被设为WSA_INFINITE,那么只有在网络事件传信了一个事件对象后,函数才会返回。fAlertable可被忽略,设为FALSE。
应该注意到一次只服务一个已传信事件(fWaitAll设为FALSE),就可能让套接字一直“挨饿”,且可能持续到事件数组的末尾。

若WSAWaitForMultipleEvents收到一个事件对象的网络通知,便会返回一个值,指出造成函数返回的事件对象。这样,应用程序便可引用事件数组中已传信的事件,并检索与那个事件对应的套接字,并判断到底是那个套接字上,发生了什么样的网络事件。对事件数组中的事件进行引用时,应该用WSAWaitForMultipleEvents的返回值,减去预定义的WSA_WAIT_EVENT_0,从而得到具体的引用值。
Index = WSAWaitForMultipleEvents(...);
MyEvent = EventArray[Index - WSA_WAIT_0];
指定了造成网络事件的套接字后, 接下来可调用WSAEnumNetworkEvents函数,调查发生了那些网络事件,该函数定义如下:
int WSAEnumNetworkEvents(
  SOCKET s,//造成网络事件的套接字
  WSAEVENT hEventObject,//可选参数,指定了一个事件句柄,对应于打算重设的那个事件对象
  LPWSANETWORKEVENTS lpNetworkEvents//指向WSANETWORKEVENTS的指针,用于检测套接字上发生的网络事件类型以及可能出现的任何错误代码
);
WSANETWORKEVENTS结构如下:
typedef struct _WSANETWORKEVNETS
{
  long lNetworkEvents;
  inbt iErrorCode[FD_MAX_EVENTS];
}WSANETWORKEVENTS, FAR* LPWSANETWORKEVENTS;
lNetworkEvents指定一个值,对应于该套接字上发生的所有网络事件类型
iErrorCode指定了一个错误代码数组,这个数组同lNetworkEvents中的事件关联在一起,针对每个网络事件类型,都存在着一个特殊的事件索引,它与事件类型的名称类似,只是事件类型名称后面添加一个"_BIT"作为后缀字符串。例如,对FD_READ事件类型来说,iErrorCode数组的索引标识便是FD_READ_BIT。
//处理FD_READ通知
if(NetworkEvents.lNetworkEvents & FD_READ)
{
  if(NetworkEvents.iErrorCode[FD_READ_BIT]!=0)
  {
    printf("FD_READ failed with error %d \n", NetworkEvents.iErrorCode[FD_READ_BIT]);
  }
}
演示用WSAEventSelect模型的创建步骤:
SOCKET SocketArray[WSA_MAXIMUM_WAIT_EVENTS];
WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];
WSAEVENT NetEvent;
SOCKADDR_IN addr;
SOCKET Accept,Listen;
DWORD EventTotal = 0;
DWORD Index;
DOWRD i;
//创建一个TCP套接字在5050端口上的监听
Listen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
addr.sin_family = AF_INET;
addr.sin_port = htons(5050);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(Listen, (SOCKADDR*)&addr, sizeof(SOCKADDR_IN)); 

NetEvent = WSACreateEvent(); 

WSAEventSelect(Listen, NetEvent, FD_ACCEPT|FD_CLOSE); 

listen(Listen, 5); 

SocketArray[EventTotal] = Listen;
EventArray[EventTotal] = NetEvent;
EventTotal++; 

while(TRUE)
{
  //等候所有套接字上的网络事件
  Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE);
  Index = Index - WSA_WAIT_EVENT_0; 

  //遍历所有事件,查看被传信的事件是否多于一个
  for(i = Index; i<EventTotal; i++)
  {
    Index = WSAWaitForMultipleEvents(1, &EventArray[i], TRUE, 1000, FALSE);
    if((Index==WSA_WAIT_FAILED)||(Index==WSA_WAIT_TIMEOUT))
    {
      continue;
    }
    else
    {
      Index = i;
      WSAEnumNetworkEvents(SocketArray[Index], EventArray[Index], &NetworkEvents);
      //检测FD_ACCEPT消息
      if(NetworkEvents.lNetworkEvents&FD_ACCEPT)
      {
        if(NetworkEvents.iErrorCode[FD_ACCEPT_BIT]!=0)
        {
          printf("FD_ACCEPT failed with error %d\n", NetworkEvents.iErrorCode[FD_ACCEPT_BIT]);
          break;
        }
        //接收一个新连接,并将它添加到套接字及事件列表中
        Accept = accept(SocketArray[Index], NULL, NULL);
        //无法处理多于WSA_MAXIMUM_WAIT_EVENTS数量套接字,故关闭接收套接字
        if(EventTotal>WSA_MAXIMUM_WAIT_EVENTS)
        {
          printf("Too Many Connections");
          closesocket(Accept);
          break;
        }
        NetEvent = WSACreateEvent();
        WSAEventSelect(Accept, NetEvent, FD_READ|FD_WRITE|FD_CLOSE);
        EventArray[EventTotal] = NetEvent;
        SocketArray[EventTotal] = Accept;
        EventTotal++;
        printf("Socket %d connected \n", Accept);
      }
      //处理FD_READ通知
      if(NetworkEvents.lNetworkEvents&FD_READ)
      {
        if(NetworkEvents.iErrorCode[FD_READ_BIT]!=0)
        {
          printf("FD_READ failed with error %d\n", NetworkEvents.iErrorCode[FD_READ_BIT]);
          break;
        }
        //从套接字读取数据
        recv(SocketArray[Index-WSA_WAIT_EVENT_0], buffer, sizeof(buffer), 0);
      }
      //处理FD_WRITE通知
      if(NetworkEvents.lNetworkEvents&FD_WRITE)
      {
        if(NetworkEvents.iErrorCode[FD_WRITE_BIT]!=0)
        {
          printf("FD_WRITE failed with error %d\n", NetworkEvents.iErrorCode[FD_WRITE_BIT]);
          break;
        }
        send(SocketArray[Index-WSA_WAIT_EVENT_0], buffer, sizeof(buffer), 0);
      }
      //处理FD_CLOSE通知
      if(NetworkEvents.lNetworkEvents&FD_CLOSE)
      {
        if(NetworkEvents.iErrorCode[FD_CLOSE_BIT]!=0)
        {
          printf("FD_CLOSE failed with error %d\n", NetworkEvents.iErrorCode[FD_CLOSE_BIT]);
          break;
        }
        closesocket(SocketArray[Index]);
        //从Socket和Event数组中删除套接字及与其关联的事件,并递减EventTotal
        CompressArrays(EventArray, SocketArray, &EventTotal);
      }
    }
  }
}
优势,概念简单,不需要窗口环境。
缺点,它每次只等待64个事件,处理多个套接字时,有必要使用一个线程池

=======================================================================
#include<stdio.h>
#include<winsock2.h>
#pragma comment(lib, "ws2_32.lib"); 

#define PORT 5050
#define MSGSIZE 1024 

int g_iTotalConn = 0;
SOCKET g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];
WSAEVENT g_CliEventArr[MAXIMUM_WAIT_OBJECTS]; 

DWORD WINAPI WorkerThread(LPVOID lpParam);
void Cleanup(int index); 

int main()
{
    WSADATA wsaData;
    SOCKET sListen, sClient;
    SOCKADDR_IN local, client;
    DWORD dwThreadId;
    int iAddrSize = sizeof(SOCKADDR_IN);
    WSAStartup(MAKEWORD(2,2), &wsaData);
    sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    memset(&local, 0, sizeof(SOCKADDR_IN));
    local.sin_family = AF_INET;
    local.sin_port = htons(PORT);
    local.sin_addr.s_addr = htonl(INADDR_ANY);
    bind(sListen, (SOCKADDR*)&local, sizeof(SOCKADDR_IN));
    listen(sListen, 3);
    CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
    while(TRUE)
    {
        // Accept a connection
        sClient = accept(sListen, (SOCKADDR*)&client, &iAddrSize);
        printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
        // Associate socket with network event
        g_CliSocketArr[g_iTotalConn] = sClient;
        g_CliEventArr[g_iTotalConn] = WSACreateEvent();
        WSAEventSelect(g_CliSocketArr[g_iTotalConn], g_CliEventArr[g_iTotalConn], FD_READ|FD_CLOSE);
        g_iTotalConn++;
    }
    return 0;
} 

DWORD WINAPI WorkerThread(LPVOID lpParam)
{
    int ret, index;
    WSANETWORKEVENTS NetworkEvents;
    char szMessage[MSGSIZE]; 

    while (TRUE)
    {
        ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000, FALSE);
        if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
        {
            continue;
        }
        index = ret - WSA_WAIT_EVENT_0;
        WSAEnumNetworkEvents(g_CliSocketArr[index], g_CliEventArr[index], &NetworkEvents);
        if (NetworkEvents.lNetworkEvents & FD_READ)
        {
            // Receive message from client
            ret = recv(g_CliSocketArr[index], szMessage, MSGSIZE, 0);
            if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
            {
                Cleanup(index);
            }
            else
            {
                szMessage[ret] = ‘\0‘;
                send(g_CliSocketArr[index], szMessage, strlen(szMessage), 0);
            }
        }
        if (NetworkEvents.lNetworkEvents & FD_CLOSE)
        {
            Cleanup(index);
        }
    } 

    return 0;
} 

void Cleanup(int index)
{
    closesocket(g_CliSocketArr[index]);
    WSACloseEvent(g_CliEventArr[index]);
    if (index < g_iTotalConn-1)
    {
        g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn-1];
        g_CliEventArr[index] = g_CliEventArr[g_iTotalConn-1];
    }
    g_iTotalConn--;
}
事件选择模型也比较简单,实现起来也不是太复杂,它的基本思想是将每个套接字都和一个WSAEVENT对象对应起来,并且在关联的时候指定需要关注的哪些网络事件。一旦在某个套接字上发生了我们关注的事件(FD_READ和FD_CLOSE),与之相关联的WSAEVENT对象被Signaled。程序定义了两个全局数组,一个套接字数组,一个WSAEVENT对象数组,其大小都是MAXIMUM_WAIT_OBJECTS(64),两个数组中的元素一一对应。 

同样的,这里的程序没有考虑两个问题,一是不能无条件的调用accept,因为我们支持的并发连接数有限。解决方法是将套接字按 MAXIMUM_WAIT_OBJECTS分组,每MAXIMUM_WAIT_OBJECTS个套接字一组,每一组分配一个工作者线程;或者采用 WSAAccept代替accept,并回调自己定义的Condition Function。第二个问题是没有对连接数为0的情形做特殊处理,程序在连接数为0的时候CPU占用率为100%。
分享至    
时间: 2024-10-24 05:25:29

套接字I/O模型-WSAEventSelect(转载)的相关文章

套接字I/O模型之WSAEventSelect

转自 http://blog.csdn.net/wanjingwei/article/details/4306609 今天我又学习了一种新的套接字I/O模型------WSAEventSelect,他与WSAAsyncSelect一样也是一种异步事件通知模型,不同的是WSAAsyncSelect是与窗口句柄关联在一起的,必须要要窗口才行,而WSAEventSelect是与事件对象关联的.这个模型的基本思路是为感兴趣的一组网络事件创建一个事件对象,再调用WSAEventSelect函数将网络事件和

套接字I/O模型-WSAAsyncSelect

利用这个异步I/O模型,应用程序可在一个套接字上接收以Windows消息为基础的网络事件通知.WSAAsyncSelect和WSAEventSelect提供读写数据能力的异步通知,但它们不提供异步数据传输,重叠及完成端口提供异步数据传输. 消息通知 要想使用WSAAsyncSelect模型,在应用程序中,首先必须用CreateWindow函数创建一个窗口,再为该窗口提供一个窗口过程支持函数,亦可使用一个对话框,为其提供一个对话框过程来代替窗口过程,这是因为对话框本质也是窗口. int WSAAs

套接字I/O模型-重叠I/O

重叠模型的基本设计原理是让应用程序使用重叠的数据结构,一次投递一个或多个WinsockI/O请求.针对那些提交的请求,在它们完成之后,应用程序可为它们提供服务.模型的总体设计以Windows重叠I/O机制为基础.这个机制可通过ReadFile和WriteFile两个函数,在设备上执行I/O操作. 要想在一个套接字上使用重叠I/O模型,首先必须创建一个设置了重叠标志的套接字. 主要有两种方法来管理重叠I/O的请求.1.事件对象通知 2.完成实例. 事件通知: 重叠I/O的事件通知方法要求将Wind

套接字I/O模型-完成端口IOCP

“完成端口”模型是迄今为止最为复杂的一种I/O模型.然而,假若一个应用程序同时需要管理为数众多的套接字,那么采用这种模型,往往可以达到最佳的系统性能!但不幸的是,该模型只适用于Windows NT和Windows 2000操作系统.因其设计的复杂性,只有在你的应用程序需要同时管理数百乃至上千个套接字的时候,而且希望随着系统内安装的CPU数量的增多,应用程序的性能也可以线性提升,才应考虑采用“完成端口”模型.要记住的一个基本准则是,假如要为Windows NT或Windows 2000开发高性能的

《JAVA套接字》

套接字(socket)为两台计算机之间的通信提供了一种机制,在 James Gosling 注意到 Java 语言之前,套接字就早已赫赫有名.为了能够方便开发网络应用程序,Unix系统推出了一种应用程序访问通信协议的操作系统调用——Socket套接字,使得程序员很方便的访问TCP/IP协议,从而开发各种网络应用程序.后来Windows也引入Socket,Java语言也引入了套接字的编程模型. 多数程序员,不管他们是否使用 Java 语言进行编码,都不想很多知道关于不同计算机上的应用程序彼此间如何

1.3.1 数据报套接字编程

1.3  实现超链接 在网络应用过程中,特别是在Web程序中,超级链接用得非常普遍.其实使用VC技术,也可以实现超级链接功能.在本节的内容中,将介绍使用Visual C++ 6.0开发一个实现超级链接功能的应用程序.在开始之前,首先简单介绍与之相关的基础知识. 1.3.1  数据报套接字编程 流式套接字主要用于TCP协议,接下来将要学的数据报套接字主要用于UDP协议.数据报套接字(Datagram Socket)提供双向的通信,但没有可靠/有序/不重复的保证,所以UDP传送数据可能会收到无次序.

[转载] 读《UNIX网络编程 卷1:套接字联网API》

原文: http://cstdlib.com/tech/2014/10/09/read-unix-network-programming-1/ 文章写的很清楚, 适合初学者 最近看了<UNIX网络编程 卷1:套接字联网API>, 英文名叫Unix Network Programming啦,后来上网查了查, 一般都叫UNP逼格会高一点, 就像APUE一样. 他们的作者都是W. Richard Stevens. 另外,他也是TCP/IP Illustrated的作者. 靠,看完作者简介,简直崇拜得

异步套接字编程之select模型

█ 选择(select)模型是Winsock中最常见的 I/O模型.核心便是利用 select 函数,实现对 I/O的管理!利用 select 函数来判断某Socket上是否有数据可读,或者能否向一个套接字写入数据,防止程序在Socket处于阻塞模式中时,在一次 I/O 调用(如send或recv.accept等)过程中,被迫进入“锁定”状态:同时防止在套接字处于非阻塞模式中时,产生WSAEWOULDBLOCK错误. █ select 的函数原型如下:int select(  __in    

10 传输模型与套接字(进阶)

01-传输模型 第一部分 传输模型 1.基本模型 2.层次划分 MAC地址:唯一的地址 TCP可靠.UDP不可靠 3.传输层说明 说明一: ? 作为Python开发,咱们都是在应用层的HTTP协议之上进行开发的. 说明二: 网络编程,主要是了解我们Python能编写的最低的层次,即传输层的基本情况 说明三: HTTP协议是基于TCP之上的,因此我们需要了解TCP连接的基本过程 第二部分 TCP连接 1.建立连接(三次握手) 为什么三次 因为第一次只是客户端告诉服务端 第二次,客户端才知道服务端收