socket编程的select模型

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情

在掌握了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模型将解决这个问题

网络程序设计——异步选择模型(基于消息的选择、基于事件的选择) 共同特点:管 I/O事件是否发生,应用程序都会按既定流程主动试着进行I/O操作,而且直至操作成功才会罢休,因此这三种套接字模型都属于。尽管非阻塞模型Select模型一次能够尝试对多个套接字进行I/O操作,要比阻塞模型效率高很多,但应用程序一旦开始I/O操作,则I/O操作完成之前都是无法进行其它操作。WSAAsyncSelect模型和WSAEventSelect模型都属于异步I/O模型,二者的差别在于。 阅读详情

相关推荐

WinSock I/O 模型 -- WSAAsyncSelect 模型

简介 WSAAsyncSelect 模型也是 WinSock 中常见的异步 I/O 模型。 使用这个模型,网络应用程序通过接收以 Windows 消息为基础的网络事件通知来处理网络请求。 这篇文章我们就来看看如何使用 WSAAsyncSelect api 来实现一个简单的 TCP 服务器. API 基础 要使用 WSAAsyncSelect 模型,我们必须创建一个窗口, 再为该窗口对象提供一个窗口历程(WinProc). 通过适当的配置之后,当有网络请求到来的时候,windows 会将网络消息投递到我们所创

Useless Programmer 的专栏 889

网络编程——select模型(总结)

为什么要使用select模型? 答:解决基本C/S模型中,accept()、recv()、send()阻塞的问题 select模型与C/S模型同点 C/S模型中accept()会阻塞一直傻等socket来链接 select模型只解决accept()傻等的问题,解决recv(),send()执行阻塞问题 其实select模型解决了实现多个客户端链接,与多个客户端分别通信 两个模型都存在recv(),send()执行阻塞问题 由于服务器端,客户端需要(客户端只有一个socket,可以通过加线程

aa804738534的博客 4666

socket网络通信模型select与epoll

socket网络通信模型select和epoll的介绍及对比分析,并结合具体问题进行阐述。

dvlinker的技术专栏 1万+

C++ socket编程select模型

在做Socket编程时,当要处理一个server对应多个client,这种可以每个客户端用一个线程来处理,但是客户端太多,程序的性能会降低。Windows提供了select模型,很好的处理了一对多的模型select的申明如下: Int WSAAPI select( _In_ int nfds, //0,无意义 _Inout_opt_ fd_set FAR * readfds, //检查...

积累点滴,保持自我 2415

socket编程select模型 转载

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

旋风的技术 479
上一篇: 运行时出现Stack overflow错误
下一篇: setsockopt、getsockopt详细介绍
一秀
博客等级 码龄14年 1粉丝 11原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值