重叠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
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_OP ATION_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]->eszM ssage;
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异步操作。