winsock編程WSAAsyncSelect模型

原文連接地址:http://www.javashuo.com/article/p-mvmlmoeg-v.htmlhtml

WSAAsyncSelect模型也稱異步選擇模型,其核心函數是WSAAsyncSelect。它能夠用來在一個socket上接收以windows消息爲基礎的網絡事件。它提供了讀寫數據的異步通知功能,但不提供異步數據傳送。WSAAsyncSelect模型的優點在於只須要一個主線程便可。缺點是必需要綁定窗口句柄。 windows

1:WSAAsyncSelect函數定義

Description:The WSAAsyncSelect function requests Windows message-based notification of network events for a socket.網絡

int WSAAsyncSelect(
  __in          SOCKET s,
  __in          HWND hWnd,
  __in          unsigned int wMsg,
  __in          long lEvent
);
Parameters
s

A descriptor that identifies the socket for which event notification is required. app

hWnd

A handle that identifies the window that will receive a message when a network event occurs. less

接收網絡事件消息的窗口句柄 異步

wMsg

A message to be received when a network event occurs. socket

網絡事件消息 async

lEvent

A bitmask that specifies a combination of network events in which the application is interested. ide

網絡事件,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:
    1. When WSAAsyncSelect is called, if there is data currently available to receive.   ------ WSAAsyncSelect 調用後,新的數據等待接收。
    2. When data arrives, if FD_READ is not already posted. ------  新的數據到達,而FD_READ 尚未傳遞。
    3. 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:
    1. When WSAAsyncSelect called, if a send or sendto is possible.  ----- WSAAsyncSelect調用的時候
    2. After connect or accept called, when connection established.  ----- connect或accept調用的時候,新鏈接到達時進入
    3. After send or sendto fail with WSAEWOULDBLOCK, when send or sendto are likely to succeed.  ----- send等函數發送數據,但緩衝區滿了,部分數據未能及時發送出去,此時將產生 WSAEWOULDBLOCK錯誤碼。此後,系統將產生該事件,通知用戶從新發送數據。
    4. 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).
    1. When WSAAsyncSelect called, if there is OOB data currently available to receive with the MSG_OOB flag.
    2. When OOB data arrives, if FD_OOB not already posted.
    3. After recv or recvfrom called with or without MSG_OOB flag, if OOB data is still available to receive.
  • FD_ACCEPT:
    1. When WSAAsyncSelect called, if there is currently a connection request available to accept. ----- WSAAsyncSelect 調用後,有鏈接請求等待accept
    2. When a connection request arrives, if FD_ACCEPT not already posted.
    3. After accept called, if there is another connection request available to accept. ----- accept鏈接後,另外有一個鏈接等待accept
  • FD_CONNECT:
    1. When WSAAsyncSelect called, if there is currently a connection established.----- WSAAsyncSelect 調用後,有鏈接創建時。
    2. After connect called, when connection is established, even when connect succeeds immediately, as is typical with a datagram socket.
    3. After calling WSAJoinLeaf, when join operation completes.
    4. 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)
    1. When WSAAsyncSelect called, if socket connection has been closed.  ----- 鏈接關閉時進入
    2. 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).
    3. 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.
    4. 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:
    1. When WSAAsyncSelect called, if the quality of service associated with the socket has been changed.
    2. 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:

#define WSAGETSELECTERROR(lParam)       HIWORD(lParam)
#define WSAGETSELECTEVENT(lParam)       LOWORD(lParam)

自定義消息函數的傳遞參數中,wParam 標識socket描述符。lParam 的低字節標識了網絡事件,lParam 的高字節標識了錯誤碼。分別用WSAGETSELECTEVENT和WSAGETSELECTERROR能夠取出lparam內的網絡事件和錯誤碼。

4:測試程序

WSAAsyncSelect 傳參須要窗口句柄,爲了簡化代碼,我直接建立了一個mfc對話框程序,用m_hwnd給WSAAsyncSelect 傳參。對話框類名爲WSAAsyncSelecDlg。

A:定義變量

bool    m_bRes;            //用做socket流程各函數調用依據
WSAData    m_wsa;            //wsastartup參數
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

m_bRes = true;
    WSAStartup(MAKEWORD(2,3),&m_wsa);
    m_listensocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if (m_listensocket == INVALID_SOCKET )
    {
        m_bRes = false;
    }
    sockaddr_in    m_server;
    m_server.sin_family = AF_INET;
    m_server.sin_port = htons(8828);
    m_server.sin_addr.s_addr = inet_addr("127.0.0.1");
    if (m_bRes && (bind(m_listensocket,(sockaddr*)&m_server,sizeof(sockaddr_in)) == SOCKET_ERROR))
    {
        DWORD dw = WSAGetLastError();
        m_bRes = false;
    }
    if (m_bRes && (listen(m_listensocket,SOMAXCONN) == SOCKET_ERROR))
    {
        m_bRes = false;
    }
    if (m_bRes && (WSAAsyncSelect(m_listensocket,m_hWnd,WM_SOCK,FD_ACCEPT) == SOCKET_ERROR))
    {
        m_bRes = false;
    }

D:實現消息映射函數

LRESULT CWSAAsyncSelecDlg::OnSocket(WPARAM w,LPARAM l)
{
    SOCKET s = (SOCKET)w;
    switch (WSAGETSELECTEVENT(l))
    {
    case FD_ACCEPT:
        {//有網絡鏈接到達
            sockaddr_in    m_client;
            int sz = sizeof(sockaddr_in);
            SOCKET acp = accept(m_listensocket,(sockaddr*)&m_client,&sz);
            if (acp == INVALID_SOCKET)
            {
                closesocket(m_listensocket);
                return 0;
            }
            WSAAsyncSelect(acp,m_hWnd,WM_SOCK,FD_READ|FD_WRITE|FD_CLOSE);
        }
        break;
    case FD_READ:
        {//緩衝區有數據待接收時進入
            char buf[1024];
            int res = recv(s,buf,1024,0);
            if (res == 0)
            {
                closesocket(s);
                break;
            }
            else if (res == SOCKET_ERROR)
            {
                //socket error
                break;
            }
            else
            {
                buf[res] = 0;
                std::string str = buf;
                str += "\n";
                OutputDebugString(str.c_str());
                str = "WSAAsyncSelect test";
                int res = send(s,str.c_str(),str.length(),0);
                if (res == SOCKET_ERROR)
                {
                    break;
                }
            }
        }
        break;
    case FD_WRITE:
        {//1:新鏈接到達時進入  2:緩衝區滿數據未發送徹底時進入
            std::string str = "WSAAsyncSelect test";
            int res = send(s,str.c_str(),str.length(),0);
            if (res == SOCKET_ERROR)
            {
                break;
            }
        }
        break;
    case FD_CLOSE:
        {//客戶端關閉鏈接時進入
            closesocket(s);
        }
        break;
    }
    return 1;
}
 

E:測試結果

客戶端程序用的是http://www.cnblogs.com/hgwang/p/6086237.html裏面的代碼。 

相關文章
相關標籤/搜索