• windows下的IO模型之异步选择(WSAAsyncSelect)模型


    异步选择(WSAAsyncSelect)模型是一个有用的异步I/O 模型。其核心函数是WSAAsyncSelect,该函数是非阻塞的

    (关于异步io的理解详情可以看:http://www.cnblogs.com/curo0119/p/8461520.html

    它可以用来在一个socket上接收以windows消息为基础的网络事件。它提供了读写数据的异步通知功能,但不提供异步数据传送。WSAAsyncSelect模型的优势在于只需要一个主线程即可。缺点是必须要绑定窗口句柄。即要先调用createwindow创建一个窗口。这也就是为什么这个模型只适用于windows操作系统而不能跨平台的原因。

    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参数指定的用户自定义窗口消息,来判断网络事件类型何时在套接字上发生;而不应盲目地进行调用。

     

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

    窗口例程通常定义如下:

    LRESULT CALLBACK WindowProc(

        HWND hwnd,

        UINT uMsg,

        WPARAM wParam,

        LPARAM lParam

    );

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

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

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

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

     

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

    这里有一个特殊的宏: WSAGETSELECTERROR,可用它返回高字位包含的错误信息。

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

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

    如:

      if(WSAGETSELECTERROR(lParam))

         return;

      else

    {

        switch(WSAGETSELECTEVENT(lParam))

       {

          case FD_READ:

                  ...

                  break;

          case FD_WRITE:

                  ...

                  break;

          ...

        }

    }

    █ 注意 ③:应用程序如何对 FD_WRITE 事件通知进行处理。

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

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

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

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

     

    因此,作为一个应用程序,自收到首条 FD_WRITE 消息开始,便应认为自己必然能在一个套接字上发出数据,

    直至一个send、WSASend、sendto 或WSASendTo 返回套接字错误 WSAEWOULDBLOCK。

    经过了这样的失败以后,要再用另一条 FD_WRITE 通知应用程序再次发送数据。

    代码:

    [cpp] view plain copy
    <span style="font-size:14px;">服务器端:  
    UINT CServerDlg::ThreadFun(LPVOID pParam )  
    {  
        CServerDlg* pDlg=(CServerDlg*)pParam;  
        pDlg->InitSock();  
      
        SOCKADDR_IN serAdd;  
        serAdd.sin_family=AF_INET; //AF_INET表示地址族,在windows下与PF_INET(协议族)是一样的  
        serAdd.sin_port=htons(5000);  
        serAdd.sin_addr.s_addr=ADDR_ANY;  
        pDlg->m_listenSock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); //创建套接字  
        if (INVALID_SOCKET==pDlg->m_listenSock)  
        {  
            AfxMessageBox(_T("初始化套接字失败"));  
            return 0;  
        }  
        if(SOCKET_ERROR==bind(pDlg->m_listenSock,(SOCKADDR*)&serAdd,sizeof(SOCKADDR)))  
        {  
            AfxMessageBox(_T("绑定地址失败"));  
            return 0;  
        }  
        if (SP_ERROR==listen(pDlg->m_listenSock,SOMAXCONN))  
        {  
            AfxMessageBox(_T("启动监听失败"));  
            return 0;  
      
        }  
    //向windows注册 WSAAsyncSelect(pDlg->m_listenSock,pDlg->GetSafeHwnd(),WM_SOCKET,FD_ACCEPT | FD_CLOSE); //当有感兴趣的事件发生时用Windows消息通知 //在窗口过程中实现该消息的判断 } LRESULT CServerDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { // TODO: 在此添加专用代码和/或调用基类 //在网络事件中wParam代表了句柄 lParam的高位表示了错误信息 低位表示了相关的网络事件 switch(message) { case WM_SYSCOMMAND: { if(SC_CLOSE==wParam) { if(m_listenSock) closesocket(m_listenSock); if (m_cliSock) closesocket(m_cliSock); WSACleanup(); } } break; case WM_SOCKET: if (WSAGETASYNCERROR(lParam)) //WSAGENSELECTERROR宏获得是否有错误 这里用HIWORD(lParam)也是可以的 { MessageBox(_T("网络出错")); closesocket(wParam); return 0; } switch(WSAGETSELECTEVENT(lParam)) //WSAGETSELECTEVENT获取网络事件 这里也可以用LODORD { case FD_ACCEPT: //case里定义变量时要加入{} { SOCKADDR_IN cliAdd; int len=sizeof(SOCKADDR); m_cliSock=accept(wParam,(SOCKADDR*)&cliAdd,&len); WSAAsyncSelect(m_cliSock,this->GetSafeHwnd(),WM_SOCKET,FD_READ | FD_WRITE |FD_CLOSE); //该套接字也要用WSAAsyncSelect处理 if (m_cliSock==INVALID_SOCKET) { MessageBox(_T("接收连接出错")); return 0; } } break; case FD_READ: { TCHAR bufData[1024]={0}; int flag; flag=recv(m_cliSock,(char*)bufData,1024,0); if(flag==0) { MessageBox(_T("连接已经断开")); return 0; } ShowMsg(bufData); } break; case FD_WRITE: wParam=wParam; //不做处理 break; case FD_CLOSE: closesocket(wParam); WSACleanup(); break; default: break; } } return CDialog::WindowProc(message, wParam, lParam); }

     

      

     

     本文转载于:http://blog.csdn.net/skyandcode/article/details/8646630

  • 相关阅读:
    php 判断访问是否是手机或者pc
    SQLSTATE[HY000] [2002] No such file or directory
    No input file specified.
    Call to undefined function openssl_decrypt()
    Parse error: syntax error, unexpected 'class' (T_CLASS)
    tp5关联模型进行条件查询
    windows下php7.1安装redis扩展以及redis测试使用全过程
    SourceTree跳过初始设置
    对象数组(JSON) 根据某个共同字段 分组
    SDUT 3377 数据结构实验之查找五:平方之哈希表
  • 原文地址:https://www.cnblogs.com/curo0119/p/8463159.html
Copyright © 2020-2023  润新知