Winsock I/O 学习笔记

时间:2022-06-10 03:06:14

Winsock分别提供了“套接字模式”和“套接字I/O模型”,可对一个套接字上的I/O行为加以控制。(两者无关)
套接字模式的用途:
决定在随一个套接字调用时,那些winsock函数的行为。
描述了一个应用程序如何对套接字上进行的I/O进行管理及处理。
套接字模型的出现,正是为了解决套接字模式存在的某些限制。
Winsock提供了两中套接字模式:锁定和非锁定。
(下面的代码均为服务器代码)
套接字模式:
锁定模式:
在此模式下,在I/O操作完成前,执行操作的winsock函数(比如send和recv)会直接等候下去,不会立即返回程序(将控制权交还给程序)。
对于处于锁定模式的套接字,因为在一个锁定的套接字上的调用任何一个winsock API函数,都会产生相同的后果——耗费或长或短的时间“等待”。
大多数的winsock应用都是遵照“生产者—消费者”模型来编制的,这种模型中,应用程序需要读写指定量的字节,然后以它为基础执行一些计算。
example:
 SOCKET sock;
 char buf[256];
 int done = 0;
 while( !done )
 {
  nBytes = recv ( sock, buff, 265 );
  if( nBytes == SOCKET_ERROR )
  {
   printf ( “recv failed with error %d /n”, WSAGetLastError() );
   return;
  }
  DoComputaitonOnData(buff);
 }
待决问题:死锁为基,防止数据缺乏时,造成应用程序完全陷于“凝固”状态,同时不必连续性地监视系统网络缓冲。
解决方法:
 ★ 将应用程序划分为一个读线程,和一个计算线程。两线程共享同一个缓冲区。
example:
 CRITICAL_SECTION  data;
 HANDLE    hEvent;
 TCHAR    buff [MAX_BUFFER_SIZE];
 Int      nbytes;
 
 …………….
 //Reader thread
 void ReadThread(void)
 {
  int  nTotal = 0;
   nRead = 0;
   nLeft = 0;
   nBytes = 0;
  while (!done)
  {
   nTotal = 0;
   nLeft = NUM_BYTES_REQUIRED;
   while ( nTotal != NUM_BYTES_REQUIRED )
   {
    EnterCreiticalSection (&data);
    nRead = recv(sock, &buff[MAX_BUFFER_SIZE –nBytes]), nLeft);
    If ( nRead == -1 )
    {
     printf(“error/n”);
     ExitThread();
    }
    nTotal += nRead;
    nLeft -= nRead;
 
    nBytes += nRead;
    LeaveCriticalSection(&data);
   }
   SetEvent(hEvent);
  }
 }
 //Computation thread
 void ProcessThread(void)
 {
  WaitForSingleObject(hEvent);
  EnterCriticalSection(&data);
  DoSomeComputation(buff);
  //Remove the processed data from the input
  //buffer, and shift the remain data to the start
  //of the array
  nBytes -= NUM_BYTES_REQUIRED;
  LeaveCriticalSection(&data);
 }
锁定模式的缺点在于:应用程序很难同时通过多个建好连接的套接字通信。使用上面的办法,可对应用程序进行修改,令其为连好的没个套接字都分配一个读线程和一个计算线程,虽然增大了一定开销,但的确是一种可行的方案。唯一的缺点就是扩展性极差。
非锁定模式
它除了具备锁定套接字已有的各项优点之外,还进行了少许扩充,功能更强大。
创建方法:
 SOCKET   s;
 unsigned long  u1 = 1;
 int      nRect;
 
 s = socket (AF_INET, SOCK_STREAM, 0);
 nRect = ioctlsocket(s, FIOBIO, (unsigned long*) &u1 );
 if (nRect == SOCKET_ERROR)
 {
  //Failed to put the socket int nonblocking mode
 }
将一个套接字设置为非锁定模式后,winsock API调用会立即返回。大多数情况下,这些调用都会“失败”,并返回一个WSAEWOULDBLOCK错误。它意味着请求的操作在调用期间没有时间完成。
由于非锁定调用会频繁返回WSAEWOULDBLOCK错误,所以在任何时候都应仔细检查所有的返回代码,并作好“失败”的准备。
 ~~ioctlsocket~~
 int ioctlsocket ( SOCKET s, long cmd, u_long* argp );       
 是用来控制socket的I/O模式控制的函数。
 param:
  s:  [in]指定的套接字描述符
  cmd: [in]在套接字上执行指令
  argp  [in out]一个指向cmd中一个参数的指针
 mark:
octlsocket方法能被用于处在任何状态的任何套接字。用它来设置或获得相关套接字的操作参数,独立于协议和通信子系统。
argp参数是一个指向u-long类型值的指针,如果是要激活非锁定模式则设置它为非零值,如果不使用非锁定模式就设置为零。
套接字I/O模型
包括select(选择)、WSAAsyncSelete(异步选择)、WSAEventSelect(事件选择)、overlapped(重叠)、completion port(完成端口)。
select模型
利用selete函数,实现对I/O的管理。
  利用selete函数来判断套接字上是否存在数据,或者能否向一个套接字写入数据。
设计这个函数的唯一目的是防止应用程序在套接字处于锁定模式中时,在一次I/O绑定调用(send或recv)过程中,*进入“锁定”状态;同时防止在套接字处于非锁定模式中残生WSAEWOULDBLOCK错误,除非满足事先用参数规定的条件,否则selete函数会在进行I/O操作时锁定。

 

~~select~~
int selete ( int nfds, fd_set FAR * readfds, fd_set FAR * writefds, fd_set FAR * exceptfds,
  const struct timeval FAR * timeout );             
param:
 第一个参数nfds会被忽略,提供此参数只是为了保持兼容。
 从根本上说,fd_set数据类型代表着一系列特定套接字的集合。
 readfds:  检查可读性(包括对象套接字的符合条件:)
    ▲ 有数据可以读入
    ▲ 连接已经关闭、重设或中止
 ▲ 假如已调用了listen,而且一个连接正在建立,那么accept函数调用会成功
 writefds:  检查可写性(包括对象套接字的符合条件:)
    ▲ 有数据可以发出
    ▲ 如果已完成了对一个非锁定连接调用的处理,连接就会成功
 exceptfds: 用于例外数据(包括对象套接字的符合条件:)
    ▲ 如果已完成了对一个非锁定连接调用的处理,连接尝试就会失败
    ▲ 有带外(OOB)数据可供读取
 timeout:  用于决定selete最多等待I/O操作完成多久的时间。如为空指针,那么其调用会无限期地“锁定”或停顿下去,直到至少有一个描述符号符合指定的条件后结束。
 对fd_set进行处理与检查的宏:
  ▲ FD_CLR (s, *set)  从set中删除套接字s
  ▲ FD_ISSET ( s, *set) 检查s是不是set的成员
  ▲ FD_SET ( s, *set)  将套接字s加入集合set
  ▲ FD_ZERO ( s, *set) 将set初始化成空集合
  selete操作步骤:
使用ZERO,初始化自己感兴趣的每个集合
使用SET,将套接字句柄分配给自己感兴趣的每个集合
调用selete方法
根据slelete返回值,判断哪写套接字存在着尚未完成的I/O操作(使用ISSET)。
知道了每个集合中的“待决”的I/O操作后,对I/O进行处理后返回步骤1。
example:
 SOCKET  s;
 fd_set   fdread;
 Int ret;
 //创建一个套接字并且接受一个连接
 。。。。。。
 //管理套接字I/O
 while( TRUE )
 {
 //在调用select之前清空集合
 FD_ZERO(&fdread);
 //将s套接字加入读集合
 FD_SET( s, &fdread );
 If( ret = selete( 0, &fread, NULL, NULL, NULL)) == SOCKET_ERROR )
 {
  //错误条件
 }
 if( ret > 0 )
//这个例子中,select应该返回1。一个应用程序可能处理若干这个套接字,所以//这个值会大于1。这时你的应用程序应该检查每个套接字是不是存在于集合
if ( FD_ISSET ( s, &fread ) )
{
// 在s套接字上曾发生了一个读事件
}
 } 
WSAAsyncSelect
提供了一个有用的异步I/O模型。利用这个模型,应用程序可在一个套接字上接收以windows消息为基础的网络事件通知。
消息通知
首先必须用CreateWindow函数创建一个窗口,再为该窗口提供一个窗口例程支持函数(winproc)。
~~WSAAsyncSelect~~
int WSAAsyncSelect ( SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent);
param:
 hWnd: 指定一个窗口句柄,它对应于网络事件发生之后,想要收到通知消息的那个窗口或对话框。
 wMsg: 指定在发生网络事件时,打算接收的消息。该消息会投递到由hwnd窗口句柄指定的那个窗口。
 LEvent: 指定的是一个位掩码,对应一写列的网络事件的组合。(0则停止通知)
  事件类型 (常用红色)       含义
  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  。。。
 例子:
  WSAAsyncSelect ( s, hwnd, WM_SOCKET,     FD_CONNECT | FD_READ | FD_WRITE | FD_CLOSE );
 在winproc回调函数接受参数的时候要注意:
  lParam的低字:指定了已经发生的网络事件
  lParam的高字:包含了可能出现的任何错误代码
 同网络事件消息抵达一个窗口例程后,应用程序首先应检查lParam的高字位,
 以判断是否在套接字上发生了一个网络错误。
 这里有一个特殊的宏:WSAGETSELECTERROR,可用它返回高字位包含的错
 误消息。
 如果没有任何错误,便可读取lParam的低字位的内容。
 一个特殊的宏:WSAGETSELECTEVENT返回低字部分。
   example:
#define WM_SOCKET  WM_USER + 1
#include <windows.h>
int WINAPI WinMain(  HINSTANCE hInstance, HINSTANCE hPrevInstance,
     LPSTR lpCmdLine, int nCmdShow )
{
 SOCKET Listen;
 HWND  hWnd;
 //创建窗口指定窗口例程
 Window = CreateWindow();
 //开始创建套接字
 WSAStartup(…);
 List = socket();
 sockaddr_in InternetAddr;
 InternetAddr.sin_family = AF_INET;
 InternetAddr.sin_adrr.s_un.s_addr = htonl(INADDR_ANY);
 InternetAddr.sin_port = htons(5150);

 Bind( Listen, (SOCKADDR*)& InternetAddr, sizeof(SOCKADDR));
 ………..
 linsten(Listen, 5);
 
 //消息循环
}

BOOL CALLBACK ServerWinProc(  HWND hDlg, WORD wMsg, WORD wParam,
        DWORD lParam )
{
 SOCKET Accept;
 Switch( wMsg )
 {
  case WM_PAINT:
   break;
  case WM_SOCKET:
   if ( WSAGETSELECTERROR(lParam))
   {
    //提示错误
    closesocket( wParam );
    break;
   }
   switch(WSAGETSELECTEVENT(lParam))
   {
    case FD_ACCEPT:
     Accept = accept(wParam, NULL, NULL );
     WSAsynSlect(Accept, hwnd, WM_SOCKET,
        FD_READ | FD_WRITE | FD_CLOSE );
     break;
    case FD_READ:
     //从wparam中得到套接字数据
     break;
    case FD_WRITE:
     break;
    case FD_CLOSE:
     closesocket(wParam);
     break;
   }
   break;
 }
 return TRUE;

如何对FD_WRITE事件通知进行处理:
只有三种情况下,才会发出该通知:
 § 使用connect或WSAConnect,一个套接字首次建立了连接
 § 使用accept或WSAAccept,套接字被接受后
 § 若send,sendto,WSASend,WSASendTo操作失败,返回了错误,而且缓冲区的空间变的可用。
WSAEventSelect
和WSAAsyncSelect模型类似处:
允许应用程序在一个或多个套接字上,接受有以事件为基础的网络事件通知
对网络事件宏,他们都可直接移植到新的模型
主要的差别就在于:网络时间会投递到一个事件对象句柄,而非投递到一个窗口的例程。
事件通知
模型要求应用程序针对打算使用的每个套接字,首先创建一个事件对象。
WSAEVENT WSACreateEvent(void);
返回事件对象的句柄,把句柄与某个套接字关联在一起,同时注册自己感兴趣的网络事件类型。
Int WSAEventSelect ( SOCKET s, WSAEVENT hEventObject, long lNetworkEvents)
用WSAEventSelect创建的事件有两种工作状态:(已传信   和   未传信)
用WSAEventSelect创建的事件有两种工作模式:(人工重设 和 自动重设) 
~Begin~ <未传信,人工重设> ~关联后事件触发~<已传信>~完成一个I/O请求后~<更变为未传信>~循环~

设置已传信到未传信状态:
BOOL WSAResetEvent( WSAEVENT hEvent );
释放时间句柄使用的系统资源:
BOOL WSACloseEvent( WSAEVENT hEvent );
当套接字同一个事件对象句柄关联以后,应用程序便可开始I/O处理;方法是等待网络事件触发时间对象句柄的工作状态。
DWORD WSAWaitForMultipleEvents (         
  DWORD     cEvents,        
  Const WSAEVENT FAR  *lphEvents,       
  BOOL      fWaitALL,       
  DWORD     dwTimeOut,       
  BOOL      fAlertalbe );       
Param:
 CEvents && lphEvents 定义了一个WSAEVENT对象构成的数组。
 Cevents是对象的数量。
函数功能:用来等待一个或多个事件对象句柄,并在事先指定的一个或所有句柄进入“已传信”状态后,或在超过一个规定时间周期后,立即返回。
判断到底是事件数组中哪个事件对象触发,需要进行:
index = WSAWaitForMultipleEvents(…..);
MyEvent = EventArray[index – SWA_WAIT_EVENT_0];
知道造成网络事件的套接字后,调用WSAEnumNetworkEvents函数,调查发生了什么类型的网络事件。
Int WSAEnumNetwordEvents (          
  SOCKET      s,       
  WSAEVENT      hEventObject,     
  LPWSANETWORKEVENTS  lpNetworkEvents );    
Param:
 hEventObject:  可选参数,重设对应事件对象的状态。
  lpNetworkEvents: 用于接收套接字上发生的网络事件类型以及可能出现的任何
 错误代码
typedef struct _WSANETWORKEVENTS
{
 long  lNetWorkEvents;
 int   iErrorCode[FD_MAX_EVENTS];
} WSANETWORDEVENTS, FAR * LPWSANETWORKEVENTS;
注意:当一个事件进入传信状态时,可能会同时发生多个网络事件类型。如:一个繁忙的服务器可能同时收到FD_READ && FD_WRITE通知。
指针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 I/O模型的示范服务器代码
SOCKET Socket[WSA_MAXIMUM_WAIT_EVENTS];
WSAEVENT Event[WSA_MAXIMUM_WAIT_EVENTS];
SOCKET Accept, Listen;
DWORD EventTotal = 0;
DWORD Index;
//Set up a TCP socket for listening on port 5150
Listen = socket( AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN internetAddr;
InternetAddr.sin_family = AF_INET;
InternetAddr.sin_port = htons(5150);
InternetAddr.sin_addr.s_un.s_addr = htonl(INADDR_ANY);

If(  bind(Listen,(SOCKADDR*)& internetAddr,sizeof(SOCKADDR)) ==
 INVAILD_SOCKET )
{
 printf(“sock() failed with error %d/n”, WSAGetLastError());
 return ;
}

NewEvent = WSACreateEvent();
WSAEventSelect(Listen, NewEvent, FD_ACCEPT | FD_CLOSE);
listen(Listen,5);
SOCKET[EventTotal] = Listen;
Event[EventTotal] = NewEvent;
EventTotal++;
while(TRUE)
{
 Index = WSAWaitForMultipleEvents( EventTotal, EventArray, FALSE,
         WSA_INFINITE, FALSE );
 WSAEnumNetworkEvents(
  SocketArray[Index – WSA_WAIT_EVENT_0],
  EventArray[Index – WSA_WAIT_EVENT_0],
  &NetworkEvents );
    
 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 – WSA_WAIT_EVENT_0],
      NULL, NULL );
  //我们不能处理大于数组的套接字,所以关闭他。
  If(EventTotal > WSA_MAXNUM_WAIT_EVENTS)
  {
   printf( “Too many connections”);
   closesocket(Accept);
   break;
  }
  NewEvent = WSACreateEvent();
  WSAEventSelect(Accept, NewEvent, FD_READ | FD_WRITE |
 FD_COLSE );
       Event[EventTotal] = NewEvent;
 Socket[EventTotal] = Accept;
 EventTotal++;
 Printf(“Socket %d connected/n”, Accept );
 }
 
 if (NetworkEvents.lNetworkEvents & FD_READ)
 {
  if(NetworkEvents.iErrorCode[FD_READ_BIT] != 0)
  {
   printf(“FD_READ failed with erro %d/n”,
    NetworkEvents.iErrorCode[FD_READ_BIT]);
   break;
  }
  recv(Socket[Index – WSA_WAIT_EVENT_0],
 buffer, sizeof(buffer), 0 );
 }
 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(Socket[Index – WSA_WAIT_EVENT_0],
 buffer, sizeof(buffer), 0);
 }


 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]);
   }   
  closesocket(Socket[Index – WSA_WAIT_EVENT_0]);
  CompressArrays(Event, Socket, &EventTotal );
 }
 
}

 

1   重叠模式
重叠I/O模型使应用程序能达到更佳的系统性能。
想在一个套接字上使用重叠I/O模型,首先必须使用WSA_FLAG_OVERLAPPED这个标志,创建一个套接字。如:
s = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED );
如果使用的是socket函数,而非WSASocket函数,那么会默认设置WSA_FLAG_OVERLAPPED标志。然后绑定就可以进行重叠I/O操作了,方法是调用winsock函数,容市指定一个WSAOVERLAPPED结构(可选):
WSASend
WSASendTo
WSARecv
WSARecvFrom
WSAIoctl
AcceptEx
TransmitFile
若随一个WSAOVERLAPPED结构一起调用这些函数,函数会立即完成并返回,无论套接字是否设为锁定模式。他们以来于WSAOVERLAPPED结构来返回一个I/O请求的返回。
管理一个重叠I/O请求的完成有两个主要方法:事件通知 和 完成例程
事件通知
WSAOVERLAPPED结构在一个重叠I/O请求的初始化,及其后续的完成之间,提供了一种沟通或通信的机制。
typedef struct WSAOVERLAPPED
{
 DWORD  Internal;
 DWORD  InternalHigh;;
 DWORD  Offset;
 DWORD  OffsetHigh;
 WSAEVENT hEvent;
} WSAOVERLAPPED, FAR *LPWSAOVERLAPPED;
param:
 前4个参数均由系统在内部使用,不应由应用程序直接进行处理或使用。
hEvent字段有点特殊,它允许应用程序将一个事件对象句柄和一个套接字关联起来。
发现一次重叠请求完成之后,接着需要调用WSAGetOverlappedResult(取得重叠结构)函数,判断那个重叠调用到底成功还是失败了,其定义:
BOOL WSAGetOverlappedResult (
 SOCKET s,
 LPWSAOVERLAPPED lpOverlapped,
 LPDWORD lpcbTransfer,
 BOOL fWait,
 LPDWORD lpdwFlags );
Param:
 LpcbTransfer:  负责接收一次重叠发送或接收操作实际传输的字节数
 FWait:   用于决定函数是否应该等待一次待决(未决)的重叠操作完成
     若将其设为TRUE,除非操作完成,否则不返回;
     若FALSE,而且操作仍然处于“待决”状态,那么 WSAGetOverlappedResult返回FALSE值,容市返回一个 WSA_IO_INCOMPLETE(I/O操作未完成)错误。
  Returns:
   TRUE: 重叠I/O操作成功完成,而且由lpcbTransfer参数指向的值也已经更新
   FALSE: 原因:(用WSAGetLastError调查到底是什么原因造成了调用失败)
     ■ 重叠I/O操作仍处于“待决”状态
     ■ 重叠操作已经完成,但含错误。
      ■ 重叠操作的完成状态不可判决,因为在提供给它的一个或多个参数
 中有错误存在。
  编程步骤:
创建一个套接字,开始在指定的端口上监听连接请求。
接受一个进入的连接请求。
为接受的套接字新建一个WSAOVERLAPPED结构,并在该结构分配一个事件对象句柄。也将事件对象分配一个事件数组,以便稍后由WSAWaitForMultipleEvents函数使用。
在套接字上投递一个异步WSARecv请求,指定参数为WSAOVERLAPPED结构。(通常函数会失败告终,返回SOCKE-ERROR错误状态WSA-IO-PENING(I/O尚未完成)
使用c步骤的事件数组,调用WSAWaitForMultipleEvents函数,并等待与重叠调用关联在一起的事件进入“已传信”状态(等待事件的“触发”)。
WSAWaitForMultipleEvents完成后,针对事件数组调用WSAResetEvent(重设事件)函数,从而重设事件对象,并对完成的重叠请求进行处理。
使用WSAGetOverlappedResult函数,判断重叠调用的返回状态是什么。
在套接字上投递另一个重叠WSARecv请求
重复步骤e)到h)。
采用时间机制的简单重叠I/O处理机制
void main(void)
{
 WSABUF DataBuf;
 DWORD EventTotal = 0;
 WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];
 WSAOVERLAPPED AcceptOverlapped;
 SOCKET ListenSocket, AcceptSocket;

 // Step 1:
 // Start Winsock and set up a listening socket
 …….
 // Step2:
 // Accept an inbound connection
 AcceptSocket = accept( ListenSocket, NULL, NULL );
 // Step3:
 // set up an overlapped structure
 EventArray[EventTotal] = WSACreateEvent();
 ZeroMemory ( &AcceptOverLapped, sizeof( WSAOVERLAPPED));
 AcceptOverlapped.hEvent = EventArray[EventTotal];
 DataBuf.len = DATA_BUFSIZE;
 DataBuf.buf = buffer;

 EventTotal++;
  
 // Step 4:
 // Post a WSARecv request to begin receiving data on the socket
 WSARecv( AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags,
    &AcceptOverlapped, NULL );
 // Process overlapped receives on the socket
 while(TRUE)
 {
  // Step 5:
  // Wait for the overlapped I/O call to complete
  Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE,
           WSA_INFINITE, FALSE);
  // Index should be 0 because we have only one event hanle in Array
  // Step 6:
  // Determine the status of the overlapped request
  WSAGetOverlappedResult( AcceptSocket, &AcceptOverlapped,
        &BytesTransferred, FALSE, &Flags );
  // First check to see whether the peer has closed the connection,and
  // if so , colse the socket
  if(ByteTransferred == 0)
  {
   printf(“Closing cocket %d/n”, AcceptSocket );
   closesocket(AcceptSocket);
   WSACloseEvent( EventArray[Index – WSA_WAIT_EVENT_0] );
   Return ;
  }
  // Do something with the recived data.
  // DataBuf contains the received data.
  …………………..
  //Step 8:
  // Post another WSARecv() request on the socket
  Flag = 0;
  ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED));
  AcceptOverlapped.hEvent =
 EventArray[Index – WSA_WAIT_EVENT_0];
     DataBuf.len = DATA_BUFFSIZE;
     Databuf.buf = Buffer;
     
     WSARecv( AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags,
 &AcceptOverlapped, NULL  );
 }
}

重叠I/O模型也允许应用程序以一种重叠方式,实现对连接的接受。具体做法是在监听套接字上调用AcceptEX函数。
AcceptEX函数和accept的区别在于,我们必须提供接受的套接字,而不是让函数自动为我们创建。正是由于要提供套接字,所以要求我们事先调用socket或WSASocket函数,创建一个套接字,以便通过sAcceptSocket参数,将其传递给AcceptEX。
BOOL AcceptEX (             
 SOCKET sListenSocket,   //监听套接字     
 SOCKET sAcceptSocket,   //负责对进入连接请求的“接受” 
 PVOID lpOutputBuffer,    //特殊缓冲区,负责3种数据接收1、服务器本地地址2、客户机的远程地址3、在新建连接上发送的第一个数据块      
 DWORD dwReceiveDataLength,  //指定lpOutputBuffer区中,保留多大的空间,用来接收数据如果为0,那么连接的接收过程中不再一道接收任何数据  
 DWORD dwLocalAddressLength, //指定lpOutputBuffer区中保留多大空间,在一个套接字被接受的时候,保存本地地址信息  
 DWORD dwRemoteAddressLength, //指定lpOutputBuffer区中保留多大空间,在一个套接字被接受的时候,保存远程地址信息要注意的是:和当前采用的传送协议允许的最大地址长度比较起来,这里指定的缓冲区大小至少多出16个字节。    
 LPDWORD lpdwBytesReceived,  //用于返回接收的实际数据量,只在操作以同步方式下完成的前提下,才会设置这个参数  
 LPOVERLAPPED lpOverlapped ); //允许函数以一种异步方式工作 


GetAcceptExSockaddrs可以从lpOutputBuffer中解析本地和远程地址元素。
VOID GetAcceptExSockaddrs (          
 PVOID lpOutputBuffer,           
 DWORD dwReceiveDataLength,         
 DWORD dwLocalAddressLength,        
 DWORD dwRemoteAddressLength,        
 LPSOCKADDR *LocalSockaddr,  //包含了本地地址信息的结构  
 LPINT localSockaddrLength,  //接收本地地址的长度   
 LPSOCKADDR *RemoteSockaddr, //包含了远程地址信息的结构  
 LPING RemoteSockaddrLength ); //接收远程地址的长度   
完成例程
另一种管理完成的重叠I/O请求的方法。
基本设计宗旨:通过调用者的线程,为一个已完成的I/O请求提供服务。
void CALLBACK CompletionROUTINE (         
 DWORD dwError,   //表明指定一个重叠操作的完成状态是什么。 
 DWORD cbTransferred, //指定重叠操作期间,实际传输的字节量  
 LPWSAOVERLAPPED lpOverlapped,  //指定的是传递到最初I/O调用内的一个WSAOVERLAPPED 
 DWORD dwFlags );  //为0,尚未使用       
  完成例程提交的重叠请求和事件对象提交的重叠请求有重要区别:WSAOVERLAP
PED结构的hEvent事件字段并未使用;也就是说我们不可将一个事件对象同重叠请 请求关联到一起。用完成例程发出一个重叠I/O调用之后,作为我们的调用线程,一旦完成,它最终必须为完成的例程提供服务。这要求将自己的调用线程置于一种“可警告的等待状态”,用WSAWaitForMultipleEvents函数和SleepEX来实现。
DWORD SleepEX ( DWORD dwMilliseconds, BOOL bAlertable );
 aAlertable: FALSE:而且进行了一次I/O完成回调,那么I/O完成函数不会执行,而且函数不会返回,除非超过了dwMilliseconds规定的时间。TRUE,那么完成例程会得到执行,同时SleepEX函数返回WAIT_IO_COMPLETION.
  实现步骤:
新建一个套接字,开始在指定端口上,坚听一个进入的连接。
接受一个进入的连接请求。
为接受的套接字创建一个WSAOVERLAPPED结构在套接字上投递一个异步WSARecv请求,方法是将WSAOVERLAPED指定成为参数,同时提供一个完成例程。
在将fAlertalbe参数为TRUE的前提下,调用WSAWaitForMultipleEvents,并等待一个重叠I/O请求完成后完成例程会自动执行,并返回WSA_IO_COMPLETION,在完成例程内,可随一个完成例程一道,投递另一个重叠WSARecv请求。
检查WSAWaitForMultipleEvents是否返回WSA_IO_COMPLETION。
重复e)f)。

 


采用完成例程的简单重叠I/O处理示例
SOCKET AcceptSocket;
WSABUF  DataBuf;

void main(void)
{
 WSAOVERLAPPED OverLapped;
 //创建监听套接字
 //接受一个新的连接
 AcceptSocket = accept(ListenSocket, NULL, NULL);
 //拥有了一个请求的套接字,开始使用完成例程方法处理重叠I/O
 //获得一个开始处理的OVERLAPPED结构。
 Flags = 0;
 ZeroMemory(&Overlapped, sizeof(WSAOVERLAPPED));
 DataBuf.len = DATA_BUFSIZE;
 DataBuf.buf = Buffer;
 //通过指定的WSAOVERLAPPED结构作为一个参数,在套接字上发出
 //异步WSARecv()请求。
 If(WSARecv( AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags,
 &Overlapped, WorkerRoutine ) == SOCKE_ERROR )
    {
     if(WSAGetLastError() != WSA_IO_PENDING)
     {
      printf(“WSARecv() failed with error %d /n”, WSAGetLastError() );
      return ;
     }
    }
    //在WSAWaitForMultipleEvents()请求等待一个或多个时间对象以后,
    //我们必须创建一个伪事件对象,从2种里选一个,用SleepEX()方法。
    EventArray[0] = WSACreateEvent();
    while( TRUE )
    {
  Index = WSAWaitMultipleEvent( 1, EventArray, FALSE,
 WSA_INFINITE, TRUE );
     If( Index == WAIT_IO_COMPLETION )
     {
   //一个重叠请求程序完成,继续服务。
   break;
     }
     else
     {
      //一个错误发生,终止处理。
      return ;
     }
    }
}

void CALLBACK WorkerRoutine ( DWORD Error,
        DWORD BytesTransferred,
        LPWSAOVERLAPPED Overlapped,
        DWORD inFlags  )
{
 DWORD SendBytes, RecvBytes;
 DWORD Flags;
 If(Error != 0 || BytesTransferred == 0)
 {
  //在套接字上有一个错误发生或套接字在客户端关闭
  return;
 }
 Flags = 0;
 ZeroMemory( &Overlapped, sizeof(WSAOVERLAPPED ));
 DataBuf.len = DATA_BUFSIZE;
 DataBuf.buf = Buffer;
 If( WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags,
 &Overlapped, WorkerRoutine ) == SOCKET_ERROR )
    {
     printf(“WSARecv() failed with erro %d/n”, WSAGetLastError() );
     return ;
    }
}
2  I/O模型问题
客户机开发
如果是令其同时管理一个或多个套接字,那么建议采用重叠I/O或WSAEventSelect模型,以便在一定程度上提升性能。
开发是一个以Windows为基础的应用程序,要进行窗口消息的管理,那么WSAAsyncSelect恐怕是最好的选择,因为其就是从Windows消息模型借鉴过来的。
服务器开发在一个给定的时间,同时控制几个套接字,采用重叠I/O模型如果预计自己的服务器在任何给定的时间,都会为大量I/O请求提供服务,使用I/O完成端口模型。