多路转接-select

1.select的核心定位

2.认识select函数

三个参数

其他参数

select是来"等"的,等 读事件就绪、写事件就绪,所以剩余的参数就是这,它们是分开的

3.demo,select server服务器

最开始只有listensocketfd,接下来有客户端新连接到来,fd 就绪,调用HanderEvent()去处理新连接

接下来,我们和对方建立好连接了,但我们不敢直接 read读取,客户端可能此时没有 发来请求!

fd 不一定 读事件就绪!而且我们今天是单进程,一旦 阻塞了,直接挂起该进程!

每次select返回时,内核都会修改 我们传入的,要select关心的 位图,下次select调用时,我们当然还要它关心这些事件,所以我们得记录--select返回之后,哪些fd还需要被添加到 位图里,让select关心!

所以select ,需要借助辅助数组,在下一次select 之前,把所有需要关心的fd都加回去!

但是这里有一个问题,我们把普通fd也添加进了select,那下次有fd就绪,不一定是listenfd就绪,那就不能直接调用HanderEvent(),然后去accept!

一旦有fd就绪,select就会立即返回,立即修改位图,所以,判断该fd是否就绪,只要判断,该fd 是否在位图里就行!IS_ISSET

我们代码甚至只是单进程,却能同时处理多个IO请求!

4.select多路转接的特点

  • 将fd加入到select的同时,还要在使用一个fd_array来保存 我们 想让select监控的fd,因为每次调用select,select一旦返回有fd就绪,就会修改我们设置的位图。
  • 检测的fd 有上限,我这边1024

5.select的缺点

  • select底层,检测多个文件描述符是否就绪,采用的是遍历for,当前进程文件描述符表,这就是为什么 select第一个参数,是当前需要监测的 文件描述符的最大值!开销大!【)
  • select的位图时 输入输出型参数,每次调用select,都需要手动重置select,添加我们要关心的fd!
  • select能同时关心的fd 数量比较少,虽然当前进程能打开的文件描述符也有上限=w=

为了解决这些缺点,我们要学习 poll、epoll多路转接


代码:

cpp 复制代码
#include <iostream>
#include <unistd.h>
#include "Socket.hpp"
#include "Log.hpp"

using namespace SocketModule;
using namespace LogModule;

class SelectServer
{
    const static int size = sizeof(fd_set) * 8;
    const static int defaultfd = -1;

public:
    SelectServer(int port) : _listensock(std::make_unique<TcpSocket>()), _isrunning(false)
    {
        _listensock->BulidListenSocket(port); // 得到监听套接字
        for (int i = 0; i < size; i++)        // 初始化辅助数组
        {
            _fd_array[i] = defaultfd;
        }
        _fd_array[0] = _listensock->Fd(); // listenfd直接添加进辅助数组
    }

    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 每次select返回后,需要重新添加 我们要关心的历史的fd!
            // 辅助数组,保存所有需要关心的fd
            fd_set rfds;
            FD_ZERO(&rfds);
            FD_SET(_listensock->Fd(), &rfds);
            int maxfd = defaultfd;
            for (int i = 0; i < size; i++)
            {
                if (_fd_array[i] == defaultfd)
                    continue;
                FD_SET(_fd_array[i], &rfds); // 在这添加
                if (maxfd < _fd_array[i])
                    maxfd = _fd_array[i];
            }
            // struct timeval timeout = {2, 0};

            // 最大fd,一定变化,每次select之前,都要对rfds重置
            PrintFd();
            int n = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr);
            switch (n)
            {
            case -1: // select 出错了
                LOG(LogLevel::ERROR) << "select error";
                break;
            case 0: // 超时了
                LOG(LogLevel::INFO) << "select time out";
                break;
            default: // 返回值>0,有事件就绪了
                // 此时有事件就绪,不一定是有新连接到来
                // 还有可能是普通fd就绪,客户端给服务器发数据了!
                LOG(LogLevel::DEBUG) << "有事件就绪了";
                Dispatcher(rfds); // 事件派发器
                break;
            }
        }
        _isrunning = false;
    }
    void PrintFd()
    {
        std::cout << "fd_array[]: ";
        for (int i = 0; i < size; i++)
        {
            if (_fd_array[i] == defaultfd)
                continue;
            std::cout << _fd_array[i] << " ";
        }
        std::cout << "\r\n";
    }

    // 有新连接到来,或者 客户端发数据了!
    // 事件派发器!
    void Dispatcher(fd_set &rfds)
    {
        // 如果是listenfd就绪
        for (int i = 0; i < size; i++)
        {
            if (_fd_array[i] == defaultfd)
                continue;
            // fd合法,但不一定就绪,我怎么直到这个fd是就绪的
            // 一旦有fd就绪,select就会立即返回,修改位图
            // 只需要判断当前fd是否在 rfd位图里!
            if (FD_ISSET(_fd_array[i], &rfds))
            {
                // 该fd 读就绪
                if (_fd_array[i] == _listensock->Fd())
                {
                    // 1.listenfd就绪,新连接到来
                    Accepter();
                }
                else
                {
                    // 2.普通fd就绪,客户端发消息了!
                    Recver(_fd_array[i], i);
                }
            }
        }
    }
    
    // 连接管理器!
    void Accepter()
    {
        InetAddr client;
        int fd = _listensock->Accept(&client); // 这回accept不会阻塞,select已经解决了等 的问题
        if (fd >= 0)
        {
            LOG(LogLevel::INFO) << "get a new link success,sockfd:"
                                << fd << ",client is" << client.StringAddr();
            // 不能直接read,如何托管给select?
            // 添加到辅助数组里
            int pos = 0;
            for (pos = 0; pos < size; pos++)
            {
                if (_fd_array[pos] == defaultfd)
                    break;
            }
            if (pos == size)
            {
                LOG(LogLevel::WARNING) << "select 满了";
                close(fd);
            }
            else
                _fd_array[pos] = fd;
        }
    }

    // IO处理器
    void Recver(int fd, int pos)
    {
        char buffer[1024];
        // 普通 fd就绪,直接 读取,不会阻塞
        ssize_t n = recv(fd, &buffer, sizeof(buffer) - 1, 0); // bug
        if (n > 0)
        {
            std::cout << "client say@ " << buffer << std::endl;
        }
        else if (n == 0) // 客户端不写了
        {
            LOG(LogLevel::INFO) << "client quit";

            _fd_array[pos] = defaultfd; // 1.从辅助数组中删除
            close(fd);                  // 2.关闭fd
        }
        else // 读取出错
        {
            LOG(LogLevel::ERROR) << "recv error";
            _fd_array[pos] = defaultfd; // 1.从辅助数组中删除
            close(fd);                  // 2.关闭fd
        }
    }
    void Stop()
    {
        _isrunning = false;
    }
    ~SelectServer()
    {
    }

private:
    std::unique_ptr<Socket> _listensock;
    bool _isrunning;
    int _fd_array[size];
};

完。

相关推荐
小冷爱读书1 小时前
allocator
开发语言·c++
森G1 小时前
71、打包发布---------打包发布
c++·qt
小冷爱读书1 小时前
C++ 单例四种实现完整演进逻辑
开发语言·c++·c++学习
我是一颗柠檬1 小时前
【计算机网络全面教学】网络设备与故障排查,从集线器到Wireshark抓包实战Day7(2026年)
网络·计算机网络·wireshark
我是小bā吖2 小时前
Claude Code 模型接入阿里云 AI 网关并统计不同使用者的模型用量
网络·人工智能·阿里云
xsc-xyc2 小时前
用 Tailscale + Syncthing 实现手机、电脑与 NAS 的跨网络文件同步
linux·网络·网络安全·智能手机·电脑
beethobe2 小时前
PythonQt 学习之旅(一):从零构建 C++ 与 Python 的桥梁
c++·python·学习
鹏易灵2 小时前
C++——2.常量与 const、constexpr 初识详解
java·开发语言·c++
VidDown2 小时前
视频帧率技术详解:从 24fps 到 120fps,帧率如何影响你的观看体验?
网络·网络协议·编辑器·音视频·视频编解码·视频