==================================声明==================================
本文原创,转载在正文中显要的注明作者和出处,并保证文章的完整性。
未经作者同意请勿修改(包括本声明),保留法律追究的权利。
未经作者同意请勿用于出版、印刷或学术引用。
本文不定期修正完善,为保证内容正确,建议移步原文处阅读。
本文链接:http://www.cnblogs.com/wlsandwho/p/4228894.html
=======================================================================
最近有个同学辞职了,在QQ空间里说要北上找工作,问何为,答曰,“年前辞职,年后上班”。
这样真的好吗?
=======================================================================
最近在写一个小东西,需要用到非阻塞模式的套接字,考虑到用的MFC界面而且信息量不是很大很长很吓人,就选用了WSAAsyncSelect模型。
=======================================================================
考虑到1对N的情形,在每次accept后都维护一下map<socket,ClientInfo>的存储结构,自然的,每当客户端正常关闭都要再次更新这个结构。
=======================================================================
“每当客户端正常关闭”,会调用shutdown和closesocket,于是需要处理FD_CLOSE。
=======================================================================
可怕的是,FD_CLOSE的分支没有触发。
=======================================================================
那么问题来了,究竟为何没有触发?
我看了下监听后的WSAAsyncSelect:
1 WSAAsyncSelect(m_sockListen,hWnd,m_wMsgServer,FD_ACCEPT|FD_CLOSE)
又看了下接受连接后的WSAAsyncSelect
1 WSAAsyncSelect(sockClient, hWnd, m_wMsgServer, FD_READ|FD_WRITE|FD_CLOSE)
感觉没什么问题啊,好奇怪。
=======================================================================
稍微看了下自己的代码后,决定搜一下网上的资料,自然的无果。
只好对照《Windows网络编程技术》的源码看看有什么忽略的地方。
贴一下书本的源码先:(要用UNICODE还要稍微改改。)
1 // Module Name: asyncselect.cpp
2 //
3 // Description:
4 //
5 // This sample illustrates how to develop a simple echo server Winsock
6 // application using the WSAAsyncSelect() I/O model. This sample is
7 // implemented as a console-style application (to reduce the programming
8 // complexity of writing a real Windows application) and simply prints
9 // messages when connections are established and removed from the server.
10 // The application listens for TCP connections on port 5150 and accepts them
11 // as they arrive. When this application receives data from a client, it
12 // simply echos (this is why we call it an echo server) the data back in
13 // it's original form until the client closes the connection.
14 //
15 // Since the WSAAsyncSelect I/O model requires an application to manage
16 // window messages when network event occur, this application creates
17 // a window for the I/O model only. The window stays hidden during the
18 // entire execution of this application.
19 //
20 // Compile:
21 //
22 // cl -o asyncselect asyncselect.cpp ws2_32.lib user32.lib gdi32.lib
23 //
24 // Command Line Options:
25 //
26 // asyncselect.exe
27 //
28 // Note: There are no command line options for this sample.
29
30 #include <winsock2.h>
31 #include <windows.h>
32 #include <stdio.h>
33 #include <conio.h>
34
35 #define PORT 5150
36 #define DATA_BUFSIZE 8192
37
38 typedef struct _SOCKET_INFORMATION {
39 BOOL RecvPosted;
40 CHAR Buffer[DATA_BUFSIZE];
41 WSABUF DataBuf;
42 SOCKET Socket;
43 DWORD BytesSEND;
44 DWORD BytesRECV;
45 _SOCKET_INFORMATION *Next;
46 } SOCKET_INFORMATION, * LPSOCKET_INFORMATION;
47
48 #define WM_SOCKET (WM_USER + 1)
49
50 void CreateSocketInformation(SOCKET s);
51 LPSOCKET_INFORMATION GetSocketInformation(SOCKET s);
52 void FreeSocketInformation(SOCKET s);
53
54 HWND MakeWorkerWindow(void);
55 LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
56
57 LPSOCKET_INFORMATION SocketInfoList;
58
59 void main(void)
60 {
61 MSG msg;
62 DWORD Ret;
63 SOCKET Listen;
64 SOCKADDR_IN InternetAddr;
65 HWND Window;
66 WSADATA wsaData;
67
68 if ((Window = MakeWorkerWindow()) == NULL)
69 return;
70
71 // Prepare echo server
72
73 if ((Ret = WSAStartup(0x0202, &wsaData)) != 0)
74 {
75 printf("WSAStartup failed with error %d\n", Ret);
76 return;
77 }
78
79 if ((Listen = socket (PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
80 {
81 printf("socket() failed with error %d\n", WSAGetLastError());
82 return;
83 }
84
85 WSAAsyncSelect(Listen, Window, WM_SOCKET, FD_ACCEPT|FD_CLOSE);
86
87 InternetAddr.sin_family = AF_INET;
88 InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
89 InternetAddr.sin_port = htons(PORT);
90
91 if (bind(Listen, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr)) == SOCKET_ERROR)
92 {
93 printf("bind() failed with error %d\n", WSAGetLastError());
94 return;
95 }
96
97 if (listen(Listen, 5))
98 {
99 printf("listen() failed with error %d\n", WSAGetLastError());
100 return;
101 }
102
103 // Translate and dispatch window messages for the application thread
104
105 while(Ret = GetMessage(&msg, NULL, 0, 0))
106 {
107 if (Ret == -1)
108 {
109 printf("GetMessage() failed with error %d\n", GetLastError());
110 return;
111 }
112
113 TranslateMessage(&msg);
114 DispatchMessage(&msg);
115 }
116 }
117
118
119 LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
120 {
121 SOCKET Accept;
122 LPSOCKET_INFORMATION SocketInfo;
123 DWORD RecvBytes, SendBytes;
124 DWORD Flags;
125
126 if (uMsg == WM_SOCKET)
127 {
128 if (WSAGETSELECTERROR(lParam))
129 {
130 printf("Socket failed with error %d\n", WSAGETSELECTERROR(lParam));
131 FreeSocketInformation(wParam);
132 }
133 else
134 {
135 switch(WSAGETSELECTEVENT(lParam))
136 {
137 case FD_ACCEPT:
138
139 if ((Accept = accept(wParam, NULL, NULL)) == INVALID_SOCKET)
140 {
141 printf("accept() failed with error %d\n", WSAGetLastError());
142 break;
143 }
144
145 // Create a socket information structure to associate with the
146 // socket for processing I/O.
147
148 CreateSocketInformation(Accept);
149
150 printf("Socket number %d connected\n", Accept);
151
152 WSAAsyncSelect(Accept, hwnd, WM_SOCKET, FD_READ|FD_WRITE|FD_CLOSE);
153
154 break;
155
156 case FD_READ:
157
158 SocketInfo = GetSocketInformation(wParam);
159
160 // Read data only if the receive buffer is empty.
161
162 if (SocketInfo->BytesRECV != 0)
163 {
164 SocketInfo->RecvPosted = TRUE;
165 return 0;
166 }
167 else
168 {
169 SocketInfo->DataBuf.buf = SocketInfo->Buffer;
170 SocketInfo->DataBuf.len = DATA_BUFSIZE;
171
172 Flags = 0;
173 if (WSARecv(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &RecvBytes,
174 &Flags, NULL, NULL) == SOCKET_ERROR)
175 {
176 if (WSAGetLastError() != WSAEWOULDBLOCK)
177 {
178 printf("WSARecv() failed with error %d\n", WSAGetLastError());
179 FreeSocketInformation(wParam);
180 return 0;
181 }
182 }
183 else // No error so update the byte count
184 {
185 SocketInfo->BytesRECV = RecvBytes;
186 }
187 }
188
189 // DO NOT BREAK HERE SINCE WE GOT A SUCCESSFUL RECV. Go ahead
190 // and begin writing data to the client.
191
192 case FD_WRITE:
193
194 SocketInfo = GetSocketInformation(wParam);
195
196 if (SocketInfo->BytesRECV > SocketInfo->BytesSEND)
197 {
198 SocketInfo->DataBuf.buf = SocketInfo->Buffer + SocketInfo->BytesSEND;
199 SocketInfo->DataBuf.len = SocketInfo->BytesRECV - SocketInfo->BytesSEND;
200
201 if (WSASend(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &SendBytes, 0,
202 NULL, NULL) == SOCKET_ERROR)
203 {
204 if (WSAGetLastError() != WSAEWOULDBLOCK)
205 {
206 printf("WSASend() failed with error %d\n", WSAGetLastError());
207 FreeSocketInformation(wParam);
208 return 0;
209 }
210 }
211 else // No error so update the byte count
212 {
213 SocketInfo->BytesSEND += SendBytes;
214 }
215 }
216
217 if (SocketInfo->BytesSEND == SocketInfo->BytesRECV)
218 {
219 SocketInfo->BytesSEND = 0;
220 SocketInfo->BytesRECV = 0;
221
222 // If a RECV occurred during our SENDs then we need to post an FD_READ
223 // notification on the socket.
224
225 if (SocketInfo->RecvPosted == TRUE)
226 {
227 SocketInfo->RecvPosted = FALSE;
228 PostMessage(hwnd, WM_SOCKET, wParam, FD_READ);
229 }
230 }
231
232 break;
233
234 case FD_CLOSE:
235
236 printf("Closing socket %d\n", wParam);
237 FreeSocketInformation(wParam);
238
239 break;
240 }
241 }
242 return 0;
243 }
244
245 return DefWindowProc(hwnd, uMsg, wParam, lParam);
246 }
247
248
249 void CreateSocketInformation(SOCKET s)
250 {
251 LPSOCKET_INFORMATION SI;
252
253 if ((SI = (LPSOCKET_INFORMATION) GlobalAlloc(GPTR,
254 sizeof(SOCKET_INFORMATION))) == NULL)
255 {
256 printf("GlobalAlloc() failed with error %d\n", GetLastError());
257 return;
258 }
259
260 // Prepare SocketInfo structure for use.
261
262 SI->Socket = s;
263 SI->RecvPosted = FALSE;
264 SI->BytesSEND = 0;
265 SI->BytesRECV = 0;
266
267 SI->Next = SocketInfoList;
268
269 SocketInfoList = SI;
270 }
271
272 LPSOCKET_INFORMATION GetSocketInformation(SOCKET s)
273 {
274 SOCKET_INFORMATION *SI = SocketInfoList;
275
276 while(SI)
277 {
278 if (SI->Socket == s)
279 return SI;
280
281 SI = SI->Next;
282 }
283
284 return NULL;
285 }
286
287 void FreeSocketInformation(SOCKET s)
288 {
289 SOCKET_INFORMATION *SI = SocketInfoList;
290 SOCKET_INFORMATION *PrevSI = NULL;
291
292 while(SI)
293 {
294 if (SI->Socket == s)
295 {
296 if (PrevSI)
297 PrevSI->Next = SI->Next;
298 else
299 SocketInfoList = SI->Next;
300
301 closesocket(SI->Socket);
302 GlobalFree(SI);
303 return;
304 }
305
306 PrevSI = SI;
307 SI = SI->Next;
308 }
309 }
310
311 HWND MakeWorkerWindow(void)
312 {
313 WNDCLASS wndclass;
314 CHAR *ProviderClass = "AsyncSelect";
315 HWND Window;
316
317 wndclass.style = CS_HREDRAW | CS_VREDRAW;
318 wndclass.lpfnWndProc = (WNDPROC)WindowProc;
319 wndclass.cbClsExtra = 0;
320 wndclass.cbWndExtra = 0;
321 wndclass.hInstance = NULL;
322 wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
323 wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
324 wndclass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
325 wndclass.lpszMenuName = NULL;
326 wndclass.lpszClassName = ProviderClass;
327
328 if (RegisterClass(&wndclass) == 0)
329 {
330 printf("RegisterClass() failed with error %d\n", GetLastError());
331 return NULL;
332 }
333
334 // Create a window.
335
336 if ((Window = CreateWindow(
337 ProviderClass,
338 "",
339 WS_OVERLAPPEDWINDOW,
340 CW_USEDEFAULT,
341 CW_USEDEFAULT,
342 CW_USEDEFAULT,
343 CW_USEDEFAULT,
344 NULL,
345 NULL,
346 NULL,
347 NULL)) == NULL)
348 {
349 printf("CreateWindow() failed with error %d\n", GetLastError());
350 return NULL;
351 }
352
353 return Window;
354 }
注意到代码中实现了FD_WRITE,于是屏蔽了一下该分支代码块的具体内容。这样一来,跟我的代码在逻辑上的差别就是:我没有实现FD_WRITE分支。
考虑到MSDN的文档太多了,今又是周五,不想细看,就试了下在自己代码中加上了对FD_WRITE_BIT的处理——空代码块。
1 case FD_WRITE:
2 {
3
4 }
5 break;
居然成功了。
后来又尝试了多次,发现,在WSAAsyncSelect中列出了哪个FD_XXXX,就要实现哪个,对于不想实现的,不要列出。
(至于为什么,这就要看文档了。)
========================这难道就是传说中的,“如果爱 请深爱”?========================
================================耻辱墙===================================
http://www.cnblogs.com/wlsandwho/p/4206472.html
===============================额外扩展===================================
那么,FD_WRITE又是什么时候触发呢?
搜索了一番,找到一个比较靠谱的:http://bbs.csdn.net/topics/200074769
现摘录12楼cpp_programer的总结:
当一个套接字连接被 建立上时(包括客户端的connect(),connectex()等和服务器端的accept接收到后创建的新套接字),这时会触发 FD_WRITE,以后就可以用send(),WSASend()发送数据了.如果以后发送正常的话,将不会再触发FD_WRITE. 如果发送数据不正常的话,即用send(),WSASend()等发数据,WSAGetLastError()返回WSAEWOULDBLOCK错误,那么等到系统缓冲可再发送数据时,就会触发FD_WRITE.
|