套接字的select、WsaAsyncSelect、WsaEventSelect模型

来源:互联网 发布:sql sum函数分组求和 编辑:程序博客网 时间:2024/05/29 07:38

在掌握了socket相关的一些函数后,套接字编程还是比较简单的,日常工作中碰到很多的问题就是客户端/服务器模型中,如何让服务端在同一时间高效的处理多个客户端的连接,我们的处理办法可能会是在服务端不停的监听客户端的请求,有新的请求到达时,开辟一个新的线程去和该客户端进行后续处理,但是这样针对每一个客户端都需要去开辟一个新的线程,效率必定底下。

     其实,socket编程提供了很多的模型来处理这种情形,我们只要按照模型去实现我们的代码就可以解决这个问题。主要有select模型和重叠I/o模型,以及完成端口模型。这次,我们主要介绍下select模型,该模型又分为普通select模型,wsaasyncselect模型,wsaeventselect模型。我们将通过样例代码的方式逐一介绍。

一、select模型

使用该模型时,在服务端我们可以开辟两个线程,一个线程用来监听客户端的连接

请求,另一个用来处理客户端的请求。主要用到的函数为select函数。如:

全局变量:fd_set  g_fdClientSock;

线程1处理函数:

复制代码
    SOCKET listenSock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );    sockaddr_in sin;    sin.sin_family = AF_INET;    sin.sin_port = htons(7788);    sin.sin_addr.S_un.S_addr = INADDR_ANY;    int nRet = bind( listenSock, (sockaddr*)&sin, (int)(sizeof(sin)));    if ( nRet == SOCKET_ERROR )    {        DWORD errCode = GetLastError();        return;    }    listen( listenSock, 5);    int clientNum = 0;    sockaddr_in clientAddr;    int nameLen = sizeof( clientAddr );    while( clientNum < FD_SETSIZE )    {        SOCKET clientSock = accept( listenSock, (sockaddr*)&clientAddr, &nameLen );        FD_SET( clientSock, &g_fdClientSock);        clientNum++;     }
复制代码

线程2处理函数:

复制代码
    fd_set fdRead;    FD_ZERO( &fdRead );    int nRet = 0;    char* recvBuffer =(char*)malloc( sizeof(char) * 1024 );    if ( recvBuffer == NULL )    {        return;    }    memset( recvBuffer, 0, sizeof(char) * 1024 );    while ( true )    {        fdRead = g_fdClientSock;        nRet = select( 0, &fdRead, NULL, NULL, NULL );        if ( nRet != SOCKET_ERROR )        {            for ( int i = 0; i < g_fdClientSock.fd_count; i++ )            {                if ( FD_ISSET(g_fdClientSock.fd_array[i],&fdRead)  )                {                    memset( recvBuffer, 0, sizeof(char) * 1024 );                    nRet = recv( g_fdClientSock.fd_array[i], recvBuffer, 1024, 0);                    if ( nRet == SOCKET_ERROR )                    {                        closesocket( g_fdClientSock.fd_array[i] );                            FD_CLR( g_fdClientSock.fd_array[i], &g_fdClientSock );                    }                    else                    {                        //todo:后续处理                       }                }            }        }    }    if ( recvBuffer != NULL )    {        free( recvBuffer );    }
复制代码

该模型有个最大的缺点就是,它需要一个死循环不停的去遍历所有的客户端套接字集合,询问是否有数据到来,这样,如果连接的客户端很多,势必会影响处理客户端请求的效率,但它的优点就是解决了每一个客户端都去开辟新的线程与其通信的问题。如果有一个模型,可以不用去轮询客户端套接字集合,而是等待系统通知,当有客户端数据到来时,系统自动的通知我们的程序,这就解决了select模型带来的问题了。

二、WsaAsyncSelect模型

WsaAsyncSelect模型就是这样一个解决了普通select模型问题的socket编程模型。它是在有客户端数据到来时,系统发送消息给我们的程序,我们的程序只要定义好消息的处理方法就可以了,用到的函数只要是WSAAsyncSelect,如:

首先,我们定义一个Windows消息,告诉系统,当有客户端数据到来时,发送该消息给我们。

#define  UM_SOCK_ASYNCRECVMSG  WM_USER + 1

在我们的处理函数中可以如下监听客户端的连接:

复制代码
    SOCKET listenSock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );    sockaddr_in sin;    sin.sin_family = AF_INET;    sin.sin_port = htons(7788);    sin.sin_addr.S_un.S_addr = INADDR_ANY;    int nRet = bind( listenSock, (sockaddr*)&sin, (int)(sizeof(sin)));    if ( nRet == SOCKET_ERROR )    {        DWORD errCode = GetLastError();        return;    }    listen( listenSock, 5);    int clientNum = 0;    sockaddr_in clientAddr;    int nameLen = sizeof( clientAddr );    while( clientNum < FD_SETSIZE )    {        SOCKET clientSock = accept( listenSock, (sockaddr*)&clientAddr, &nameLen );        //hWnd为接收系统发送的消息的窗口句柄         WSAAsyncSelect( clientSock, hWnd, UM_SOCK_ASYNCRECVMSG, FD_READ | FD_CLOSE );        clientNum++;    }
复制代码

 

接下来,我们需要在我们的窗口添加对UM_SOCK_ASYNCRECVMSG消息的处理函数,在该函数中真正接收客户端发送过来的数据,在这个消息处理函数中的wparam参数表示的是客户端套接字,lparam参数表示的是发生的网络事件如:

复制代码
   SOCKET clientSock = (SOCKET)wParam;   if ( WSAGETSELECTERROR( lParam ) )   {      closesocket( clientSock );      return;   }   switch ( WSAGETSELECTEVENT( lParam ) )   {       case FD_READ:       {           char recvBuffer[1024] = {'\0'};           int nRet = recv( clientSock, recvBuffer, 1024, 0 );           if ( nRet > 0 )           {                szRecvMsg.AppendFormat(_T("Client %d Say:%s\r\n"), clientSock, recvBuffer );           }           else           {                //client disconnect                szRecvMsg.AppendFormat(_T("Client %d Disconnect!\r\n"), clientSock );           }        }                                      break;      case FD_CLOSE:      {           closesocket( clientSock );           szRecvMsg.AppendFormat(_T("Client %d Disconnect!\r\n"), clientSock );      }      break;    }
复制代码

    可以看到WsaAsyncSelect模型是非常简单的模型,它解决了普通select模型的问题,但是它最大的缺点就是它只能用在windows程序上,因为它需要一个接收系统消息的窗口句柄,那么有没有一个模型既可以解决select模型的问题,又不限定只能是windows程序才能用呢?下面我们来看看WsaEventSelect模型。

三、WsaEventSelect模型

WsaEventSelect模型是一个不用主动去轮询所有客户端套接字是否有数据到来的模型,它也是在客户端有数据到来时,系统发送通知给我们的程序,但是,它不是发送消息,而是通过事件的方式来通知我们的程序,这就解决了WsaAsyncSelect模型只能用在windows程序的问题。

该模型的实现,我们也可以开辟两个线程来进行处理,一个用来接收客户端的连接请求,一个用来与客户端进行通信,用到的主要函数有WSAEventSelect,WSAWaitForMultipleEvents,WSAEnumNetworkEvents实现方式如下:

首先定义三个全局数组

SOCKET      g_SockArray[MAX_NUM_SOCKET];//存放客户端套接字WSAEVENT    g_EventArray[MAX_NUM_SOCKET];//存放该客户端有数据到来时,触发的事件UINT32      g_totalEvent = 0;//记录客户端的连接数

线程1处理函数如下:

复制代码
    SOCKET listenSock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );    sockaddr_in sin;    sin.sin_family = AF_INET;    sin.sin_port = htons(7788);    sin.sin_addr.S_un.S_addr = INADDR_ANY;    int nRet = bind( listenSock, (sockaddr*)&sin, (int)(sizeof(sin)));    if ( nRet == SOCKET_ERROR )    {        DWORD errCode = GetLastError();        return;    }    listen( listenSock, 5);    sockaddr_in clientAddr;    int nameLen = sizeof( clientAddr );    while( g_totalEvent < MAX_NUM_SOCKET )    {        SOCKET clientSock = accept( listenSock, (sockaddr*)&clientAddr, &nameLen );        if ( clientSock == INVALID_SOCKET )        {            continue;        }        g_SockArray[g_totalEvent] = clientSock;        if( (g_EventArray[g_totalEvent] = WSACreateEvent()) == WSA_INVALID_EVENT )        {            continue;        }        WSAEventSelect( clientSock, g_EventArray[g_totalEvent],FD_READ | FD_CLOSE );        g_totalEvent++;    }
复制代码

    线程2的处理函数如下:

复制代码
    int nIndex = 0;    char* recvBuffer =(char*)malloc( sizeof(char) * 1024 );    if ( recvBuffer == NULL )    {    return;    }    memset( recvBuffer, 0, sizeof(char) * 1024 );    while( true )    {        nIndex = WSAWaitForMultipleEvents( g_totalEvent, g_EventArray, FALSE, WSA_INFINITE,FALSE );        if ( nIndex == WSA_WAIT_FAILED )        {            continue;        }        else        {             WSAResetEvent( g_EventArray[ nIndex - WSA_WAIT_EVENT_0]);            SOCKET clientSock = g_SockArray[ nIndex - WSA_WAIT_EVENT_0 ];            WSANETWORKEVENTS wsaNetWorkEvent;            int nRet = WSAEnumNetworkEvents( clientSock, g_EventArray[nIndex - WSA_WAIT_EVENT_0], &wsaNetWorkEvent );            if ( SOCKET_ERROR == nRet )            {                continue;            }            else if ( wsaNetWorkEvent.lNetworkEvents & FD_READ )            {                if ( wsaNetWorkEvent.iErrorCode[FD_READ_BIT] != 0 )                {                    //occur error                    closesocket( clientSock );                }                else                 {                    memset( recvBuffer, 0, sizeof(char) * 1024 );                    nRet = recv( clientSock, recvBuffer, 1024, 0);                    if ( nRet == SOCKET_ERROR )                    {                        closesocket( clientSock );                    }                    else                    {                        //todo:对接收到的客户端数据进行处理                        }                 }             }             else if( wsaNetWorkEvent.lNetworkEvents & FD_CLOSE )             {                if ( wsaNetWorkEvent.iErrorCode[FD_CLOSE_BIT] != 0 )                {                    //occur error                    closesocket( clientSock );                }                else                {                    closesocket( clientSock );                }               }        }    }    if ( recvBuffer != NULL )    {        free( recvBuffer );    }
复制代码

 

     该模型通过一个死循环里面调用WSAWaitForMultipleEvents函数来等待客户端套接字对应的Event的到来,一旦事件通知到达,就通过该套接字去接收数据。虽然WsaEventSelect模型的实现较前两种方法复杂,但它在效率和兼容性方面是最好的。

    以上三种模型虽然在效率方面有了不少的提升,但它们都存在一个问题,就是都预设了只能接收64个客户端连接,虽然我们在实现时可以不受这个限制,但是那样,它们所带来的效率提升又将打折扣,那又有没有什么模型可以解决这个问题呢?我们的下一篇重叠I/0模型将解决这个问题


  个人认为本文最好之处在与将三个模型的区别通俗的讲述清晰,附上原文地址:套接字的select、WsaAsyncSelect、WsaEventSelect模型
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 事业单位50岁不愿退休的怎么办 回美国i20丢了怎么办 i20忘签字美国入境怎么办 社保基数报错了怎么办 公司合同没给我怎么办 给客户报错价格怎么办 给客户报价低了怎么办 报价失误报低了怎么办 期望薪资说低了怎么办 期望薪资说高了怎么办 面试工资说低了怎么办 期望薪资谈低了怎么办 请年假公司不批怎么办 期望工资填低了怎么办 面试工资要高了怎么办 找工作期望薪资写低了怎么办 期望工资写少了怎么办 不给工人发工资怎么办 天亮了怎么办我好想你 亲爱的我想你我怎么办 人在澳大利亚悉尼找不到了怎么办 红米手机忘记手势密码怎么办 捡到苹果手机怎么办才能自己用 日语会读不会写怎么办 手术后nbp过低怎么办 我的手破了怎么办英文 平板手机屏坏了怎么办 他很优秀我该怎么办 洗澡的花洒漏水怎么办 高三了文科成绩很差怎么办 骑缝章最后一页没盖全怎么办 机票取早了没有登机口怎么办 机票早订比晚订贵怎么办? 孩子考差了父母怎么办 保险公司不给业务员办退司怎么办 我不习惯没有你我怎么办 锁坏了打不开了怎么办 要上班老人生病无人照顾怎么办 苹果手机一直说英文怎么办 公司很抠门怎么办英文怎么说 过了截港时间怎么办