socket编程的select模型

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

时间: 2024-09-29 12:44:08

socket编程的select模型的相关文章

Socket编程之Select模型

echoserver_select.c 1 #include <apue.h> 2 3 #define BACKLOG 10 4 #define PORT 8080 5 #define MAXCLIENT 20 6 #define LEN_BUF 255 7 8 fd_set grset; 9 int maxfd; 10 struct client 11 { 12 char ip[16]; 13 unsigned short port; 14 int connfd; 15 }; 16 17 s

socket编程以及select、epoll、poll示例详解

socket编程socket这个词可以表示很多概念,在TCP/IP协议中“IP地址 + TCP或UDP端口号”唯一标识网络通讯中的一个进程,“IP + 端口号”就称为socket.在TCP协议中,建立连接的两个进程各自有一个socket来标识,那么两个socket组成的socket pair就唯一标识一个连接. 预备知识 网络字节序:内存中多字节数据相对于内存地址有大端小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分.网络数据流同样有大端小端之分,所以发送主机通常将发送缓冲

[00015]-[2015-09-04]-[01]-[WinSocket编程1 Select模型开发]

套接字Select模型是比较常用的一种I/O模型,利用该模型使得Windows Sockets应用程序可以在同一时间内管理和控制多个套接字,该模型的核心就是select()函数----调用select()函数检查当前多个套接字的状态----是否可读,可写,有异常.....根据该函数的返回值,判断套接字的可读可写性,然后调用相应的Windows Sockets API函数完成数据的发送和接收等操作.... [阻塞模式] 套接字执行I/O操作时,如果执行操作的条件没有得到满足,线程会被阻塞在该调用的

socket编程:I/O模型

在TCP服务器编程那篇博客中,我们提到了更加优化连接方式,其实就是关于I/O的传输模型: 我们常见的几种I/O模型: 阻塞式I/O模型: 当进程进行数据传输连接的时候,只能够阻塞的等待数据的到来,而无法去干其他的事情,将服务进程的所有精力全部去应付当前连接; 图解: 2.非阻塞式I/O模型: 讲数据的等待响应变为轮询等待,当轮询失败是返回EMOULODBLOCK,然后一段时间后再次进行询问,成功就进入数据的复制. 图解; 3.信号响应I/O: 进程轮询进行,然后设置SIGIO的处理程序,当有数据

【socket编程】select manual page翻译

原文: select manual page 依赖的头文件 /* According to POSIX.1-2001, POSIX.1-2008 */ #include <sys/select.h> /* According to earlier standards */ #include <sys/time.h> #include <sys/types.h> #include <unistd.h> 方法定义 int select(int nfds, fd_

windows socket编程select模型使用

int select( int nfds,            //忽略 fd_ser* readfds,    //指向一个套接字集合,用来检测其可读性 fd_set* writefds,   // 指向一个套接字结合,用来检测其可写性 fd_ser* exceptfds, //指向一个套接字集合,用来检测错误 const struct timeval * timeout   //指定此函数等待的最长时间,如果为NULL,则最长时间为无限大. ); 参数说明: (1)   nfds  win

socket编程:多路复用之select模型

系统提供select函数来实现多路复用输入/输出模型. select函数让我们的程序监视多个文件描述符的状态变化.程序会停在select这里等待,直到被监视的文件描述符中有一个或多个发生了状态变化 函数原型如下: 返回值:   成功返回就绪描述符的个数,超过timeout时间且没有任何事件发生返回0,失败返回-1 参数解释: nfds:    被监视的文件描述符中值最大描述符值加1(描述符是从0开始的,描述符0.1.2...nfds-1均将被测试) 下面三个参数readset.writeset和

0729------Linux网络编程----------使用 select 、poll 和 epoll 模型 编写客户端程序

1.select 模型 1.1 select 函数原型如下,其中 nfds 表示的描述符的最大值加1(因为这里是左闭右开区间),中间三个参数分别表示要监听的不同类型描述符的集合,timeout用来表示轮询的时间间隔,这里用NULL表示无限等待. 1.2 使用 select函数编写客户端程序的一般步骤: a)初始化参数,包括初始化监听集合read_set并添加fd,以及初始化监听的最大描述符 maxfd 和select的返回值 nready: b)将read_set 赋值给 ready_set,因

Socket I/O模型之select模型

socket网络编程中有多种常见的I/O模型: 1.blocking阻塞 2.nonblocking非阻塞 3.I/O multiplexing复用 4.signal driven 5.asynchronous I/O异步 这里我们主要介绍I/O multiplexing模型中的代表select模型:select模型将多个套接字放在一个集合里,然后统一检查这些套接字的状态,每次调用套接字后会更新这些套接字的状态,然后做判断,如果套接字可读,就执行read操作.这样就巧妙地避免了阻塞,达到同时处理