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

    重叠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 
  4 #define DATA_BUFSIZE 4096
  5 
  6 #pragma comment(lib, "ws2_32.lib")
  7 
  8 int _tmain(int argc, _TCHAR* argv[])
  9 {
 10     DWORD EventTotal = 0,RecvBytes = 0, Flags = 0;
 11     char buffer[DATA_BUFSIZE];
 12 
 13     WSABUF  DataBuf;    
 14     WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];
 15     WSAOVERLAPPED  AcceptOverlapped;
 16     SOCKET  Listen,Accept;
 17 
 18     //step1:
 19     //start Winsock and set up a listening socket
 20     WSADATA wsaData;
 21     WSAStartup(MAKEWORD(2,2), &wsaData);
 22 
 23     ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 24     u_short port = 27015;
 25     char* ip;
 26     sockaddr_in service;
 27     service.sin_family = AF_INET;
 28     service.sin_port = htons(port);
 29     hostent* thisHost;
 30     thisHost = gethostbyname("");
 31     ip = inet_ntoa (*(struct in_addr *)*thisHost->h_addr_list);
 32 
 33     service.sin_addr.s_addr = inet_addr(ip);
 34 
 35     //-----------------------------------------
 36     // Bind the listening socket to the local IP address
 37     // and port number
 38     bind(ListenSocket, (SOCKADDR *&service, sizeof(SOCKADDR));
 39 
 40     //-----------------------------------------
 41     // Set the socket to listen for incoming
 42     // connection requests
 43     listen(ListenSocket, 1);
 44     printf("Listening\n");
 45 
 46     //step2:
 47     Accept=accept(Listen,NULL,NULL);
 48 
 49     //step3:
 50     //set up  an overlapped structure
 51     EventArray[EventTotal]=WSACreateEvent();//先存到事件数组中
 52     ZeroMemory(&AcceptOverlapped,sizeof(WSAOVERLAPPED));
 53     AcceptOverlapped.hEvent = EventArray[EventTotal];
 54 
 55     DataBuf.len = DATA_BUFSIZE;
 56     DataBuf.buf =buffer;
 57 
 58     EventTotal++;
 59 
 60     //step4:
 61     //投递WSARecv准备在Accept套接字上接收数据
 62     WSARecv(Accept,&DataBuf,1,&RecvBytes,&Flag,&AcceptOverlapped,NULL);
 63 
 64     while (TRUE){
 65         //step5:
 66         //等待overlapped IO调用的完成
 67         DWORD Index;
 68         Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE);
 69 
 70         //step6:
 71         // Reset the signaled event
 72         WSAResetEvent(EventArray[Index - WSA_WAIT_EVENT_0]);
 73 
 74         //step7:
 75         // Determine the status of the overlapped event
 76         WSAGetOverlappedResult(AcceptSocket, &AcceptOverlapped, &BytesTransferred, FALSE, &Flags);
 77 
 78 
 79         // If the connection has been closed, close the accepted socket
 80         if (BytesTransferred == 0) {
 81             printf("Closing Socket %d\n", AcceptSocket);
 82             closesocket(AcceptSocket);
 83             WSACloseEvent(EventArray[Index - WSA_WAIT_EVENT_0]);
 84             return;
 85         }
 86 
 87         // If data has been received,do something with received data
 88         //DataBuf  contains the received data
 89 
 90         //step8:
 91         //post another WSARecv () request on the socket
 92         Flag=0;
 93         ZeroMemory(&AcceptOverlapped,sizeof(WSAOVERLAPPED));
 94         AcceptOverlapped.hEvent=EventArray[Index - WSA_WAIT_EVENT_0];//该事件对象已经被复位
 95 
 96         DataBuf.len = DATA_BUFSIZE;
 97         DataBuf.buf =buffer;
 98 
 99         WSARecv(Accept,&DataBuf,1,&RecvBytes,&Flag,&AcceptOverlapped,NULL);
100     }
101     return 0;
102 }

 

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

 

  1 #define     LISTEN_PORT 5000
  2 #define     DATA_BUFSIZE 8192
  3 #define     POST_RECV 0X01   
  4 #define     POST_SEND 0X02
  5 
  6 int   main(  )
  7 {
  8     LPPER_HANDLE_DATA    lpPerHandleData;
  9     SOCKET               hListenSocket;
 10     SOCKET               hClientSocket;
 11     SOCKADDR_IN          ClientAddr;
 12     int                  nAddrLen;
 13     HANDLE               hThread; 
 14 
 15     // Start WinSock and create a listen socket.
 16 
 17     listen(hListenSocket,  5); 
 18     for (; ;)
 19     {
 20         nAddrLen  =  sizeof(SOCKADDR);
 21         hClientSocket  =  accept(hListenSocket,  (LPSOCKADDR)&ClientAddr,  &nAddrLen);
 22 
 23         lpPerHandleData = (LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA));
 24         lpPerHandleData->hSocket  =  hClientSocket;
 25         // 注意这里将连接的客户端的IP地址,保存到了lpPerHandleData字段中了
 26         strcpy(lpPerHandleData->szClientIP,   inet_ntoa(ClientAddr.sin_addr));
 27 
 28         // 为本次客户请求产生子线程
 29         hThread = CreateThread(
 30             NULL,
 31             0,
 32             OverlappedThread,
 33             lpPerHandleData,   // 将lpPerHandleData传给子线程
 34             0,
 35             NULL
 36             );
 37         CloseHandle(hThread);
 38     }   
 39     return 0;
 40 }
 41 
 42 DWORD   WINAPI   OverlappedThread(LPVOID    lpParam)
 43 {
 44     LPPER_HANDLE_DATA     lpPerHandleData   =   (LPPER_HANDLE_DATA)lpParam;
 45     WSAOVERLAPPED Overlapped;
 46     WSABUF        wsaBuf;
 47     WSAEVENT    EventArray[WSA_MAXIMUM_WAIT_EVENTS];//事件对象数组
 48 
 49     DWORD     dwEventTotal = 0,            // 程序中事件的总数
 50     char          Buffer[DATA_BUFSIZE];
 51     BOOL          bResult;
 52     int           nResult;
 53 
 54     EventArray[dwEventTotal] = WSACreateEvent();      
 55 
 56     ZeroMemory(&Overlapped, sizeof(WSAOVERLAPPED));
 57 
 58     Overlapped.hEvent = EventArray[dwEventTotal];            // 关联事件
 59 
 60     ZeroMemory(Buffer, DATA_BUFSIZE);
 61     wsaBuf.buf = Buffer;
 62     wsaBuf.len = sizeof(Buffer);
 63     
 64     lpPerHandleData->nOperateType = POST_RECV;     // 记录本次操作是Recv(..)
 65 
 66     dwEventTotal ++;                              // 总数加一
 67     dwFlags = 0;
 68 
 69     nResult = WSARecv(
 70         lpPerHandleData->hSocket,   // Receive socket
 71         &wsaBuf,                                  // 指向WSABUF结构的指针
 72         1,                                                 // WSABUF数组的个数
 73         &dwNumOfBytesRecved,      // 存放当WSARecv完成后所接收到的字节数,实际接收到的字节数
 74         &dwFlags,                                 // A pointer to flags
 75         &Overlapped,                           // A pointer to a WSAOVERLAPPED structure
 76         NULL                                         // A pointer to the completion routine,this is NULL
 77         );
 78 
 79     if   ( nResult   ==   SOCKET_ERROR     &&   GetLastError() !=       WSA_IO_PENDING)
 80     {
 81         printf("WSARecv(..) failed.\n");
 82         free(lpPerHandleData);
 83 
 84         closesocket(lpPerHandleData->hSocket;
 85          WSACloseEvent(EventArray[dwEventTotal]);
 86         return -1;
 87     }
 88 
 89     while (TRUE)
 90     {
 91         DWORD dwIndex;
 92 
 93         dwIndex = WSAWaitForMultipleEvents(dwEventTotal, EventArray ,
 94             FALSE ,WSA_INFINITE,FALSE);
 95 
 96        WSAResetEvent(EventArray[dwIndex– WSA_WAIT_EVENT_0]);
 97 
 98 
 99         bResult  =  WSAGetOverlappedResult(
100             lpPerHandleData->hSocket,  
101             &Overlapped,           
102             &dwBytesTransferred,       // 当一个同步I/O完成后,接收到的字节数
103             TRUE,                      // 等待I/O操作的完成
104             &dwFlags                   
105             );
106         if   (bResult  ==  FALSE  &&  WSAGetLastError()  !=  WSA_IO_INCOMPLETE)
107         {
108             printf("WSAGetOverlappdResult(..) failed.\n");
109             free(lpPerHandleData);
110             return 0;   // 错误退出
111         }
112 
113         if  (dwBytesTransferred == 0)
114         {
115             printf("客户端已退出,将断开与之的连接!\n");
116             closesocket(lpPerHandleData->hSocket);
117             free(lpPerHandleData);
118             break;
119         }
120 
121         // 在这里将接收到的数据进行处理
122         printf("Received from IP: %s.\ndata: %s\n", lpPerHandleData->szClientIP, wsaBuf.buf);    
123 
124         // 发送另外一个请求操作
125         ZeroMemory(&Overlapped, sizeof(WSAOVERLAPPED));
126         lpPerHandleData->nOperateType = POST_RECV;
127 
128         dwFlags = 0;
129         nResult = WSARecv();
130         if (){}
131 
132     }
133 
134     return 1;
135 }

 

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

  1 #include <winsock2.h>
  2 #include <stdio.h>
  3 
  4 #define PORT    5150
  5 #define MSGSIZE 1024
  6 
  7 #pragma comment(lib, "ws2_32.lib")
  8 
  9 typedef struct
 10 {
 11 WSAOVERLAPPED overlap;
 12 WSABUF        Buffer;
 13 char          szMessage[MSGSIZE];
 14 DWORD         NumberOfBytesRecvd;
 15 DWORD         Flags;
 16 }PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;
 17 
 18 int                     g_iTotalConn = 0;
 19 SOCKET                  g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];
 20 WSAEVENT                g_CliEventArr[MAXIMUM_WAIT_OBJECTS];
 21 LPPER_IO_OPERATION_DATA g_pPerIODataArr[MAXIMUM_WAIT_OBJECTS];
 22 
 23 DWORD WINAPI WorkerThread(LPVOID);
 24 
 25 void Cleanup(int);
 26 
 27 int main()
 28 {
 29 WSADATA     wsaData;
 30 SOCKET      sListen, sClient;
 31 SOCKADDR_IN local, client;
 32 DWORD       dwThreadId;
 33 int         iaddrSize = sizeof(SOCKADDR_IN);
 34 // Initialize Windows Socket library
 35 WSAStartup(0x0202&wsaData);
 36 // Create listening socket
 37 sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 38 // Bind
 39 local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
 40 local.sin_family = AF_INET;
 41 local.sin_port = htons(PORT);
 42 bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));
 43 // Listen

 44 listen(sListen, 3);

 

 

//网上的代码如此,好奇怪,为什么是在连接还没开始的时候就创建一个线程,而不是像上面的程序一样,accept一个

//connection创建一个线程,并将从connection 的socket中获取的信息当作形参传递给workThread?

//这里是只创建一个线程为重叠socket I/O操作服务

 45 // Create worker thread
 46 CreateThread(NULL, 0WorkerThread, NULL, 0&dwThreadId);
 47 while (TRUE)
 48 {
 49     // Accept a connection

 50     sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);

 

         //这很好,在accept函数中的后两个形参中获取client的相关信息,并直接在server端的控制台中显示出来

 51     printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
 52     g_CliSocketArr[g_iTotalConn] = sClient;//添加到socket数组中
 53    
 54     // Allocate a PER_IO_OPERATION_DATA structure
 55     g_pPerIODataArr[g_iTotalConn] = ER(LPPER_IO_OPATION_DATA)HeapAlloc(

 56              GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(PER_IO_OPERATION_DATA));

 

 

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

  

    这个模型与上述其他模型不同的是它使用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异步操作。

 

posted on 2008-08-16 19:25  风荷小筑  阅读(1221)  评论(0编辑  收藏  举报