Winsock I/O方法

来源:互联网 发布:工业机器人控制软件 编辑:程序博客网 时间:2024/05/01 21:11


1.blocking(阻塞)模型

最简单的模式,也是最基础的模式。

2.select模型

        其使用select函数实现对I/O的管理。select函数可以判断套接字上是否有数据,或者是否能够向套接字上写数据。设计这可函数的目的是,为了防止套接字处于阻塞模式时,I/O调用过程处于阻塞模式;或者当套接字处于非阻塞模式时,产生WSAEWOULDBLOCK错误。如果不满足实现规定的参数条件,那么select函数在进行I/O操作时会阻塞。其定义如下:

int select(   int nfds,   fd_set FAR* readfds,   fd_set FAR* writefds,   fd_set FAR* exceptfds,   const struct timeval FAR* timeout);
  • nfds:这个参数会被忽略。
  • readfds:其为sd_set类型,其是一系列套接字的集合,用于检查可读性。这个集合要满足下面条件之一:1)有数据可读入  2)连接已经被关闭、重启或终止  3)假如已经调用listen,且有一个连接处于搁置状态,那么accept调用成功。
  • wrtiefds:用于检查可写性。其套接字要满足下面条件之一:1)有数据发出  2)如果正在对一个非阻塞连接调用进行处理,则连接就成功了。
  • exceptfds:用于带外数据。其套机字要满足下面条件之一:1)加入正在对一个非阻塞连接调用进行处理,连接尝试机会失败。  2)有00B数据可读操作。
  • timeout:其一个指向timeval结构体的指针,用于表示select函数在调用返回前的等待时间,如果为空指针({0,0}),表示无限期等待。不为0,表示其中至少一个套接字满足条件。
  • 返回值:如果select调用成功,会在fd_set结构中,返回被挂起的I/O操作的所有套接字句柄总量。超时,返回0。失败,返回SOCKET_ERROR。

select返回后,会对每个fd_set结构体进行修改,会将那些不存在被挂起I/O操作的套接字删除。也就是说,我们可以通过FD-ISSET宏来判断等待的套接字是还处于宏中。

timeval结构体定义如下:

struct timeval{   long tv_sec;   long tv_usec;};
  • tv_sec:以秒为单位指定等待时间。
  • tv_usec:以毫秒为单位指定等待时间。

在用select函数对套接字进行监听前,需要将套接字分配给一个集合。对fd_set集合进行处理与检查的宏:

  • FD_ZERO(*,set):将set集合初始化为空。
  • FD_CLR(s,* set):从set中删除套接字s.
  • FD_ISSET(s,* set):从集合set中检查s是否在其中;是,就返回TRUE。
  • FD_SET(s,* set):将套接字s加入集合set中。
  • FD_SETSIZE:对fd_set结构中的最多套接字进行设置。因为默认情况下最多能包含64个套接字。

下面是一个框架:

SOCKET s;fd_set fread;int ret;while(TRUE){    FD_SERO(&fread);    FD_SET(s,&fread);    if((ret=select(0,&fread,NULL,NULL,NULL))==SOCKET_ERROR)    {      ....    }    if(ret>0)    {       if(FD_ISSET(s,&fread))       {       }    }}

3.WSAAsyncSelect模型

       这个模型是在一个套接字上,可以接收以windows消息为基础的网络事件通知。要想使用该函数模型,首先必须用CreateWindow函数来创建一个窗口,并且为该窗口提供一个窗口过程支持函数。WSAAsyncSelect和WSAEventSelect一样提供了异步数据读写能力的通知,但不提供异步数据的传输,而重叠和完成端口都提供了异步数据传输功能。该函数定义如下:

int WSAAsyncSelect(  SOCKET s,  HWND hWnd,  unsigned int wMsg,  long lEvent);
  • s:为感兴趣的套接字。
  • hWnd:为一个窗口句柄,用于接收消息。
  • wMsg:发生网络事件时,打算接收的消息。该消息会被传到标识的窗口。
  • lEvent:是一位掩码,指定一系列网络事件集合,有如下值(可以用“或”运算):FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT和FD_CLOSE。

sd

接收网络消息的窗口过程:

LPRESULT CALLBACK WindowPro(    HWND hWnd,      UINT wMsg,    WPARAM wParam,    LPARAM  lParam);
  • 对于WSAAsyncSelect来说,其中wMsg为网路事件发生时发给窗口过程的消息;wParam为发生网络事件的套接字;lParam高字节为可能发生的错误,可用宏WSAGETSELECTERROR获得lParam参数低字节的错误信息;lParam低字节为发生的网络事件,可用WSAGETSELECTEVENT返回lparam低字节。

一个模型如下:

define WM_SOCKET WM_USER+1  int WINAPI WinMain(....) {     .....      HWND window=CreateWindow(...);      ....      WSAAsyncSelect(s,window,WM_SOCKET,FD_ACCEPT|FD_CLOSE);      ...  }  BOOL CALLBACK ServerPro(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lparam)  {      switch(wMsg)    {       case WM_PAINT:           ....       break;       case WM_SOCKET:          if(WSAGETSELECTERROR | lParam)           {              .....              break;           }            switch(WSAGETSELECTEVENT(lPram))                {               case FD_ACCPET:               accept=accept(wParam,NULL,NULL);               WSAAsyncSelect(accept,window,WM_SOCKET,FD_READ|FD_WRITE|FD_CLOSE);                   break;                case  FD_FD_READ:                break;                .......            }            break;    }    return TRUT;}

其中FD_WRITE事件发生的条件:

1)使用conenct或WSAConnect,一个套接字首次建立连接

2)使用accept或WSAAccept,套接字被接收之后。

3)若send、WSASend、sendto或WSASendTo调用失败后,返回WSAEWOULDBLOCK错误,而且缓冲空间变得可用时。
 WSAAsyncSelect模型优点是可以子啊花销不大情况下处理多个连接,缺点是必须建立窗口。 

 4.WSAEventSelect模型

        其与WSAAsyncSelect一样,都运行在一个或多个套接字上接收网络事件,不同之处在于WSAEventSelect网络事件通知是由事件对象句柄完成的,而不是窗口。事件通知模型需要应用程序使用的每个套接字,首先建立一个事件对象,通过函数WSACreateEvent来实现。WSACreateEvent函数返回的是一个人工重置的事件对象句柄。

其定义如下:

WSAWVENT WSACreateEvent(void);//创建开始处于未传信状态

创建了事件对象句柄后,就需要将其连接到一个套接字上,同时注册感兴趣的网络事件,可以通过WSAEventSelect函数来实现的,其定义如下:

int WSAEventelect(   SOCKET s,   WSAEVENT hEventObject,   long lNetworkEvents);
  • s:为程序感兴趣的套接字。
  • hEventObject:为要关联的事件对象句柄。
  • lNetworkEvents:为感兴趣的网络事件,与WSAAsyncSelect一样。

        WSAEventSelect有两种工作状态(传信和未传信)和工作模式(人工重置和自动重置)。WSACreateEvent在一个为未传信的状态下,建立一个人工重置的事件对象。当网络事件触发了相关的事件对象后,事件对象将从未传信变为传信状态。由于其为人工重置,所以在处理完后,需调用WSAResetEvent函数将事件对象重置为未传信。其定义如下:

BOOL WSAResetEvent(WSAEVENT hEvent);

应用程序完成对摸个事件对象的处理后,将调用WSACloseEvent函数关闭事件对象。其定义如下:

BOOL WSACloseEvent(WSAEVENT hEvent);

上面两个函数调用成功都返回TRUE,失败都返回FALSE。

      套接字和一个事件对象关联后,应用程序就可以进行I/O处理了:这就需要应用程序等待网络事件触发事件句柄的工作状态。WSAWaitForMultipleObject函数的设计宗旨就是用来等待一个或多个事件对象句柄。该函数会在事先设定一个或多个事件在相关联的套接字的网络事件触发时变为已传信后返回,或者等待超时后返回。其定义如下:

DWORD WSAWaitForMultipleEvents(     DWORD cEvents,     const WSAENENT FAR* lphEvents,     BOOL fWaitAll,     DWORD dwTimeOut,     BOOL  fAlertable);
  • cEvents:为事件对象的数量。
  • lphEvents:为事件对象数组。
  • fWaitAll:TRUE是指所有事件对象变为传信后才返回;FALSE表示数组中有一个事件对象变为传信后就返回。
  • dwTiemOut:表示等待的时间,时间一到就返回(返回WSA_WAIT_TIMEOUT)。设为0,表示无限等待,只有对象为传信才返回。
  • fAertable:对于该模型来说,设为FALSE就可以了。
  • 返回值:其表示等待的与重叠操作完成的那个关联时间的数组索引。

        应用程序通过返回的值,来判断是哪个套接字的网络事件被触发。当知道哪个套接字被触发后,就可以通过WSAEnumNetworkEvents函数,来判断是发生了哪些网络事件。其定义如下:

int WSAEnumNetworkEvents(   SOCKET s,   WSAEVENT hEventObject,    LPWSANETWORKEVENTS lpNetworkEvents);

hEventObject:可选参数,选择后将使事件对象变为未传信,不选就使用WSAResetEvent将事件设为未传信。

lpNetworkEvents:为指向WSANETWORKEVENTS结构体,用于判断套接字上发生的网络事件或者出现的错误代码。
WSANETWORKEVENTS定义如下:

typedef struct _WSANETWORKEVENTS{   long lNetworkEvents,   int iErrorCode[FD_MAX_EVENTS]}WSANETWORKEVENTS,FAR *LPWSANETWORKEVENTS;

lNetworkEvents:为一个网络事件

iErrorCode:为一个错误代码数组,每个网络事件都有一个索引。

 

下面是WSAEventSelect模型的一个架构:

SOCKET SocketArray[WSA_MAINUM_WAIT_EVENTS];WSAEVENT EventArraY[WSA_MAINUM_WAIT_EVENTS];.......newEvent=WSACreateEvent();WSAEventSelect(Listen,newEvent,FD_ACCEPT|FD_CLOSE);listen(Listen,5);....while(TRUE){     Index=WSAWaitForMultipleEvent(EventToal,EventArray,FALSE,WSA_INFINETE,FALSE);    Index=Index-WSA_WAIT_WAIT_0;    for(int i=Index;i<EventTotal;i++)//遍历所有事件,查看不传信的事件是否多于一个   {      Index=WSAWaitForMultipleEvent(1,&EventArray[Index],TRUE,1000,FALSE);      if((Index==WSA_WAIT_FAILED)||(Index==WSA_WAIT_TIMEOUT))          continue;      else      {          Index=i;          WSAEnumNetworkevents(SocketArray[Index],EventArray[Index],&NetworkEvents);          if(Nerworkvents.lNetworkEvents & FD_ACCEPT)          {              if(Nerworkvents.iErrorCode[FD_ACCEPT_BLT]!=0)            {                  错误处理;                  break;             }             连接请求处理           }         if(Nerworkvents.lNetworkEvents & FD_READ)         {              if(Nerworkvents.iErrorCode[FD_READ_BLT]!=0)            {                  错误处理;                  break;             }             连接请求处理           }           ............      }    } }

 WSAEventSelect其结构简单,不需要窗口。但其不足是最多只能等待64个事件。

5.重叠I/O

       重叠I/O是系统性能更佳,其原理是让应用程序使用重叠的数据结构,一次传递一个或多个Winsock I/O请求。针对那些提交的请求,在它们完成后,应用程序可为它们提供服务。这种机制可通过ReadFile和WriteFile函数,在设备上进行I/O操作。

       Winsock 重叠I/O开始只能在Windows NT上的Winsock 1.1使用,应用程序使用对套接字句柄调用ReadFile和WriteFile函数函数,同时指定重叠结构。后来,在Winsock 2中,重叠I/O集成到了新的Winsock函数中,如WSASend和WSARecv。

      要想在一个套接字上使用重叠I/O,首先必须创建一个设置了重叠标志的套接字。然后将套与一个本地接口绑定在一起,然后就可以进行重叠I/O操作。方法是调用下了Winsock函数,同时指定一个可选的WSAOVERLPPED结构:

      函数有:WSASend、WSARecv、WSASendTo、WSARecvFrom、WSAloctl、WSARecvMsg、AcceptEx、ConnectEx、TransmitFile、TransmitPackets、DisconectEx和WSANSPloctl。使用这些函数需要使用WSAOVERLPPED结构作为参数,函数会立即完成调用并返回,不管套接字是否处于阻塞模式。这些函数使用WSAOVERLPPED结构来管理I/O请求的完成。有两种方法来管理:应用程序可以通过等待事件对象通知,也可通过完成例程,对已经完成的请求加以处理。上面的函数,出AcceptEx外还有一个参数:WSAOVERLPPED_COMPLETION_ROUTINE。该参数是一个可选指针,指向一个完成例程函数,该函数在冲抵请求完成后调用。

 事件通知

        重叠I/O的事件通知方式是将windows事件对象和WSAOVERLPPED结构关联起来,使用想WSARecv和WSASend函数会立即返回。这些调用通常会返回SOCKET_ERROR错误,通过WSAGetLastError便会返回一个与WSA_IO_PENDING错误状态相关的一个报告,表示I/O操作正在完成。应用程序通过与WSAOVERLPPED结构相关的事件对象来判断摸个重叠I/O请求何时完成。WSAOVERLPPED结构定义如下:

typedef struct WSAOVERLPPED{    DWORD Internal;    DWORD InternalHigh;    DWORD Offest;    DWORD OffestHigh;    WSAEVENT hEvent;}WSAOVERLPPED,FAR* LPWSAOVERLPPED;
  • Internal、InternalHigh、Offest和OffestHigh有系统内部使用,不能由应有程序直接进行处理或使用。
  •  hEvent:要关联的事件对象。

 一个重叠I/O完成后,与WSAOVERLPPED结构关联的事件会有未传信变为传信状态。因此可以用WSAWaitForMultipleEvent函数(最多等待64个事件对象)来判断重叠I/O何时完成。确认某个重叠I/O完成后,就可以调用WSAGetverlappedResult函数来判断这个重叠I/O是成功还是失败。函数的定义如下:

BOOL WSAGetOverlappedResult(    SOCKET s,    LPWSAOVERLAPPED  lpOverlapped,    LPDWORD lpcbTransfer,    BOOL fWait,    LPDWORD lpdwFlags);
  • s:为重叠I/O操作的那个套接字。
  • lpOverlapped:为传给重叠操作的那个WSAOVERLPPED结构。
  • lpcbTrabsfer:是一个指向DWORD变量的指针,该变量负责接收一次重叠发送或接收操作实际完成的字节数。
  • fWait:决定是否应该等待重叠操作完成。设为TRUE,表示要等待操作完成才会返回;设为FALSE,不等待重叠操作完成,函数会返回FALSE,同时返回一个WSA_IO_INCOMPLETE错误。
  • lpwsFlags:一个指向DWORD变量的指针,用于接收结果标志。
  • 返回值:成功返回TRUE,并且lpcbTrabsfer参数指向的值已经更新;失败返回FALSE,lpcbTrabsfer参数不会变,可以通过调用WSAGetLastError获取失败原因。其原因有:1)重叠I/O操作仍然处于等待状态  2)重叠操作已经完成,但含有错误 3)传给函数的参数有错,无法判断重叠操作的状态。

完成例程

         完成例程是一些函数,我们将这些函数传递给重叠I/O请求,让系统在重叠操作完成后调用。为了使用完成例程我们需要为Winsock函数指定一个完成例程,同时指定一个WSAOVERLPPED结构。一个完成例程函数的原型如下:

void CALLBACK CompletionROUTINE(    DWORD dwError,    DWORD cbTransferred,    LPWSAOVERLAPPED lpOverlapped,    DWORD dwFlags);
  • dwError:表明一个重叠I/O的完成状态是什么。
  • cbTransferred:表明重叠I/O期间,实际传输的字节量是多大。
  • lpOverlapped:传递到最初重叠操作的WSAOVERLAPPED结构。
  • dwFlags:返回操作结束时可能的标识。

       用一个完成例程提交的重叠I/O和用一个事件对象提交的重叠请求间有一个重要区别,就是:WSAOVERLAPPED结构的事件字段未被使用,即不能将事件对象和重叠请求关联在一起。为了让等地线程调用完成例程提供的服务,必须将完成例程置于一种警觉的等待状态。当重叠I/O完成后,对完成例程加以处理。可以使用WSAWaitForMultipleEvent函数来实现,但参数bAltertable必须设为TRUE。这个等待函数用于等待重叠I/O操作完成后,自动执行完成例程,当一个完成例程成功执行后,该等待函数会返回WAIT_IO_COMPLITION。也可以使用SleepEx函数来实现。其定义如下:

DORD SleepEx(    DWORD dwMilliseconds,    BOOL  bAltertable ); 
  • dwMilliseconds:一毫秒为单位的等待时间。设为INFINITE,表示无限时间。
  • bAltertable:设为TRUE,完成例程会得到执行,同时返回WAIT_IO_COMPLITION;设为FALSE,而且进行了一次I/O完成回叫,那么完成函数不会被调用,且函数不会被返回,除非时间用完。

       优点是这种模型的应用程序通知缓冲区收发系统直接使用数据,和前面几种不同。也就是说这种模型,如果应用程序提供了一个接收缓冲区,且数据已经到了,那么数据会被直接复制到接收缓冲区中。前面几种模型,是数据到了后,通知应用程序,应用程序通过调用接收函数,将数据拷贝到缓冲区中。缺点是最多能等待64 事件。

6.完成端口

        完成端口适用于要处理的套接字多的情况。完成端口是windows采用的一种I/O构造机制,除了套接字句柄之外,还可接受其他东西。完成端口模型首先创建一个windows完成端口对象,该对象通过指定数量的线程,对重叠I/O操作管理,一遍对已经完成的重叠操作提供服务。创建完成端口可以使用CreateIoCompletionPort函数,该函数将句柄关联大完成端口上,其定义如下:

HANDLE CreateIoCompletionPort(   HANDLE FileHandle,   HANDLE ExistingComletionPort,     DWORD CompletionKey,   DWORD NumberOfConcurrentThreads);

FileHandle:与完成端口关联在一起的套接字句柄。

ExistingComletionPort:标识是一个现有的完成端口套接字句柄已经于他关联在一起。

CompletionKey:要与某个特定套接字句柄关联在一起的蛋句柄数据。该参数用于保存于套接字对应的任意类型的信息。

NumberOfConcurrentThreads:用于设置在完成端口上同时执行的线程数量,设置为0,表示有多少处理器就运行同时执行多少线程。

eg:CompletionPort=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);

       在创建了完成端口对象后,就可以将套接字句柄和对象关联起来。在关联套接字前,需要建立一个或多个工作器线程,以便在套接字的I/O请求投递给完成端口对象后,为完成端口提供服务。关联再次调用CreateIoCompletionPort函数,对前3个参数进行设置。    
       在本质上,完成端口利用了windows重叠I/O模型。在某个时间,判断OVERLAPPED结构来检索调用结果。在完成端口中,可以通过函数GetQueuedComplrtionStaus来实现,让一个或多个工作器线程在完成端口上等待。其定义如下: 

BOOL  GetQueuedCompletionStatus(   HANDLE CompletionPort,   LPDWORD  lpNumberOfBytesTransferred,   PULONG_PTR lpComplrtionKey,   LPOVERLAPPED *lpOverlapped,   DWORD dwMiliseconds);
  • CompletionPort:表示对应于线程所在的完成端口。
  •  lpNumberOfBytesTransferred:负责在一次完成了一次I/O操作后,实际传输的字节数。
  • lpComplrtionKey:单句柄数据。
  • lpOverlapped:用于接收已完成I/O操作的OVERLAPPED结构。
  • dwMiliseconds:用于指定等待完成数据包到完成端口的等待时间。设为INFINITE,表示无时间限制。

终止所有的线程,调用PostQuenuedCompletionStatus函数来指示每个线程立即结束并退出。

原创粉丝点击