欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Cpp-Socket网络编程(八)服务器端升级为select模型处理多客户端

程序员文章站 2024-03-23 10:35:40
...

之前学习的服务器端与客户端网络程序都是1对1的阻塞模式的网络程序,每次服务器端想要接收新的客户端连接时,都必须要新的客户端向服务器端发送连接;某一端想要调用recv函数接收数据时也必须要另一段真正的发送数据过来。那么当我们调用这些阻塞函数时,就必须要等待函数执行,不能进行其他的业务处理,服务器端就无法同时处理多个客户端的业务。为了令服务器端能够处理多客户端业务,这里使用select网络模型。

select可以用于服务器端和客户端,select的基本原理是会在一段指定的时间内监听用户感兴趣的文件描述符的可读可写和异常事件,若无新数据则会立即返回,如果检查到新的变化则会进行处理。select解决了C/S模型中accept()一直阻塞等待socket连接的问题(但是不解决recv()和send()的执行阻塞问题)以及解决了实现多个客户端连接、与多个客户端通信的问题。

Socket的select模型:

int select{
    int nfds; //为了兼容Berkeley sockets,忽略填0
    fed_set FAR* readfds; //检查是否有可读的socket
    fed_set FAR* writefds; //检查是否有可写的socket
    fed_set FAR* exceptfds; //检查socket上的异常错误
    const struct timeval FAR* timeout; //最大等待时间
};

在服务器端加入select模型:

//循环接收客户端数据
    while (true) {
        //fd_set类型成员包括socket数目和socket数组
        fd_set fdRead;
        fd_set fdWrite;
        fd_set fdExp;
        
        //置空
        FD_ZERO(&fdRead);
        FD_ZERO(&fdWrite);
        FD_ZERO(&fdExp);
        
        FD_SET(_sock, &fdRead);
        FD_SET(_sock, &fdWrite);
        FD_SET(_sock, &fdExp);
        
        //循环的加入数据
        for (int n = (int)g_clients.size() - 1; n >= 0 ; --n) {
            //将新加入的客户端放到可读集合中用于查询
            FD_SET(g_clients[n], &fdRead);
        }
        
        //select模型(nfds是指FED_SET集合中所有描述符(socket)的范围,而不是数量,即是所有文件描述符的最大值+1)
        int ret = select(_sock + 1, &fdRead, &fdWrite, &fdExp, NULL);
        if (ret < 0) {
            printf("client has quit!\n");
            break;
        }
        
        if (FD_ISSET(_sock, &fdRead)) {
            FD_CLR(_sock, &fdRead);
            //4. 等待客户端连接accept
            sockaddr_in clientAddr = {};
            socklen_t nAddrLen = sizeof(sockaddr_in);
            int _cSock = accept(_sock, (sockaddr*)&clientAddr, &nAddrLen); //套接字,收到客户端socket地址,返回socket地址的大小
            if (_cSock == -1) {
                perror("client socket error!\n");
            } else {
                g_clients.push_back(_cSock);
                printf("client socket success! socket = %d, IP = %s \n", _cSock, inet_ntoa(clientAddr.sin_addr)); //打印客户端socket和IP地址
            }
        }
        //循环调用进程处理函数
        for (int n = 0; n < FD_SETSIZE; ++n){
            if (processor(fdRead.fds_bits[n]) == -1){ //如果客户端请求数据长度<=0则从客户端数组中h删除该socket
                auto it = find(g_clients.begin(), g_clients.end(), fdRead.fds_bits[n]);
                if (it != g_clients.end())
                    g_clients.erase(it);
            }
        }
        
    }
    
    //循环关闭数组中的socket
    for (int n = (int)g_clients.size() - 1; n >= 0 ; --n) {
        close(g_clients[n]);
    }