I/O模型(异步选择)

异步选择

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

WSAAsyncSelect模型是Select模型的异步版本,在调用select()函数时,会发生阻塞现象。可以通过select()函数timeout参数,设置函数调用的阻塞时间。在设定的时间内,线程保持等待,直到其中一个或多个套接字满足可读可写的条件时,该函数返回。

该模型的核心即是WSAAsyncSelect函数,该函数是非阻塞的。

要想使用 WSAAsyncSelect 模型,在应用程序中,必须有一个窗口,且该窗口有一个窗口例程函数(WinProc)

与select模型比较

相同点:他们都可以对Windows套接字应用程序所使用的多个套接字进行有效的管理。

不同点:     1.WSAAsyncSelect模型是异步的。在应用程序中调用WSAAsyncSelect()函数,通知系统感兴趣的网络事件,该函数立即返回,应用程序继续执行;   

 2.发生网络事件时,应用程序得到的通知方式不同。Select()函数返回时,说明某个或者某些套接字满足可读可写的条件,应用程序需要使用FD_ISSET宏,判断套接字是否存在可读可写集合中。而对于WSAAsyncSelect模型来说,当网络事件发生时,系统向应用程序发送消息。    

3.WSAAsyncSelect模型应用在基于消息的Windos环境下,使用该模型时必须创建窗口。而Select模型广泛应用在Unix系统和Windows系统,使用该模型不需要创建窗口。    

4.应用程序调用WSAAsyncSelect()函数后,自动将套接字设置为非阻塞模式。而应用程序中调用select()函数后,并不能改变套接字的工作方式

WSAAsyncSelect 的函数原型

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

● s 参数指定的是我们感兴趣的那个套接字。   

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

 ● wMsg 参数指定在发生网络事件时,打算接收的消息。该消息会投递到由hWnd窗口句柄指定的那个窗口。(通常,应用程序需要将这个消息设为比Windows的WM_USER大的一个值,避免网络窗口消息与系统预定义的标准窗口消息发生混淆与冲突)     ● lEvent 参数指定一个位掩码,对应于一系列网络事件的组合,大多数应用程序通常感兴趣的网络事件类型包括: FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT、FD_CLOSE。当然,到底使用FD_ACCEPT,还是使用FD_CONNECT类型,要取决于应用程序的身份是客户端,还是服务器。

如应用程序同时对多个网络事件有兴趣,只需对各种类型执行一次简单的按位OR(或)运算,然后将它们分配给lEvent就可以了,例如:     WSAAsyncSeltct(s, hwnd, WM_SOCKET, FD_CONNECT | FD_READ | FD_WRITE | FD_CLOSE);

解释说明:我们的应用程序以后便可在套接字s上,接收到有关连接、发送、接收以及套接字关闭这一系列网络事件的通知。 FD_READ        应用程序想要接收有关是否可读的通知,以便读入数据   

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

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

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

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

注意 ①:     多个事件务必在套接字上一次注册!     另外还要注意的是,一旦在某个套接字上允许了事件通知,那么以后除非明确调用closesocket命令,或者由应用程序针对那个套接字调用了WSAAsyncSelect,从而更改了注册的网络事件类型,否则的话,事件通知会永远有效!若将lEvent参数设为0,效果相当于停止在套接字上进行的所有网络事件通知。

注意 ②:     若应用程序针对一个套接字调用了WSAAsyncSelect,那么套接字的模式会从“锁定”变成“非锁定”。这样一来,如果调用了像WSARecv这样的Winsock函数,但当时却并没有数据可用,那么必然会造成调用的失败,并返回WSAEWOULDBLOCK错误。为防止这一点,应用程序应依赖于由WSAAsyncSelect的uMsg参数指定的用户自定义窗口消息,来判断网络事件类型何时在套接字上发生;而不应盲目地进行调用。

注意 ③:应用程序如何对 FD_WRITE 事件通知进行处理。     只有在三种条件下,才会发出 FD_WRITE 通知:   

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

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

 ■ 若 send、WSASend、sendto 或 WSASendTo 操作失败,返回WSAEWOULDBLOCK 错误,而且缓冲区的空间变得可用。            因此,作为一个应用程序,自收到首条 FD_WRITE 消息开始,便应认为自己必然能在一个套接字上发出数据,直至一个send、WSASend、sendto 或 WSASendTo 返回套接字错误 WSAEWOULDBLOCK。经过了这样的失败以后,要再用另一条 FD_WRITE 通知应用程序再次发送数据。

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

窗口例程通常定义如下:     LRESULT CALLBACK WindowProc(         HWND hwnd,         UINT uMsg,         WPARAM wParam,         LPARAM lParam     );   

 ● hWnd 参数指定一个窗口的句柄,对窗口例程的调用正是由那个窗口发出的。   

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

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

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

步骤:

网络事件消息抵达一个窗口例程后,应用程序首先应检查lParam的高字位,以判断是否在网络错误。

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

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

WSAAsyncSelect模型的优势和不足

优势:   

 1.该模型的使用方便了在基于消息的Windows环境下开发套接字的应用程序。开发人员可以像处理其他消息一样对网络事件消息进行处理。   

 2.该模型确保接收所有数据提供了很好的机制。通过注册FD_CLOSE网络事件,从容关闭服务器与客户端的连接保证了数据全部接收。

不足:     

1.该模型局限在,他基于Windows的消息机制,必须在应用程序中创建窗口。当然,在开发中可以根据具体情况是否显示该窗口。MFC的CSocketWnd类就是用来创建一个不显示的窗口,并在该类中声明接收网络事件消息处理函数。   

 2.由于调用WSAAsyncSelect()函数后,自动将套接字设置为非阻塞状态。当应用程序为接收到网络事件调用相应函数时,未必能够成功返回。这无疑增加了开发人员使用该模型的难度。对于这一点可以从MFC CSocket类的Accept()、Receive()和Send()函数的实现得到验证。

代码示例

// MyWnd.cpp : 实现文件
//

#include "stdafx.h"
#include "0809Feiq.h"
#include "MyWnd.h"


// CMyWnd

IMPLEMENT_DYNCREATE(CMyWnd, CFrameWnd)
CMyWnd* CMyWnd::m_pWnd =  new CMyWnd;
CMyWnd::CMyWnd()
{

}

CMyWnd::~CMyWnd()
{
}


BEGIN_MESSAGE_MAP(CMyWnd, CFrameWnd)
    ON_MESSAGE(NM_READMSG,&CMyWnd::ReadMsg )
END_MESSAGE_MAP()


// CMyWnd 消息处理程序
//recvfrom
LRESULT CMyWnd::ReadMsg(WPARAM w,LPARAM l)
{
     char szbuf[_DEF_SIZE] = {0};
     sockaddr_in addrClient;
     int nSize = sizeof(addrClient);
     int nRelRecvNum;
    //WPARAM -- 发生网络事件的socket
    SOCKET sock = (SOCKET)w;

    //LPARAM -- 低位发生的网络事件
    switch (LOWORD(l))
    {
    case FD_READ:
        {
             nRelRecvNum = recvfrom(sock,szbuf,_DEF_SIZE,0,(sockaddr*)&addrClient,&nSize);
        
            if(nRelRecvNum >0)
            {
                //交给中介者去处理
                theApp.GetMediator()->DealData(szbuf,addrClient.sin_addr.S_un.S_addr);
            }
        }
        break;
    case FD_WRITE:
        break;
    default:
        break;
    }
    
    return 0;
}

#include "stdafx.h"
#include "UDPNet.h"

UDPNet::UDPNet(IMediator *pMediator)
{
    m_sockListen = NULL;
    m_hThread = NULL;
    m_bFlagQuit = true;
    m_pMediator = pMediator;
    
}

UDPNet::~UDPNet()
{}


bool UDPNet::InitNetWork()
{
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
    wVersionRequested = MAKEWORD(2, 2);

    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0) {
        /* Tell the user that we could not find a usable */
        /* Winsock DLL.                                  */
       
        return false;
    }

    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
      
       
       UnInitNetWork();
        return false;
    }
   

    //2.雇个人  -- 创建套接字(与外界通信接口)-
    m_sockListen = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
    m_sockbak = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
    if(m_sockListen == INVALID_SOCKET  || INVALID_SOCKET == m_sockbak)
    {
        UnInitNetWork();
        return false;
    }

    //改变socket 广播属性
    BOOL bval = TRUE;
    setsockopt(m_sockListen,SOL_SOCKET,SO_BROADCAST,(const char*)&bval,sizeof(bval));
    //改变socket 属性
    u_long argp = true;
    ioctlsocket(m_sockListen,FIONBIO,&argp);
    ioctlsocket(m_sockbak,FIONBIO,&argp);
    //3.选个地方 -- 绑定--(IP,端口号)
    sockaddr_in  addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(_DEF_PORT);
    addr.sin_addr.S_un.S_addr =INADDR_ANY /*inet_addr("127.0.0.1")*/;
    if(SOCKET_ERROR == bind(m_sockListen,(const sockaddr *)&addr,sizeof(addr)))
    {
        UnInitNetWork();
        return false;
    }
    sockaddr_in  addrbak;
    addrbak.sin_family = AF_INET;
    addrbak.sin_port = htons(1245);
    addrbak.sin_addr.S_un.S_addr =INADDR_ANY /*inet_addr("127.0.0.1")*/;
    if(SOCKET_ERROR == bind(m_sockbak,(const sockaddr *)&addrbak,sizeof(addrbak)))
    {
        UnInitNetWork();
        return false;
    }
    //创建窗口 ---处理消息
    if(!CMyWnd::GetMyWnd()->Create(NULL,"MyWnd"))
    {
        UnInitNetWork();
        return false;
    }
    //向Windows注册
    WSAAsyncSelect(m_sockListen,CMyWnd::GetMyWnd()->m_hWnd,NM_READMSG,FD_READ|FD_WRITE);
    WSAAsyncSelect(m_sockbak,CMyWnd::GetMyWnd()->m_hWnd,NM_READMSG,FD_READ|FD_WRITE);
    //recvfrom --创建线程
    //m_hThread = (HANDLE)_beginthreadex(NULL,0,&ThreadProc,this,0,0);
    return true;
}

 unsigned  __stdcall UDPNet::ThreadProc( void * lpvoid)
 {
    // UDPNet *pthis = (UDPNet*)lpvoid;
    // char szbuf[_DEF_SIZE] = {0};
    // sockaddr_in addrClient;
    // int nSize = sizeof(addrClient);
    // int nRelRecvNum;
    // fd_set fdReadsets;

    //timeval tv;
    //tv.tv_sec = 0;
    //tv.tv_usec = 100;
    //
    //
    // while(pthis->m_bFlagQuit)
    // {
    //     fdReadsets = pthis->fdsets;

    //     select(NULL,&fdReadsets,NULL,NULL,&tv);
    //     int i = 0;
    //     while(i < pthis->fdsets.fd_count)
    //     {
    //         if(FD_ISSET(pthis->fdsets.fd_array[i],&fdReadsets))
    //         {
    //             nRelRecvNum = recvfrom(pthis->fdsets.fd_array[i],szbuf,_DEF_SIZE,0,(sockaddr*)&addrClient,&nSize);
    //    
    //             if(nRelRecvNum >0)
    //             {
    //                 //交给中介者去处理
    //                 pthis->m_pMediator->DealData(szbuf,addrClient.sin_addr.S_un.S_addr);
    //             }
    //         }
    //         i++;
    //     }
    //    
    //        
    //    
    //    
    //    
    // }
     return 0;
 }

//bool UDPNet::SelectSocket()
//{
//    
//    
//
//    if(!FD_ISSET(sock,&fdsets))
//        return false;
//
//    return true;
//
//}


void UDPNet::UnInitNetWork()
{
    m_bFlagQuit = false;
    if(m_hThread)
    {
        if(WAIT_TIMEOUT == WaitForSingleObject(m_hThread,100))
            TerminateThread(m_hThread,-1);

        CloseHandle(m_hThread);
        m_hThread = NULL;
    }

     WSACleanup();
     if(m_sockListen)
     {
         closesocket(m_sockListen);
         m_sockListen = NULL;
     }
     if(m_sockbak)
     {
         closesocket(m_sockbak);
         m_sockbak = NULL;
     }

     CMyWnd::DeleteMyWnd();
}

bool UDPNet::SendData(long lSendIp,char *szbuf,int nlen)
{
    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(_DEF_PORT);
    addr.sin_addr.S_un.S_addr = lSendIp;
    if(!szbuf || nlen <=0)
        return false;
    if(sendto(m_sockListen,szbuf,nlen,0,(const sockaddr*)&addr,sizeof(addr))<=0)
        return false;
    return true;
}

猜你喜欢

转载自blog.csdn.net/qq_37163944/article/details/81742123