首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 其他教程 > 互联网 >

Winsock I/O步骤

2013-10-29 
Winsock I/O方法1.blocking(阻塞)模型最简单的模式,也是最基础的模式。2.select模型其使用select函数实现对

Winsock I/O方法


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=connect(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。

Winsock I/O步骤

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

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函数来实现。也可以使用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函数来指示每个线程立即结束并退出。

热点排行