我们为什么要使用Socket I/O模型呢?还得从Socket的阻塞和非阻塞说起。
在网上看过一篇讲解I/O模型的文章,它举过一个例子觉得挺好,那就是收信的例子。
比如:老周在等待他女儿从美国寄过来的信件,老周住三楼,信箱在一楼。有以下几种情况:
第一:老周一直守在信箱旁边,直到收到信件为止。这样太费精力。这就好比是阻塞套接字。
第二:老周到信箱那里看一下,发现还没有来,就马上回家了。这就好比非阻塞套接字。
第三:老周先打个电话到一楼管理员问一下自己的信件是否到了,如果到了才下楼去取信件。当然这样浪费电话费,但是值得的。这可以比作Socket的Select I/O模型。
1 #include "stdafx.h"
2 #include <iostream>
3 #include <winsock2.h>
4 #include <windows.h>
5
6 #define TRACE ATLTrace //必须要加上这个宏定义,否则在WIN32的控制台程序中是不能直接用的
7
8 #define InternetAddr "127.0.0.1"
9 #define iPort 5055
10
11 #pragma comment(lib, "ws2_32.lib")
12
13 int _tmain(int argc, _TCHAR* argv[])
14 {
15 WSADATA wsa;
16 WORD wVersionRequested;
17 int err;
18
19 wVersionRequested = MAKEWORD( 2, 2 );
20 err = WSAStartup( wVersionRequested, &wsa);
21 if ( err != 0 ) {
22 //Tell the user that we could not find a usable
23 //WinSock DLL.
24 TRACE("你忘记添加WinSock DLL了\n");
25 WSACleanup();
26 return 1;
27 }
28
29 // Create a SOCKET for listening for incoming connection requests
30 SOCKET fdServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
31
32 sockaddr_in server;
33
34 server.sin_family = AF_INET;
35 server.sin_addr.s_addr = inet_addr(InternetAddr);
36 server.sin_port = htons(iPort);
37 //Bind the socket.
38 int ret = bind(fdServer, (sockaddr*)&server, sizeof(server));
39 ret = listen(fdServer, 4);
40
41 SOCKET AcceptSocket;
42 fd_set fdread;
43 timeval tv;
44 int nSize;
45 //其实也算是轮训,那么对阻塞socket用select和对使用非阻塞socket的优点在哪?
46 //可能的优点就是避免在非阻塞套接字里重复检查WSAEWOULDBLOCK错误。
47 while(1)
48 {
49
50 FD_ZERO(&fdread);//初始化fd_set
51 FD_SET(fdServer, &fdread);//分配套接字句柄到相应的fd_set
52
53 tv.tv_sec = 2;//这里我们打算让select等待两秒后返回,避免被锁死,也避免马上返回
54 tv.tv_usec = 0;
55
56 select(0, &fdread, NULL, NULL, &tv);
57
58 nSize = sizeof(server);
59 //先判断fdServer是否还在fd_set内来判断是否可以读,这样就避免因为 accept在等待
60 //时造成的阻塞
61 if (FD_ISSET(fdServer, &fdread))
62 //如果套接字句柄还在fd_set里,说明客户端已经有connect的请求发过来了,
63 //马上可以accept成功
64 {
65 AcceptSocket = accept(fdServer,( sockaddr*) &server, &nSize);
66 break;
67 }
68 else
69 //还没有客户端的connect请求,我们可以去做别的事,避免像没有用select方式
70 //的阻塞套接字程序被锁死的情况,如果没用select,当程序运行到accept的时候客户
71 //端恰好没有connect请求,那么程序就会被锁死,做不了任何事情
72 {
73 //do something
74 MessageBox(NULL, "waiting", "recv", MB_ICONINFORMATION);
75 //别的事做完后,继续去检查是否有客户端连接请求
76 }
77 }
78
79 char buffer[128];
80 ZeroMemory(buffer, 128);
81
82 ret = recv(AcceptSocket,buffer,128,0);//这里同样可以用select,用法和上面一样
83
84 MessageBox(NULL, buffer, "recv", MB_ICONINFORMATION);
85
86 closesocket(AcceptSocket);
87 WSACleanup();
88 return 0;
89 }
2 #include <iostream>
3 #include <winsock2.h>
4 #include <windows.h>
5
6 #define TRACE ATLTrace //必须要加上这个宏定义,否则在WIN32的控制台程序中是不能直接用的
7
8 #define InternetAddr "127.0.0.1"
9 #define iPort 5055
10
11 #pragma comment(lib, "ws2_32.lib")
12
13 int _tmain(int argc, _TCHAR* argv[])
14 {
15 WSADATA wsa;
16 WORD wVersionRequested;
17 int err;
18
19 wVersionRequested = MAKEWORD( 2, 2 );
20 err = WSAStartup( wVersionRequested, &wsa);
21 if ( err != 0 ) {
22 //Tell the user that we could not find a usable
23 //WinSock DLL.
24 TRACE("你忘记添加WinSock DLL了\n");
25 WSACleanup();
26 return 1;
27 }
28
29 // Create a SOCKET for listening for incoming connection requests
30 SOCKET fdServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
31
32 sockaddr_in server;
33
34 server.sin_family = AF_INET;
35 server.sin_addr.s_addr = inet_addr(InternetAddr);
36 server.sin_port = htons(iPort);
37 //Bind the socket.
38 int ret = bind(fdServer, (sockaddr*)&server, sizeof(server));
39 ret = listen(fdServer, 4);
40
41 SOCKET AcceptSocket;
42 fd_set fdread;
43 timeval tv;
44 int nSize;
45 //其实也算是轮训,那么对阻塞socket用select和对使用非阻塞socket的优点在哪?
46 //可能的优点就是避免在非阻塞套接字里重复检查WSAEWOULDBLOCK错误。
47 while(1)
48 {
49
50 FD_ZERO(&fdread);//初始化fd_set
51 FD_SET(fdServer, &fdread);//分配套接字句柄到相应的fd_set
52
53 tv.tv_sec = 2;//这里我们打算让select等待两秒后返回,避免被锁死,也避免马上返回
54 tv.tv_usec = 0;
55
56 select(0, &fdread, NULL, NULL, &tv);
57
58 nSize = sizeof(server);
59 //先判断fdServer是否还在fd_set内来判断是否可以读,这样就避免因为 accept在等待
60 //时造成的阻塞
61 if (FD_ISSET(fdServer, &fdread))
62 //如果套接字句柄还在fd_set里,说明客户端已经有connect的请求发过来了,
63 //马上可以accept成功
64 {
65 AcceptSocket = accept(fdServer,( sockaddr*) &server, &nSize);
66 break;
67 }
68 else
69 //还没有客户端的connect请求,我们可以去做别的事,避免像没有用select方式
70 //的阻塞套接字程序被锁死的情况,如果没用select,当程序运行到accept的时候客户
71 //端恰好没有connect请求,那么程序就会被锁死,做不了任何事情
72 {
73 //do something
74 MessageBox(NULL, "waiting", "recv", MB_ICONINFORMATION);
75 //别的事做完后,继续去检查是否有客户端连接请求
76 }
77 }
78
79 char buffer[128];
80 ZeroMemory(buffer, 128);
81
82 ret = recv(AcceptSocket,buffer,128,0);//这里同样可以用select,用法和上面一样
83
84 MessageBox(NULL, buffer, "recv", MB_ICONINFORMATION);
85
86 closesocket(AcceptSocket);
87 WSACleanup();
88 return 0;
89 }
第四:老周告诉一楼管理员,如果有他的信件就通知老周。这可以比做Socket的WSAAsynSelect模型
1 #include <winsock.h>
2 #include <tchar.h>
3
4 #define PORT 5150
5 #define MSGSIZE 1024
6 #define WM_SOCKET WM_USER+0
7
8 #pragma comment(lib, "ws2_32.lib")
9
10 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
11
12 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
13 {
14 static TCHAR szAppName[] = _T("AsyncSelect Model");
15 HWND hwnd ;
16 MSG msg ;
17 WNDCLASS wndclass ;
18
19 wndclass.style = CS_HREDRAW | CS_VREDRAW ;
20 wndclass.lpfnWndProc = WndProc ;
21 wndclass.cbClsExtra = 0 ;
22 wndclass.cbWndExtra = 0 ;
23 wndclass.hInstance = hInstance ;
24 wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
25 wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
26 wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
27 wndclass.lpszMenuName = NULL ;
28 wndclass.lpszClassName = szAppName ;
29
30 if (!RegisterClass(&wndclass))
31 {
32 MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ;
33 return 0 ;
34 }
35
36 hwnd = CreateWindow (szAppName, // window class name
37 TEXT ("AsyncSelect Model"), // window caption
38 WS_OVERLAPPEDWINDOW, // window style
39 CW_USEDEFAULT, // initial x position
40 CW_USEDEFAULT, // initial y position
41 CW_USEDEFAULT, // initial x size
42 CW_USEDEFAULT, // initial y size
43 NULL, // parent window handle
44 NULL, // window menu handle
45 hInstance, // program instance handle
46 NULL) ; // creation parameters
47
48 ShowWindow(hwnd, iCmdShow);
49 UpdateWindow(hwnd);
50
51 while (GetMessage(&msg, NULL, 0, 0))
52 {
53 TranslateMessage(&msg) ;
54 DispatchMessage(&msg) ;
55 }
56
57 return msg.wParam;
58 }
59
60 LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
61 {
62 WSADATA wsd;
63 static SOCKET sListen;
64 SOCKET sClient;
65 SOCKADDR_IN local, client;
66 int ret, iAddrSize = sizeof(client);
67 char szMessage[MSGSIZE];
68
69 switch (message)
70 {
71 case WM_CREATE:
72 // Initialize Windows Socket library
73 WSAStartup(0x0202, &wsd);
74
75 // Create listening socket
76 sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
77
78 // Bind
79 local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
80 local.sin_family = AF_INET;
81 local.sin_port = htons(PORT);
82 bind(sListen, (struct sockaddr *)&local, sizeof(local));
83
84 // Listen
85 listen(sListen, 3);
86
87 // Associate listening socket with FD_ACCEPT event
88 WSAAsyncSelect(sListen, hwnd, WM_SOCKET, FD_ACCEPT);
89 return 0;
90
91 case WM_DESTROY:
92 closesocket(sListen);
93 WSACleanup();
94 PostQuitMessage(0);
95 return 0;
96
97 case WM_SOCKET:
98 if (WSAGETSELECTERROR(lParam))//lParam的高字节包含了可能出现的任何的错误代码
99 {
100 closesocket(wParam);
101 break;
102 }
103
104 switch (WSAGETSELECTEVENT(lParam)) //lParam的低字节指定已经发生的网络事件
105 {
106 case FD_ACCEPT:
107 // Accept a connection from client
108 sClient = accept(wParam, (struct sockaddr *)&client, &iAddrSize);
109
110 // Associate client socket with FD_READ and FD_CLOSE event
111 WSAAsyncSelect(sClient, hwnd, WM_SOCKET, FD_READ | FD_CLOSE);
112 break;
113
114 case FD_READ:
115 ret = recv(wParam, szMessage, MSGSIZE, 0);
116
117 if (ret == 0 || ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET)
118 {
119 closesocket(wParam);
120 }
121 else
122 {
123 szMessage[ret] = '\0';
124 send(wParam, szMessage, strlen(szMessage), 0);
125 }
126 break;
127
128 case FD_CLOSE:
129 closesocket(wParam);
130 break;
131 }
132 return 0;
133 }
134
135 return DefWindowProc(hwnd, message, wParam, lParam);
136 }
2 #include <tchar.h>
3
4 #define PORT 5150
5 #define MSGSIZE 1024
6 #define WM_SOCKET WM_USER+0
7
8 #pragma comment(lib, "ws2_32.lib")
9
10 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
11
12 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
13 {
14 static TCHAR szAppName[] = _T("AsyncSelect Model");
15 HWND hwnd ;
16 MSG msg ;
17 WNDCLASS wndclass ;
18
19 wndclass.style = CS_HREDRAW | CS_VREDRAW ;
20 wndclass.lpfnWndProc = WndProc ;
21 wndclass.cbClsExtra = 0 ;
22 wndclass.cbWndExtra = 0 ;
23 wndclass.hInstance = hInstance ;
24 wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
25 wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
26 wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
27 wndclass.lpszMenuName = NULL ;
28 wndclass.lpszClassName = szAppName ;
29
30 if (!RegisterClass(&wndclass))
31 {
32 MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ;
33 return 0 ;
34 }
35
36 hwnd = CreateWindow (szAppName, // window class name
37 TEXT ("AsyncSelect Model"), // window caption
38 WS_OVERLAPPEDWINDOW, // window style
39 CW_USEDEFAULT, // initial x position
40 CW_USEDEFAULT, // initial y position
41 CW_USEDEFAULT, // initial x size
42 CW_USEDEFAULT, // initial y size
43 NULL, // parent window handle
44 NULL, // window menu handle
45 hInstance, // program instance handle
46 NULL) ; // creation parameters
47
48 ShowWindow(hwnd, iCmdShow);
49 UpdateWindow(hwnd);
50
51 while (GetMessage(&msg, NULL, 0, 0))
52 {
53 TranslateMessage(&msg) ;
54 DispatchMessage(&msg) ;
55 }
56
57 return msg.wParam;
58 }
59
60 LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
61 {
62 WSADATA wsd;
63 static SOCKET sListen;
64 SOCKET sClient;
65 SOCKADDR_IN local, client;
66 int ret, iAddrSize = sizeof(client);
67 char szMessage[MSGSIZE];
68
69 switch (message)
70 {
71 case WM_CREATE:
72 // Initialize Windows Socket library
73 WSAStartup(0x0202, &wsd);
74
75 // Create listening socket
76 sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
77
78 // Bind
79 local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
80 local.sin_family = AF_INET;
81 local.sin_port = htons(PORT);
82 bind(sListen, (struct sockaddr *)&local, sizeof(local));
83
84 // Listen
85 listen(sListen, 3);
86
87 // Associate listening socket with FD_ACCEPT event
88 WSAAsyncSelect(sListen, hwnd, WM_SOCKET, FD_ACCEPT);
89 return 0;
90
91 case WM_DESTROY:
92 closesocket(sListen);
93 WSACleanup();
94 PostQuitMessage(0);
95 return 0;
96
97 case WM_SOCKET:
98 if (WSAGETSELECTERROR(lParam))//lParam的高字节包含了可能出现的任何的错误代码
99 {
100 closesocket(wParam);
101 break;
102 }
103
104 switch (WSAGETSELECTEVENT(lParam)) //lParam的低字节指定已经发生的网络事件
105 {
106 case FD_ACCEPT:
107 // Accept a connection from client
108 sClient = accept(wParam, (struct sockaddr *)&client, &iAddrSize);
109
110 // Associate client socket with FD_READ and FD_CLOSE event
111 WSAAsyncSelect(sClient, hwnd, WM_SOCKET, FD_READ | FD_CLOSE);
112 break;
113
114 case FD_READ:
115 ret = recv(wParam, szMessage, MSGSIZE, 0);
116
117 if (ret == 0 || ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET)
118 {
119 closesocket(wParam);
120 }
121 else
122 {
123 szMessage[ret] = '\0';
124 send(wParam, szMessage, strlen(szMessage), 0);
125 }
126 break;
127
128 case FD_CLOSE:
129 closesocket(wParam);
130 break;
131 }
132 return 0;
133 }
134
135 return DefWindowProc(hwnd, message, wParam, lParam);
136 }
第五:老周要一楼管理员发个短信到他们家里去。相当如WSAEventSelect模型,其实WSAEventSelect模型类似WSAAsynSelect模型,但最主要的区别是网络事件发生时会被发送到一个事件对象句柄,而不是发送到一个窗口。这样可能更加的好,对于服务器端的程序来说。
1 SOCKET Socket[WSA_MAXIMUM_WAIT_EVENTS];
2 WSAEVENT Event[WSA_MAXINUM_WAIT_EVENTS];
3 SOCKET Accept, Listen;
5 DWORD EventTotal = 0;
6 DWORD Index;
7
8 //Set up a TCP socket for listening on port 5150
9 Listen = socket(PF_INET,SOCK_STREAM,0);
10
11 InternetAddr.sin_family = AF_INET;
12 InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
13 InternetAddr.sin_port = htons(5150);
14
15 bind(Listen,(PSOCKADDR) &InternetAddr,sizeof(InternetAddr));
16
17 NewEvent = WSACreateEvent();
18
19 WSAEventSelect(Listen,NewEvnet,FD_ACCEPT|FD_CLOSE);
20
21 listen(Listen,5);
22
23 Socket[EventTotal] = Listen;
24 Event[EventTotal] = NewEvent;
25 EventTotal++;
26
27 while (TRUE)
28 {
29 //Wait for network events on all sockets
30 Index = WSAWaitForMultipleEvents(EventTotal,EventArray,FALSE,WSA_INFINITE,FALSE);
31
32 WSAEnumNewWorkEvents(SocketArray[Index-WSA_WAIT_EVENT_0],
33 EventArray[Index-WSA_WAIT_EVENT_0],
34 &NetworkEvents);
35 //Check for FD_ACCEPT messages
36 if (NetworkEvents.lNetworkEvents & FD_ACCEPT)
37 {
38 if (NetworkEvents.iErrorCode[FD_ACCEPT_BIT] !=0)
39 {
40 //Error
41 break;
42 }
43 //Accept a new connection and add it to the socket and event lists
44 Accept = accept(SocketArray[Index-WSA_WAIT_EVENT_0],NULL,NULL);
45
46 //We cannot process more than WSA_MAXIMUM_WAIT_EVENTS sockets ,
47 //so close the accepted socket
48 if (EventTotal > WSA_MAXIMUM_WAIT_EVENTS)
49 {
50 printf("..");
51 closesocket (Accept);
52 break;
53 }
54 NewEvent = WSACreateEvent();
55
56 WSAEventSelect(Accept,NewEvent,FD_READ|FD_WRITE|FD_CLOSE);
57
58 Event[EventTotal] = NewEvent;
59 Socket[EventTotal]= Accept;
60 EventTotal++;
61 prinrt("Socket %d connect\n",Accept);
62 }
63 //Process FD_READ notification
64 if (NetworkEvents.lNetwoAD)rkEvents & FD_RE
65 {
66 if (NetworkEvents.iErrorCode[FD_READ_BIT !=0])
67 {
68 //Error
69 break;
70 }
71
72 //Read data from the socket
73 recv(Socket[Index-WSA_WAIT_EVENT_0],buffer,sizeof(buffer),0);
74 }
75 //process FD_WRITE notitication
76 if (NetworkEvents.lNetworkEvents & FD_WRITE)
77 {
78 if (NetworkEvents.iErrorCode[FD_WRITE_BIT] !=0)
79 {
80 //Error
81 break;
82 }
83 send(Socket[Index-WSA_WAIT_EVENT_0],buffer,sizeof(buffer),0);
84 }
85 if (NetworkEvents.lNetworkEvents & FD_CLOSE)
86 {
87 if(NetworkEvents.iErrorCode[FD_CLOSE_BIT] !=0)
88 {
89 //Error
90 break;
91 }
92 closesocket (Socket[Index-WSA_WAIT_EVENT_0]);
93 //Remove socket and associated event from the Socket and Event arrays and
94 //decrement eventTotal
95 CompressArrays(Event,Socket,& EventTotal);
96 }
97 }
2 WSAEVENT Event[WSA_MAXINUM_WAIT_EVENTS];
3 SOCKET Accept, Listen;
5 DWORD EventTotal = 0;
6 DWORD Index;
7
8 //Set up a TCP socket for listening on port 5150
9 Listen = socket(PF_INET,SOCK_STREAM,0);
10
11 InternetAddr.sin_family = AF_INET;
12 InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
13 InternetAddr.sin_port = htons(5150);
14
15 bind(Listen,(PSOCKADDR) &InternetAddr,sizeof(InternetAddr));
16
17 NewEvent = WSACreateEvent();
18
19 WSAEventSelect(Listen,NewEvnet,FD_ACCEPT|FD_CLOSE);
20
21 listen(Listen,5);
22
23 Socket[EventTotal] = Listen;
24 Event[EventTotal] = NewEvent;
25 EventTotal++;
26
27 while (TRUE)
28 {
29 //Wait for network events on all sockets
30 Index = WSAWaitForMultipleEvents(EventTotal,EventArray,FALSE,WSA_INFINITE,FALSE);
31
32 WSAEnumNewWorkEvents(SocketArray[Index-WSA_WAIT_EVENT_0],
33 EventArray[Index-WSA_WAIT_EVENT_0],
34 &NetworkEvents);
35 //Check for FD_ACCEPT messages
36 if (NetworkEvents.lNetworkEvents & FD_ACCEPT)
37 {
38 if (NetworkEvents.iErrorCode[FD_ACCEPT_BIT] !=0)
39 {
40 //Error
41 break;
42 }
43 //Accept a new connection and add it to the socket and event lists
44 Accept = accept(SocketArray[Index-WSA_WAIT_EVENT_0],NULL,NULL);
45
46 //We cannot process more than WSA_MAXIMUM_WAIT_EVENTS sockets ,
47 //so close the accepted socket
48 if (EventTotal > WSA_MAXIMUM_WAIT_EVENTS)
49 {
50 printf("..");
51 closesocket (Accept);
52 break;
53 }
54 NewEvent = WSACreateEvent();
55
56 WSAEventSelect(Accept,NewEvent,FD_READ|FD_WRITE|FD_CLOSE);
57
58 Event[EventTotal] = NewEvent;
59 Socket[EventTotal]= Accept;
60 EventTotal++;
61 prinrt("Socket %d connect\n",Accept);
62 }
63 //Process FD_READ notification
64 if (NetworkEvents.lNetwoAD)rkEvents & FD_RE
65 {
66 if (NetworkEvents.iErrorCode[FD_READ_BIT !=0])
67 {
68 //Error
69 break;
70 }
71
72 //Read data from the socket
73 recv(Socket[Index-WSA_WAIT_EVENT_0],buffer,sizeof(buffer),0);
74 }
75 //process FD_WRITE notitication
76 if (NetworkEvents.lNetworkEvents & FD_WRITE)
77 {
78 if (NetworkEvents.iErrorCode[FD_WRITE_BIT] !=0)
79 {
80 //Error
81 break;
82 }
83 send(Socket[Index-WSA_WAIT_EVENT_0],buffer,sizeof(buffer),0);
84 }
85 if (NetworkEvents.lNetworkEvents & FD_CLOSE)
86 {
87 if(NetworkEvents.iErrorCode[FD_CLOSE_BIT] !=0)
88 {
89 //Error
90 break;
91 }
92 closesocket (Socket[Index-WSA_WAIT_EVENT_0]);
93 //Remove socket and associated event from the Socket and Event arrays and
94 //decrement eventTotal
95 CompressArrays(Event,Socket,& EventTotal);
96 }
97 }
第六:老周可以要求一楼管理员把信件送到他们家去,好比Overlapped I/O 事件通知模型。
第七:老周还可以要求不仅送信件,还可以要求管理员帮他把信封打开,读给老周听(假设老周为文盲,管理员也够累的),这就是Overlapped I/O 完成例程模型 了。