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

相关文章
相关标签/搜索