问题解决——WSAAsyncSelect模型 不触发 FD_CLOSE

本文链接:http://www.cnblogs.com/wlsandwho/p/4228894.html

======================================================================

 最近在写一个小东西,需要用到非阻塞模式的套接字,考虑到用的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,就要实现哪个,对于不想实现的,不要列出。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值