异步选择模型

    异步选择(WSAAsyncSelect)模型是一个有用的异步 I/O 模型。利用这个模型,应用程序可在一个套接字上,接收以 Windows 消息为基础的网络事件通知。具体

的做法是在建好一个套接字后,调用WSAAsyncSelect函数。该模型的核心即是WSAAsyncSelect函数。

    WSAAsyncSelect函数定义如下:

    int WSAAsyncSelect(

        __in SOCKET s,              //指定的是我们感兴趣的那个套接字。

        __in HWND hWnd,          //指定一个窗口句柄,它对应于网络事件发生之后,想要收到通知消息的那个窗口。

        __in unsigned int wMsg,  //指定在发生网络事件时,打算接收的消息。该消息会投递到由hWnd窗口句柄指定的那个窗口。

        __in long lEvent              //指定一个位掩码,对应于一系列网络事件的组合
   );

注意:

      1、wMsg参数指定的消息通常是我们自定义的消息,应用程序需要将这个消息设为比Windows的WM_USER大的一个值,避免网络窗口消息与系统预定义的标准窗

口消息发生混淆与冲突。

      2、lEvent参数指定的网络类型为:FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT、FD_CLOSE等。当然,到底使用FD_ACCEPT,还是使用

FD_CONNECT类型,要取决于应用程序的身份是客户端,还是服务器。如应用程序同时对多个网络事件有兴趣,只需对各种类型执行一次简单的按位OR(或)运算就OK。

      FD_READ              应用程序想要接收有关是否可读的通知,以便读入数据

      FD_WRITE            应用程序想要接收有关是否可写的通知,以便写入数据

      FD_ACCEPT           应用程序想接收与进入连接有关的通知

      FD_CONNECT        应用程序想接收与一次连接完成的通知

      FD_CLOSE            应用程序想接收与套接字关闭的通知

      3、多个事件务必在套接字上一次注册!另外还要注意的是,一旦在某个套接字上允许了事件通知,那么以后除非明确调用closesocket命令,或者由应用程序针对那

个套接字调用了WSAAsyncSelect,从而更改了注册的网络事件类型,否则的话,事件通知会永远有效!若将lEvent参数设为0,效果相当于停止在套接字上进行的所有

网络事件通知。

      4、若应用程序针对一个套接字调用了WSAAsyncSelect,那么套接字的模式会从“锁定”变成“非锁定”。这样一来,如果调用了像WSARecv这样的Winsock函数,

但当时却并没有数据可用,那么必然会造成调用的失败,并返回WSAEWOULDBLOCK错误。为防止这一点,应用程序应依赖于由WSAAsyncSelect的uMsg参数指定的

用户自定义窗口消息,来判断网络事件类型何时在套接字上发生;而不应盲目地进行调用。

     应用程序在一个套接字上成功调用了WSAAsyncSelect之后,会在与hWnd窗口句柄对应的窗口例程中,以Windows消息的形式,接收网络事件通知。窗口例程通常

定义如下:

    LRESULT CALLBACK WindowProc(

    HWND hwnd,           //指定一个窗口的句柄,对窗口例程的调用正是由那个窗口发出的。

    UINT uMsg,             //指定需要对哪些消息进行处理。这里我们感兴趣的是WSAAsyncSelect调用中定义的消息。

    WPARAM wParam,   //指定在其上面发生了一个网络事件的套接字。(假若同时为这个窗口例程分配了多个套接字,这个参数的重要性便显示出来了。)

    LPARAM lParam       //包含了两方面重要的信息。其中, lParam的低字(低位字)指定了已经发生的网络事件,而lParam的高字(高位字)包含了可能出现的任何

                                //错误代码。

    );

    流程:网络事件消息抵达一个窗口例程后,应用程序首先应检查lParam的高字位,以判断是否在网络错误。这里有一个特殊的宏: WSAGETSELECTERROR,可用

它返回高字位包含的错误信息。若应用程序发现套接字上没有产生任何错误,接着便应调查到底是哪个网络事件类型,具体的做法便是读取lParam低字位的内容。此时可

使用另一个特殊的宏:WSAGETSELECTEVENT,用它返回lParam的低字部分。

    FD_WRITE 事件通知:只有在三种条件下,才会发出 FD_WRITE 通知:

        1、使用 connect 或 WSAConnect,一个套接字首次建立了连接。

        2、使用 accept 或 WSAAccept,套接字被接受以后。

        3、若 send、WSASend、sendto 或 WSASendTo 操作失败,返回了 WSAEWOULDBLOCK 错误,而且缓冲区的空间变得可用。

    下面,我们来看看具体的演示,客户端是用select模型实现的,服务器端是用WSAAsyncSelect实现的,两者界面都相当的简洁

    服务器界面如下:    

    

    客户端界面如下:    

    

    其中,多个客户端可以同时与服务器端实现了,由于使用WSAAsyncSelect模型及我只起一个工作线程的缘故,故最多只能接受64个客户端。    

    

   理论知识与演示完之后,我们来看看服务器端具体的代码实现

   1、当然是初始化socket了,没什么好说的

    WSAData data;
    int error;
    error = WSAStartup(MAKEWORD(2, 2), &data);
    if (0 != error)
    {
        return FALSE;
    }
    if(HIBYTE(data.wVersion) != 2 && LOBYTE(data.wVersion))
{
WSACleanup();
return FALSE;
}
return TRUE;

    2、定义一个自定义消息,前面理论知识已讲过就不再重复了    

#define WM_SOCKET WM_USER + 0x01

    3、开启一个工作线程,初始化socket,开始监听。其实,这个线程可以封装成一个函数,具体看代码就知道了,封装成线程只是个人习惯哈。  

UINT ThreadProc(LPVOID lpParameter)
{
    CWSAAsyncSelectDlg *pDlg = (CWSAAsyncSelectDlg*)lpParameter;
    ASSERT(pDlg != NULL);

    pDlg->m_listenSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (INVALID_SOCKET == pDlg->m_listenSocket)
    {
        return FALSE;
    }
    char ipbuf[1024] = {0};
    wcstombs(ipbuf, pDlg->GetIpAddress(), pDlg->GetIpAddress().GetLength());
    const char *p = ipbuf;
    sockaddr_in serverAddress;
    serverAddress.sin_addr.S_un.S_addr = inet_addr(p);
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(pDlg->m_iPort);

    if (SOCKET_ERROR == bind(pDlg->m_listenSocket, (sockaddr*)&serverAddress, sizeof(sockaddr_in)))
    {
        return FALSE;
    }

    WSAAsyncSelect(pDlg->m_listenSocket, pDlg->GetSafeHwnd(), WM_SOCKET, FD_ACCEPT | FD_CLOSE);

    listen(pDlg->m_listenSocket, SOMAXCONN);

    pDlg->ShowText(_T("系统消息:服务器开始监听。。。"));

    return TRUE;
}

    4、WSAAsyncSelect模型的核心就是这个窗口过程,但是格式也是一样一样的。。。

LRESULT CWSAAsyncSelectDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
    // TODO: 在此添加专用代码和/或调用基类
    TCHAR buf[1024] = {0};
    CString cstrMsg;
    CString cstrIP;
    szClientItem clientItem;
    sockaddr_in clientAddr = {0};
    int iLength = sizeof(sockaddr_in);
    switch (message)
    {
    case WM_SOCKET:
        if (WSAGETASYNCERROR(lParam))
        {
            vector<szClientItem>::iterator iter = m_ClientSockets.begin();
            for (vector<szClientItem>::size_type i = 0; i < m_ClientSockets.size(); i++)
            {
                if (m_ClientSockets.at(i).m_socket == (SOCKET)wParam)
                {
                    ShowText(_T("系统消息: ") + m_ClientSockets.at(i).m_cstrIP + _T("客户端已经关闭。"));
                    closesocket(wParam);
                    m_ClientSockets.erase(iter + i);
                    break;
                }
            }
        }
        switch(WSAGETSELECTEVENT(lParam))
        {
        case FD_ACCEPT:
            clientItem.m_socket = accept(wParam, (sockaddr*)&clientAddr, &iLength);
            cstrIP.Format(_T("%d.%d.%d.%d"),clientAddr.sin_addr.S_un.S_un_b.s_b1,clientAddr.sin_addr.S_un.S_un_b.s_b2,clientAddr.sin_addr.S_un.S_un_b.s_b3,clientAddr.sin_addr.S_un.S_un_b.s_b4);
            clientItem.m_cstrIP = cstrIP;
            WSAAsyncSelect(clientItem.m_socket, m_hWnd, WM_SOCKET, FD_READ | FD_WRITE | FD_CLOSE);
            m_ClientSockets.push_back(clientItem);
            ShowText(_T("系统消息: 客户端 ")+ cstrIP + _T(" 已经连接成功"));
            break;
        case FD_READ:
            recv(wParam, (char *)buf, 1024, 0);
            cstrMsg = buf;
            for (vector<szClientItem>::size_type i = 0; i < m_ClientSockets.size(); i++)
            {
                if (m_ClientSockets.at(i).m_socket == (SOCKET)wParam)
                {
                    ShowText(_T("Client: ") + m_ClientSockets.at(i).m_cstrIP + _T(">") + cstrMsg);
                    break;
                }
            }
            break;
        case FD_WRITE:
            break;
        case FD_CLOSE:
break;
} default:break; } return CDialogEx::WindowProc(message, wParam, lParam); }

 具体核心代码我想应该就是这些了,这个模型相当简单,操作无非是这几步:

    1、定义一个自定义消息

    2、WSAAsyncSelect函数关联自定义消息并选择感兴趣的网络事件

    3、定义一个窗口过程,WSAAsyncSelect函数绑定到这个窗口过程

    4、在窗口过程捕获感兴趣的网络事件并做相应的处理。

    是不是很简单。。。。

    

posted @ 2012-06-10 22:31  venow  阅读(3499)  评论(0编辑  收藏  举报