==================================声明==================================
本文原创,转载在正文中显要的注明作者和出处,并保证文章的完整性。
未经作者同意请勿修改(包括本声明),保留法律追究的权利。
未经作者同意请勿用于出版、印刷或学术引用。
本文不定期修正完善,为保证内容正确,建议移步原文处阅读。
本文链接: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.