
正文
问题解决——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:
WSAAsyncSelect(m_sockListen,hWnd,m_wMsgServer,FD_ACCEPT|FD_CLOSE)
又看了下接受连接后的WSAAsyncSelect
WSAAsyncSelect(sockClient, hWnd, m_wMsgServer, FD_READ|FD_WRITE|FD_CLOSE)
感觉没什么问题啊,好奇怪。
=======================================================================
稍微看了下自己的代码后,决定搜一下网上的资料,自然的无果。
只好对照《Windows网络编程技术》的源码看看有什么忽略的地方。
贴一下书本的源码先:(要用UNICODE还要稍微改改。)
// Module Name: asyncselect.cpp
//
// Description:
//
// This sample illustrates how to develop a simple echo server Winsock
// application using the WSAAsyncSelect() I/O model. This sample is
// implemented as a console-style application (to reduce the programming
// complexity of writing a real Windows application) and simply prints
// messages when connections are established and removed from the server.
// The application listens for TCP connections on port 5150 and accepts them
// as they arrive. When this application receives data from a client, it
// simply echos (this is why we call it an echo server) the data back in
// it's original form until the client closes the connection.
//
// Since the WSAAsyncSelect I/O model requires an application to manage
// window messages when network event occur, this application creates
// a window for the I/O model only. The window stays hidden during the
// entire execution of this application.
//
// Compile:
//
// cl -o asyncselect asyncselect.cpp ws2_32.lib user32.lib gdi32.lib
//
// Command Line Options:
//
// asyncselect.exe
//
// Note: There are no command line options for this sample. #include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <conio.h> #define PORT 5150
#define DATA_BUFSIZE 8192 typedef struct _SOCKET_INFORMATION {
BOOL RecvPosted;
CHAR Buffer[DATA_BUFSIZE];
WSABUF DataBuf;
SOCKET Socket;
DWORD BytesSEND;
DWORD BytesRECV;
_SOCKET_INFORMATION *Next;
} SOCKET_INFORMATION, * LPSOCKET_INFORMATION; #define WM_SOCKET (WM_USER + 1) void CreateSocketInformation(SOCKET s);
LPSOCKET_INFORMATION GetSocketInformation(SOCKET s);
void FreeSocketInformation(SOCKET s); HWND MakeWorkerWindow(void);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); LPSOCKET_INFORMATION SocketInfoList; void main(void)
{
MSG msg;
DWORD Ret;
SOCKET Listen;
SOCKADDR_IN InternetAddr;
HWND Window;
WSADATA wsaData; if ((Window = MakeWorkerWindow()) == NULL)
return; // Prepare echo server if ((Ret = WSAStartup(0x0202, &wsaData)) != )
{
printf("WSAStartup failed with error %d\n", Ret);
return;
} if ((Listen = socket (PF_INET, SOCK_STREAM, )) == INVALID_SOCKET)
{
printf("socket() failed with error %d\n", WSAGetLastError());
return;
} WSAAsyncSelect(Listen, Window, WM_SOCKET, FD_ACCEPT|FD_CLOSE); InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
InternetAddr.sin_port = htons(PORT); if (bind(Listen, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr)) == SOCKET_ERROR)
{
printf("bind() failed with error %d\n", WSAGetLastError());
return;
} if (listen(Listen, ))
{
printf("listen() failed with error %d\n", WSAGetLastError());
return;
} // Translate and dispatch window messages for the application thread while(Ret = GetMessage(&msg, NULL, , ))
{
if (Ret == -)
{
printf("GetMessage() failed with error %d\n", GetLastError());
return;
} TranslateMessage(&msg);
DispatchMessage(&msg);
}
} LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
SOCKET Accept;
LPSOCKET_INFORMATION SocketInfo;
DWORD RecvBytes, SendBytes;
DWORD Flags; if (uMsg == WM_SOCKET)
{
if (WSAGETSELECTERROR(lParam))
{
printf("Socket failed with error %d\n", WSAGETSELECTERROR(lParam));
FreeSocketInformation(wParam);
}
else
{
switch(WSAGETSELECTEVENT(lParam))
{
case FD_ACCEPT: if ((Accept = accept(wParam, NULL, NULL)) == INVALID_SOCKET)
{
printf("accept() failed with error %d\n", WSAGetLastError());
break;
} // Create a socket information structure to associate with the
// socket for processing I/O. CreateSocketInformation(Accept); printf("Socket number %d connected\n", Accept); WSAAsyncSelect(Accept, hwnd, WM_SOCKET, FD_READ|FD_WRITE|FD_CLOSE); break; case FD_READ: SocketInfo = GetSocketInformation(wParam); // Read data only if the receive buffer is empty. if (SocketInfo->BytesRECV != )
{
SocketInfo->RecvPosted = TRUE;
return ;
}
else
{
SocketInfo->DataBuf.buf = SocketInfo->Buffer;
SocketInfo->DataBuf.len = DATA_BUFSIZE; Flags = ;
if (WSARecv(SocketInfo->Socket, &(SocketInfo->DataBuf), , &RecvBytes,
&Flags, NULL, NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != WSAEWOULDBLOCK)
{
printf("WSARecv() failed with error %d\n", WSAGetLastError());
FreeSocketInformation(wParam);
return ;
}
}
else // No error so update the byte count
{
SocketInfo->BytesRECV = RecvBytes;
}
} // DO NOT BREAK HERE SINCE WE GOT A SUCCESSFUL RECV. Go ahead
// and begin writing data to the client. case FD_WRITE: SocketInfo = GetSocketInformation(wParam); if (SocketInfo->BytesRECV > SocketInfo->BytesSEND)
{
SocketInfo->DataBuf.buf = SocketInfo->Buffer + SocketInfo->BytesSEND;
SocketInfo->DataBuf.len = SocketInfo->BytesRECV - SocketInfo->BytesSEND; if (WSASend(SocketInfo->Socket, &(SocketInfo->DataBuf), , &SendBytes, ,
NULL, NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != WSAEWOULDBLOCK)
{
printf("WSASend() failed with error %d\n", WSAGetLastError());
FreeSocketInformation(wParam);
return ;
}
}
else // No error so update the byte count
{
SocketInfo->BytesSEND += SendBytes;
}
} if (SocketInfo->BytesSEND == SocketInfo->BytesRECV)
{
SocketInfo->BytesSEND = ;
SocketInfo->BytesRECV = ; // If a RECV occurred during our SENDs then we need to post an FD_READ
// notification on the socket. if (SocketInfo->RecvPosted == TRUE)
{
SocketInfo->RecvPosted = FALSE;
PostMessage(hwnd, WM_SOCKET, wParam, FD_READ);
}
} break; case FD_CLOSE: printf("Closing socket %d\n", wParam);
FreeSocketInformation(wParam); break;
}
}
return ;
} return DefWindowProc(hwnd, uMsg, wParam, lParam);
} void CreateSocketInformation(SOCKET s)
{
LPSOCKET_INFORMATION SI; if ((SI = (LPSOCKET_INFORMATION) GlobalAlloc(GPTR,
sizeof(SOCKET_INFORMATION))) == NULL)
{
printf("GlobalAlloc() failed with error %d\n", GetLastError());
return;
} // Prepare SocketInfo structure for use. SI->Socket = s;
SI->RecvPosted = FALSE;
SI->BytesSEND = ;
SI->BytesRECV = ; SI->Next = SocketInfoList; SocketInfoList = SI;
} LPSOCKET_INFORMATION GetSocketInformation(SOCKET s)
{
SOCKET_INFORMATION *SI = SocketInfoList; while(SI)
{
if (SI->Socket == s)
return SI; SI = SI->Next;
} return NULL;
} void FreeSocketInformation(SOCKET s)
{
SOCKET_INFORMATION *SI = SocketInfoList;
SOCKET_INFORMATION *PrevSI = NULL; while(SI)
{
if (SI->Socket == s)
{
if (PrevSI)
PrevSI->Next = SI->Next;
else
SocketInfoList = SI->Next; closesocket(SI->Socket);
GlobalFree(SI);
return;
} PrevSI = SI;
SI = SI->Next;
}
} HWND MakeWorkerWindow(void)
{
WNDCLASS wndclass;
CHAR *ProviderClass = "AsyncSelect";
HWND Window; wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = (WNDPROC)WindowProc;
wndclass.cbClsExtra = ;
wndclass.cbWndExtra = ;
wndclass.hInstance = NULL;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = ProviderClass; if (RegisterClass(&wndclass) == )
{
printf("RegisterClass() failed with error %d\n", GetLastError());
return NULL;
} // Create a window. if ((Window = CreateWindow(
ProviderClass,
"",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
NULL,
NULL)) == NULL)
{
printf("CreateWindow() failed with error %d\n", GetLastError());
return NULL;
} return Window;
}
注意到代码中实现了FD_WRITE,于是屏蔽了一下该分支代码块的具体内容。这样一来,跟我的代码在逻辑上的差别就是:我没有实现FD_WRITE分支。
考虑到MSDN的文档太多了,今又是周五,不想细看,就试了下在自己代码中加上了对FD_WRITE_BIT的处理——空代码块。
case FD_WRITE:
{ }
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.







