Overlapped I/O模型--事件通知【摘录自《Windows网络编程》】

原文:http://www.cnblogs.com/NeuqUstcIim/archive/2008/08/16/1269479.html
 
重叠I / O的事件通知方法要求将Wi n 3 2事件对象与W S A O V E R L A P P E D结构关联在一起。若使用一个W S A O V E R L A P P E D结构,发出像W S A S e n d和W S A R e c v这样的I / O调用,它们会立即返回。

    一个重叠I / O请求最终完成后,我们的应用程序要负责取回重叠I / O操作的结果。一个重叠请求操作最终完成之后,在事件通知方法中, Wi n s o c k会更改与一个W S A O V E R L A P P E D结构对应的一个事件对象的事件传信状态,将其从“未传信”变成“已传信”。 由于一个事件对象

已分配给W S A O V E R L A P P E D结构,所以只需简单地调用W S AWa i t F o r M u l t i p l e E v e n t s函数,从而判断出一个重叠I / O调用在什么时候完成。

   注意: W S AWa i t F o r M u l t i p l e E v e n t s返回只是说明重叠IO操作完成,但是是成功的完成还是失败的完成还要调用W S A G e t O v e r l a p p e dR e s u l t(取得重叠结构)函数

    如W S A G e t O v e r l a p p e d R e s u l t函数调用成功,返回值就是T R U E。这意味着我们的重叠I / O操作已成功完成,而且由l p c b Tr a n s f e r参数指向的值已进行了更新。

 

   我们向大家阐述了如何编制一个简单的服务器应用,令其在一个套接字上对重叠I / O操作进行管理,程序完全利用了前述的事件通知机制。对该程序采用的编程步骤总结如下:
1) 创建一个套接字,开始在指定的端口上监听连接请求。
2) 接受一个进入的连接请求。
3) 为接受的套接字新建一个W S A O V E R L A P P E D结构,并为该结构分配一个事件对象句柄。也将事件对象句柄分配给一个事件数组,以便稍后由W S AWa i t F o r M u l t i p l e E v e n t s函数使用。
4) 在套接字上投递一个异步W S A R e c v请求,指定参数为W S A O V E R L A P P E D结构。
注意函数通常会以失败告终,返回S O C K E T _ E R R O R错误状态W S A _ I O _ P E N D I N G
(I/O操作尚未完成)。
5) 使用步骤3 )的事件数组,调用W S AWa i t F o r M u l t i p l e E v e n t s函数,并等待与重叠调用关联在一起的事件进入“已传信”状态(换言之,等待那个事件的“触发”)。
6) WSAWa i t F o r M u l t i p l e E v e n t s函数完成后,针对事件数组,调用W S A R e s e t E v e n t(重设事件)函数,从而重设事件对象,并对完成的重叠请求进行处理。
7) 使用W S A G e t O v e r l a p p e d R e s u l t函数,判断重叠调用的返回状态是什么。
8) 在套接字上投递另一个重叠W S A R e c v请求。

9) 重复步骤5 ) ~ 8 )。


点击(此处)折叠或打开

  1. #include "stdafx.h"
  2.  #include <winsock2.h>

  3.  #define DATA_BUFSIZE 4096

  4.  #pragma comment(lib, "ws2_32.lib")

  5.  int _tmain(int argc, _TCHAR* argv[])
  6.  {
  7.      DWORD EventTotal = 0,RecvBytes = 0, Flags = 0;
  8.      char buffer[DATA_BUFSIZE];

  9.      WSABUF DataBuf;
  10.      WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];
  11.      WSAOVERLAPPED AcceptOverlapped;
  12.      SOCKET Listen,Accept;

  13.      //step1:
  14.      //start Winsock and set up a listening socket
  15.      WSADATA wsaData;
  16.      WSAStartup(MAKEWORD(2,2), &wsaData);

  17.      ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  18.      u_short port = 27015;
  19.      char* ip;
  20.      sockaddr_in service;
  21.      service.sin_family = AF_INET;
  22.      service.sin_port = htons(port);
  23.      hostent* thisHost;
  24.      thisHost = gethostbyname("");
  25.      ip = inet_ntoa (*(struct in_addr *)*thisHost->h_addr_list);

  26.      service.sin_addr.s_addr = inet_addr(ip);

  27.      //-----------------------------------------
  28.      // Bind the listening socket to the local IP address
  29.      // and port number
  30.      bind(ListenSocket, (SOCKADDR *) &service, sizeof(SOCKADDR));

  31.      //-----------------------------------------
  32.      // Set the socket to listen for incoming
  33.      // connection requests
  34.      listen(ListenSocket, 1);
  35.      printf("Listening\n");

  36.      //step2:
  37.      Accept=accept(Listen,NULL,NULL);

  38.      //step3:
  39.      //set up an overlapped structure
  40.      EventArray[EventTotal]=WSACreateEvent();//先存到事件数组中
  41.      ZeroMemory(&AcceptOverlapped,sizeof(WSAOVERLAPPED));
  42.      AcceptOverlapped.hEvent = EventArray[EventTotal];

  43.      DataBuf.len = DATA_BUFSIZE;
  44.      DataBuf.buf =buffer;

  45.      EventTotal++;

  46.      //step4:
  47.      //投递WSARecv准备在Accept套接字上接收数据
  48.      WSARecv(Accept,&DataBuf,1,&RecvBytes,&Flag,&AcceptOverlapped,NULL);

  49.      while (TRUE){
  50.          //step5:
  51.          //等待overlapped IO调用的完成
  52.          DWORD Index;
  53.          Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE);

  54.          //step6:
  55.          // Reset the signaled event
  56.          WSAResetEvent(EventArray[Index - WSA_WAIT_EVENT_0]);

  57.          //step7:
  58.          // Determine the status of the overlapped event
  59.          WSAGetOverlappedResult(AcceptSocket, &AcceptOverlapped, &BytesTransferred, FALSE, &Flags);


  60.          // If the connection has been closed, close the accepted socket
  61.          if (BytesTransferred == 0) {
  62.              printf("Closing Socket %d\n", AcceptSocket);
  63.              closesocket(AcceptSocket);
  64.              WSACloseEvent(EventArray[Index - WSA_WAIT_EVENT_0]);
  65.              return;
  66.          }

  67.          // If data has been received,do something with received data
  68.          //DataBuf contains the received data

  69.          //step8:
  70.          //post another WSARecv () request on the socket
  71.          Flag=0;
  72.          ZeroMemory(&AcceptOverlapped,sizeof(WSAOVERLAPPED));
  73.          AcceptOverlapped.hEvent=EventArray[Index - WSA_WAIT_EVENT_0];//该事件对象已经被复位

  74.          DataBuf.len = DATA_BUFSIZE;
  75.          DataBuf.buf =buffer;

  76.          WSARecv(Accept,&DataBuf,1,&RecvBytes,&Flag,&AcceptOverlapped,NULL);
  77.      }
  78.      return 0;
  79.  }

改进后的程序(并非实际可以运行的程序,只是为了理清思路):

点击(此处)折叠或打开

  1. #define LISTEN_PORT 5000
  2.  #define DATA_BUFSIZE 8192
  3.  #define POST_RECV 0X01
  4.  #define POST_SEND 0X02

  5.  int main( )
  6.  {
  7.      LPPER_HANDLE_DATA lpPerHandleData;
  8.      SOCKET hListenSocket;
  9.      SOCKET hClientSocket;
  10.      SOCKADDR_IN ClientAddr;
  11.      int nAddrLen;
  12.      HANDLE hThread;

  13.      // Start WinSock and create a listen socket.

  14.      listen(hListenSocket, 5);
  15.      for (; ;)
  16.      {
  17.          nAddrLen = sizeof(SOCKADDR);
  18.          hClientSocket = accept(hListenSocket, (LPSOCKADDR)&ClientAddr, &nAddrLen);

  19.          lpPerHandleData = (LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA));
  20.          lpPerHandleData->hSocket = hClientSocket;
  21.          // 注意这里将连接的客户端的IP地址,保存到了lpPerHandleData字段中了
  22.          strcpy(lpPerHandleData->szClientIP, inet_ntoa(ClientAddr.sin_addr));

  23.          // 为本次客户请求产生子线程
  24.          hThread = CreateThread(
  25.              NULL,
  26.              0,
  27.              OverlappedThread,
  28.              lpPerHandleData, // 将lpPerHandleData传给子线程
  29.              0,
  30.              NULL
  31.              );
  32.          CloseHandle(hThread);
  33.      }
  34.      return 0;
  35.  }

  36.  DWORD WINAPI OverlappedThread(LPVOID lpParam)
  37.  {
  38.      LPPER_HANDLE_DATA lpPerHandleData = (LPPER_HANDLE_DATA)lpParam;
  39.      WSAOVERLAPPED Overlapped;
  40.      WSABUF wsaBuf;
  41.      WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];//事件对象数组

  42.      DWORD dwEventTotal = 0, // 程序中事件的总数
  43.      char Buffer[DATA_BUFSIZE];
  44.      BOOL bResult;
  45.      int nResult;

  46.      EventArray[dwEventTotal] = WSACreateEvent();

  47.      ZeroMemory(&Overlapped, sizeof(WSAOVERLAPPED));

  48.      Overlapped.hEvent = EventArray[dwEventTotal]; // 关联事件

  49.      ZeroMemory(Buffer, DATA_BUFSIZE);
  50.      wsaBuf.buf = Buffer;
  51.      wsaBuf.len = sizeof(Buffer);
  52.      
  53.      lpPerHandleData->nOperateType = POST_RECV; // 记录本次操作是Recv(..)

  54.      dwEventTotal ++; // 总数加一
  55.      dwFlags = 0;

  56.      nResult = WSARecv(
  57.          lpPerHandleData->hSocket, // Receive socket
  58.          &wsaBuf, // 指向WSABUF结构的指针
  59.          1, // WSABUF数组的个数
  60.          &dwNumOfBytesRecved, // 存放当WSARecv完成后所接收到的字节数,实际接收到的字节数
  61.          &dwFlags, // A pointer to flags
  62.          &Overlapped, // A pointer to a WSAOVERLAPPED structure
  63.          NULL // A pointer to the completion routine,this is NULL
  64.          );

  65.      if ( nResult == SOCKET_ERROR && GetLastError() != WSA_IO_PENDING)
  66.      {
  67.          printf("WSARecv(..) failed.\n");
  68.          free(lpPerHandleData);

  69.          closesocket(lpPerHandleData->hSocket;
  70.           WSACloseEvent(EventArray[dwEventTotal]);
  71.          return -1;
  72.      }

  73.      while (TRUE)
  74.      {
  75.          DWORD dwIndex;

  76.          dwIndex = WSAWaitForMultipleEvents(dwEventTotal, EventArray ,
  77.              FALSE ,WSA_INFINITE,FALSE);

  78.         WSAResetEvent(EventArray[dwIndex– WSA_WAIT_EVENT_0]);


  79.          bResult = WSAGetOverlappedResult(
  80.              lpPerHandleData->hSocket,
  81.              &Overlapped,
  82.              &dwBytesTransferred, // 当一个同步I/O完成后,接收到的字节数
  83.              TRUE, // 等待I/O操作的完成
  84.              &dwFlags
  85.              );
  86.          if (bResult == FALSE && WSAGetLastError() != WSA_IO_INCOMPLETE)
  87.          {
  88.              printf("WSAGetOverlappdResult(..) failed.\n");
  89.              free(lpPerHandleData);
  90.              return 0; // 错误退出
  91.          }

  92.          if (dwBytesTransferred == 0)
  93.          {
  94.              printf("客户端已退出,将断开与之的连接!\n");
  95.              closesocket(lpPerHandleData->hSocket);
  96.              free(lpPerHandleData);
  97.              break;
  98.          }

  99.          // 在这里将接收到的数据进行处理
  100.          printf("Received from IP: %s.\ndata: %s\n", lpPerHandleData->szClientIP, wsaBuf.buf);

  101.          // 发送另外一个请求操作
  102.          ZeroMemory(&Overlapped, sizeof(WSAOVERLAPPED));
  103.          lpPerHandleData->nOperateType = POST_RECV;

  104.          dwFlags = 0;
  105.          nResult = WSARecv();
  106.          if (){}

  107.      }

  108.      return 1;
  109.  }

最后的一个改进版本,看上去是个不错的版本,相对来说算是比较实用的。但是使用了过多的全局变量,代码是C风格的,不可取。

点击(此处)折叠或打开

  1. #include <winsock2.h>
  2.  #include <stdio.h>

  3.  #define PORT 5150
  4.  #define MSGSIZE 1024

  5.  #pragma comment(lib, "ws2_32.lib")

  6.  typedef struct
  7.  {
  8.  WSAOVERLAPPED overlap;
  9.  WSABUF Buffer;
  10.  char szMessage[MSGSIZE];
  11.  DWORD NumberOfBytesRecvd;
  12.  DWORD Flags;
  13.  }PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;

  14.  int g_iTotalConn = 0;
  15.  SOCKET g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];
  16.  WSAEVENT g_CliEventArr[MAXIMUM_WAIT_OBJECTS];
  17.  LPPER_IO_OPERATION_DATA g_pPerIODataArr[MAXIMUM_WAIT_OBJECTS];

  18.  DWORD WINAPI WorkerThread(LPVOID);

  19.  void Cleanup(int);

  20.  int main()
  21.  {
  22.  WSADATA wsaData;
  23.  SOCKET sListen, sClient;
  24.  SOCKADDR_IN local, client;
  25.  DWORD dwThreadId;
  26.  int iaddrSize = sizeof(SOCKADDR_IN);
  27.  // Initialize Windows Socket library
  28.  WSAStartup(0x0202, &wsaData);
  29.  // Create listening socket
  30.  sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  31.  // Bind
  32.  local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
  33.  local.sin_family = AF_INET;
  34.  local.sin_port = htons(PORT);
  35.  bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));
  36.  // Listen
  37.  listen(sListen, 3);


  38. //网上的代码如此,好奇怪,为什么是在连接还没开始的时候就创建一个线程,而不是像上面的程序一样,accept一个
  39. //connection创建一个线程,并将从connection 的socket中获取的信息当作形参传递给workThread?
  40. //这里是只创建一个线程为重叠socket I/O操作服务
  41.  // Create worker thread
  42.  CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
  43.  while (TRUE)
  44.  {
  45.      // Accept a connection
  46.      sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);

  47.          //这很好,在accept函数中的后两个形参中获取client的相关信息,并直接在server端的控制台中显示出来
  48.      printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
  49.      g_CliSocketArr[g_iTotalConn] = sClient;//添加到socket数组中
  50.     
  51.      // Allocate a PER_IO_OPERATION_DATA structure
  52.      g_pPerIODataArr[g_iTotalConn] = ER(LPPER_IO_OPATION_DATA)HeapAlloc(
  53.               GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(PER_IO_OPERATION_DATA));
  54.      g_pPerIODataArr[g_iTotalConn]->Buffer.len = MSGSIZE;
  55.      g_pPerIODataArr[g_iTotalConn]->Buffer.buf = g_pPerIODataArr[g_iTotalConn]->eszMssage;
  56.      g_CliEventArr[g_iTotalConn] = g_pPerIODataArr[g_iTotalConn]->overlap.hEvent = WSACreateEvent();
  57.      // Launch an asynchronous operation
  58.      WSARecv(
  59.        g_CliSocketArr[g_iTotalConn],
  60.        &g_pPerIODataArr[g_iTotalConn]->Buffer,
  61.        1,
  62.        &g_pPerIODataArr[g_iTotalConn]->NumberOfBytesRecvd,
  63.        &g_pPerIODataArr[g_iTotalConn]->Flags,
  64.        &g_pPerIODataArr[g_iTotalConn]->overlap,
  65.        NULL);
  66.     
  67.      g_iTotalConn++;
  68.  }

  69.  closesocket(sListen);
  70.  WSACleanup();
  71.  return 0;
  72.  }
  73.  DWORD WINAPI WorkerThread(LPVOID lpParam)
  74.  {
  75.  int ret, index;
  76.  DWORD cbTransferred;
  77.  while (TRUE)
  78.  {
  79.      ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000, FALSE);
  80.      if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
  81.      {
  82.        continue;
  83.      }
  84.      index = ret - WSA_WAIT_EVENT_0;
  85.      WSAResetEvent(g_CliEventArr[index]);
  86.      WSAGetOverlappedResult(
  87.        g_CliSocketArr[index],
  88.        &g_pPerIODataArr[index]->overlap,
  89.        &cbTransferred,
  90.        TRUE,
  91.        &g_pPerIODataArr[g_iTotalConn]->Flags);
  92.      if (cbTransferred == 0)
  93.      {
  94.        // The connection was closed by client
  95.        Cleanup(index);
  96.      }
  97.      else
  98.      {
  99.        // g_pPerIODataArr[index]->szMessage contains the received data
  100.        g_pPerIODataArr[index]->szMessage[cbTransferred] = '\0';
  101.        send(g_CliSocketArr[index], g_pPerIODataArr[index]->szMessage,\
  102.          cbTransferred, 0);
  103.        // Launch another asynchronous operation
  104.        WSARecv(
  105.          g_CliSocketArr[index],
  106.          &g_pPerIODataArr[index]->Buffer,
  107.          1,
  108.          &g_pPerIODataArr[index]->NumberOfBytesRecvd,
  109.          &g_pPerIODataArr[index]->Flags,
  110.          &g_pPerIODataArr[index]->overlap,
  111.          NULL);
  112.      }
  113.  }
  114.  return 0;
  115.  }
  116.  void Cleanup(int index)
  117.  {
  118.  closesocket(g_CliSocketArr[index]);
  119.  WSACloseEvent(g_CliEventArr[index]);
  120.  HeapFree(GetProcessHeap(), 0, g_pPerIODataArr[index]);
  121.  if (index < g_iTotalConn - 1)
  122.  {
  123.      g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn - 1];
  124.      g_CliEventArr[index] = g_CliEventArr[g_iTotalConn - 1];
  125.      g_pPerIODataArr[index] = g_pPerIODataArr[g_iTotalConn - 1];
  126.  }
  127.  g_pPerIODataArr[--g_iTotalConn] = NULL;
  128.  }

    这个模型与上述其他模型不同的是它使用Winsock2提供的异步I/O函数WSARecv。在调用WSARecv时,指定一个WSAOVERLAPPED结构,这个调用不是阻塞的,也就是说,它会立刻返回。一旦有数据到达的时候,被指定的WSAOVERLAPPED结构中的hEvent被Signaled。由于下面这个语句g_CliEventArr[g_iTotalConn] = g_pPerIODataArr[g_iTotalConn]->overlap.hEvent;

    使得与该套接字相关联的WSAEVENT对象也被Signaled,所以WSAWaitForMultipleEvents的调用操作成功返回。我们现在应该做的就是与调用WSARecv相同的WSAOVERLAPPED结构为参数调用WSAGetOverlappedResult,从而得到本次I/O传送的字节数等相关信息。在取得接收的数据后,把数据原封不动的发送到客户端,然后重新激活一个WSARecv异步操作。

 

阅读(523) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~
评论热议
posted on 2016-01-25 16:34  玄冬  阅读(326)  评论(0编辑  收藏  举报