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 )。
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 }
改进后的程序(并非实际可以运行的程序,只是为了理清思路):
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风格的,不可取。
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操作服务
46 CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
47 while (TRUE)
48 {
49 // Accept a connection
50 sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
//这很好,在accept函数中的后两个形参中获取client的相关信息,并直接在server端的控制台中显示出来
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异步操作。