首页 > 代码库 > 问题解决——WSAAsyncSelect模型不触发FD_CLOSE
问题解决——WSAAsyncSelect模型不触发FD_CLOSE
==================================声明==================================
本文原创,转载在正文中显要的注明作者和出处,并保证文章的完整性。
未经作者同意请勿修改(包括本声明),保留法律追究的权利。
本文不定期修正完善,为保证内容正确,建议移步原文处阅读。
本文链接: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 thread104 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 else134 {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 the146 // 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 else168 {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 count184 {185 SocketInfo->BytesRECV = RecvBytes;186 }187 }188 189 // DO NOT BREAK HERE SINCE WE GOT A SUCCESSFUL RECV. Go ahead190 // 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 count212 {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_READ223 // 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 else299 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
问题解决——WSAAsyncSelect模型不触发FD_CLOSE