重叠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,
0
, WorkerThread, 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_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异步操作。