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里面的代碼。