winsock编程WSAAsyncSelect模型
winsock编程WSAAsyncSelect模型
WSAAsyncSelect模型也称异步选择模型,其核心函数是WSAAsyncSelect。它可以用来在一个socket上接收以windows消息为基础的网络事件。它提供了读写数据的异步通知功能,但不提供异步数据传送。WSAAsyncSelect模型的优势在于只需要一个主线程即可。缺点是必须要绑定窗口句柄。
1:WSAAsyncSelect函数定义
Description:The WSAAsyncSelect function requests Windows message-based notification of network events for a socket.
1 int WSAAsyncSelect( 2 __in SOCKET s, 3 __in HWND hWnd, 4 __in unsigned int wMsg, 5 __in long lEvent 6 );
Parameters
- s
-
A descriptor that identifies the socket for which event notification is required.
- hWnd
-
A handle that identifies the window that will receive a message when a network event occurs.
接收网络事件消息的窗口句柄
- wMsg
-
A message to be received when a network event occurs.
网络事件消息
- lEvent
-
A bitmask that specifies a combination of network events in which the application is interested.
网络事件,windows已定义好网络事件,如FD_CONNECT、FD_CLOSE、FD_ACCEPT等。
Return Value
If the WSAAsyncSelect function succeeds, the return value is zero, provided that the application's declaration of interest in the network event set was successful. Otherwise, the value SOCKET_ERROR is returned, and a specific error number can be retrieved by calling WSAGetLastError.
2:网络事件说明
MSDN上列出如下常见网络事件:
MSDN对上述网络事件及关联函数触发关系的解释如下:
Here is a summary of events and conditions for each asynchronous notification message.
- FD_READ:
- When WSAAsyncSelect is called, if there is data currently available to receive. ------ WSAAsyncSelect 调用后,新的数据等待接收。
- When data arrives, if FD_READ is not already posted. ------ 新的数据到达,而FD_READ 还没有传递。
- After recv or recvfrom is called, with or without MSG_PEEK), if data is still available to receive. ----- 使用recv等函数后,仍有数据等待接收。MSG_PEEK解释:MSG_PEEK可作为recv等函数最后一个参数的标志位传入。如果recv带有MSG_PEEK,则recv读取数据后,并不会把数据从缓冲区取出来,这样可以方便其他recv调用方继续读取缓冲区数据。如果recv不带有MSG_PEEK,recv读取一定长度数据后,缓冲区将移除该部分数据。
Note When setsockopt SO_OOBINLINE is enabled, data includes both normal data and OOB data in the instances noted above.
- FD_WRITE:
- When WSAAsyncSelect called, if a send or sendto is possible. ----- WSAAsyncSelect调用的时候
- After connect or accept called, when connection established. ----- connect或accept调用的时候,新连接到达时进入
- After send or sendto fail with WSAEWOULDBLOCK, when send or sendto are likely to succeed. ----- send等函数发送数据,但缓冲区满了,部分数据未能及时发送出去,此时将产生 WSAEWOULDBLOCK错误码。此后,系统将产生该事件,通知用户重新发送数据。
- After bind on a connectionless socket. FD_WRITE may or may not occur at this time (implementation-dependent). In any case, a connectionless socket is always writeable immediately after a bind operation.
- FD_OOB: Only valid when setsockopt SO_OOBINLINE is disabled (default).
- FD_ACCEPT:
- When WSAAsyncSelect called, if there is currently a connection request available to accept. ----- WSAAsyncSelect 调用后,有连接请求等待accept
- When a connection request arrives, if FD_ACCEPT not already posted.
- After accept called, if there is another connection request available to accept. ----- accept连接后,另外有一个连接等待accept
- FD_CONNECT:
- When WSAAsyncSelect called, if there is currently a connection established.----- WSAAsyncSelect 调用后,有连接建立时。
- After connect called, when connection is established, even when connect succeeds immediately, as is typical with a datagram socket.
- After calling WSAJoinLeaf, when join operation completes.
- After connect, WSAConnect, or WSAJoinLeaf was called with a nonblocking, connection-oriented socket. The initial operation returned with a specific error of WSAEWOULDBLOCK, but the network operation went ahead. Whether the operation eventually succeeds or not, when the outcome has been determined, FD_CONNECT happens. The client should check the error code to determine whether the outcome was successful or failed.
- FD_CLOSE: Only valid on connection-oriented sockets (for example, SOCK_STREAM)
- When WSAAsyncSelect called, if socket connection has been closed. ----- 连接关闭时进入
- After remote system initiated graceful close, when no data currently available to receive (Be aware that, if data has been received and is waiting to be read when the remote system initiates a graceful close, the FD_CLOSE is not delivered until all pending data has been read).
- After local system initiates graceful close with shutdown and remote system has responded with "End of Data" notification (for example, TCP FIN), when no data currently available to receive.
- When remote system terminates connection (for example, sent TCP RST), and lParam will contain WSAECONNRESET error value.
Note FD_CLOSE is not posted after closesocket is called.---- 获取FD_CLOS后应调用closesocket
- FD_QOS:
- When WSAAsyncSelect called, if the quality of service associated with the socket has been changed.
- After WSAIoctl with SIO_GET_QOS called, when the quality of service is changed.
- FD_GROUP_QOS: Reserved.
- FD_ROUTING_INTERFACE_CHANGE:
- After WSAIoctl with SIO_ROUTING_INTERFACE_CHANGE called, when the local interface that should be used to reach the destination specified in the IOCTL changes.
- FD_ADDRESS_LIST_CHANGE:
- After WSAIoctl with SIO_ADDRESS_LIST_CHANGE called, when the list of local addresses to which the application can bind changes.
连续调用两次WSAAsyncSelect函数,后设置的事件标志位将替换先前设置的事件标志位。设置多重事件,需要用到或运算,如FD_READ|FD_WRITE。
3:自定义消息传参
The wParam parameter identifies the socket on which a network event has occurred. The low word of lParam specifies the network event that has occurred. The high word of lParam contains any error code. The error code be any error as defined in Winsock2.h.
WSAGETSELECTEVENT和WSAGETSELECTERROR宏
Description:The error and event codes can be extracted from the lParam using the macros WSAGETSELECTERROR and WSAGETSELECTEVENT, defined in Winsock2.h as:
1 #define WSAGETSELECTERROR(lParam) HIWORD(lParam) 2 #define WSAGETSELECTEVENT(lParam) LOWORD(lParam)
自定义消息函数的传递参数中,wParam 标识socket描述符。lParam 的低字节标识了网络事件,lParam 的高字节标识了错误码。分别用WSAGETSELECTEVENT和WSAGETSELECTERROR可以取出lparam内的网络事件和错误码。
4:测试程序
WSAAsyncSelect 传参需要窗口句柄,为了简化代码,我直接创建了一个mfc对话框程序,用m_hwnd给WSAAsyncSelect 传参。对话框类名为WSAAsyncSelecDlg。
A:定义变量
1 bool m_bRes; //用作socket流程各函数调用依据 2 WSAData m_wsa; //wsastartup参数 3 SOCKET m_listensocket; //监听socket
B:定义消息宏及消息映射函数
#define WM_SOCK WM_USER+1 afx_msg LRESULT OnSocket(WPARAM w,LPARAM l); ON_MESSAGE(WM_SOCK,OnSocket)
C:在OnInitDialog内创建监听socket
1 m_bRes = true; 2 WSAStartup(MAKEWORD(2,3),&m_wsa); 3 m_listensocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); 4 if (m_listensocket == INVALID_SOCKET ) 5 { 6 m_bRes = false; 7 } 8 sockaddr_in m_server; 9 m_server.sin_family = AF_INET; 10 m_server.sin_port = htons(8828); 11 m_server.sin_addr.s_addr = inet_addr("127.0.0.1"); 12 if (m_bRes && (bind(m_listensocket,(sockaddr*)&m_server,sizeof(sockaddr_in)) == SOCKET_ERROR)) 13 { 14 DWORD dw = WSAGetLastError(); 15 m_bRes = false; 16 } 17 if (m_bRes && (listen(m_listensocket,SOMAXCONN) == SOCKET_ERROR)) 18 { 19 m_bRes = false; 20 } 21 if (m_bRes && (WSAAsyncSelect(m_listensocket,m_hWnd,WM_SOCK,FD_ACCEPT) == SOCKET_ERROR)) 22 { 23 m_bRes = false; 24 }
D:实现消息映射函数
1 LRESULT CWSAAsyncSelecDlg::OnSocket(WPARAM w,LPARAM l) 2 { 3 SOCKET s = (SOCKET)w; 4 switch (WSAGETSELECTEVENT(l)) 5 { 6 case FD_ACCEPT: 7 {//有网络连接到达 8 sockaddr_in m_client; 9 int sz = sizeof(sockaddr_in); 10 SOCKET acp = accept(m_listensocket,(sockaddr*)&m_client,&sz); 11 if (acp == INVALID_SOCKET) 12 { 13 closesocket(m_listensocket); 14 return 0; 15 } 16 WSAAsyncSelect(acp,m_hWnd,WM_SOCK,FD_READ|FD_WRITE|FD_CLOSE); 17 } 18 break; 19 case FD_READ: 20 {//缓冲区有数据待接收时进入 21 char buf[1024]; 22 int res = recv(s,buf,1024,0); 23 if (res == 0) 24 { 25 closesocket(s); 26 break; 27 } 28 else if (res == SOCKET_ERROR) 29 { 30 //socket error 31 break; 32 } 33 else 34 { 35 buf[res] = 0; 36 std::string str = buf; 37 str += "\n"; 38 OutputDebugString(str.c_str()); 39 str = "WSAAsyncSelect test"; 40 int res = send(s,str.c_str(),str.length(),0); 41 if (res == SOCKET_ERROR) 42 { 43 break; 44 } 45 } 46 } 47 break; 48 case FD_WRITE: 49 {//1:新连接到达时进入 2:缓冲区满数据未发送完全时进入 50 std::string str = "WSAAsyncSelect test"; 51 int res = send(s,str.c_str(),str.length(),0); 52 if (res == SOCKET_ERROR) 53 { 54 break; 55 } 56 } 57 break; 58 case FD_CLOSE: 59 {//客户端关闭连接时进入 60 closesocket(s); 61 } 62 break; 63 } 64 return 1; 65 }
E:测试结果
客户端程序用的是http://www.cnblogs.com/hgwang/p/6086237.html里面的代码。