【Linux/多路复用】select

五种IO模型

任何IO都包含两个步骤:等待和拷贝。在实际应用场景中,等待消耗的时间远远高于拷贝的时间,让IO更高效的核心办法是让等待的时间尽量少。

IO重要概念

同步VS异步通信

复制代码
同步:由调用者主动等待调用结果。
异步:调用发出后,调用者通过状态,信号等被通知,或通过回调函数处理这个调用。  

阻塞VS非阻塞

复制代码
阻塞调用指调用结果返回之前当前进程会被挂起,调用的线程只有在得到结果之后才会返回。
非阻塞调用指得到结果之前不会阻塞当前进程。

实现函数SetNoBlock

cpp 复制代码
void SetNonBlockOrDie(int fd)
{
    int f1=fcntl(fd,F_GETFL);
    if(f1<0)
    {
       perror("fcntl");
       return; 
    }
    fcntl(fd,F_SETFL,f1|O_NONBLOCK);
}

轮询方式读取标准输入:

cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
using namespace std;
void SetNoBlock(int fd)
{
    int fl = fcntl(fd, F_GETFL);
    if (fl < 0)
    {
        perror("fcntl");
        return;
    }
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}

int main()
{
    SetNoBlock(0);
    while (1)
    {
        char buf[1024] = {0};
        ssize_t read_size = read(0, buf, sizeof(buf) - 1);
        if (read_size < 0)
        {
            perror("read");
            sleep(1);
            continue;
        }
        printf("input:%s\n", buf);
    }
    return 0;
}

select介绍

复制代码
参数:
参数nfds是需要监视的最大的文件描述符值+1;
readfds,writefds,exceptfds分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描述符的集合.
参数timeout为结构timeval,用来设置select()的等待时间。
timeout取值:
NULL:一直等,select被阻塞,直到某个 fd 就绪
0,0:仅检测描述符集合的状态,立即返回,不阻塞
其他值:等待指定时间,若在指定的时间段里没有事件发生,select将超时返回。

fd_set本质是一个位图,使用对应的位来表示要监视的文件描述符。

一组接口来操作位图:

复制代码
void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位
int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd 的位是否为真
void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位
void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位

函数返回值:

复制代码
1.执行成功则返回文件描述词状态已改变的个数.
2.如果返回0代表在描述词状态改变前已超过timeout时间,没有返回.
3.当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds, exceptfds和timeout的值变成不可预测。

select执行过程举例

1.执行fd_set set; FD_ZERO(&set);则set用位表示是0000,0000。

2.若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1) .

3.若再加入fd=2,fd=1,则set变为0001,0011

4.执行select(6,&set,0,0,0)阻塞等待

5.若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。若没有事件发生,则fd=5被清空。

select特点

1.可监控的文件描述符取决于sizeof(fd_set)的值,即位图的大小是一定的,服务器上支持的最大文件描述符是sizeof(fd_set)*8.

  1. 将fd加入到select监控集的同时。还要再用一个数据结构array来保存放入select监控集中的fd。一是用于在select 返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入,扫描array的同时取得fd最大值maxfd,用于select的第一个参数。
    缺点

    1.每次调用select, 都需要手动设置fd集合, 非常不便.
    2.每次调用select,都需要把fd集合从用户态拷贝到内核态,开销在fd很多时会很大
    3.同时每次调用select都需要在内核遍历传递进来的所有fd,开销在fd很多时很大
    4.select支持的文件描述符数量太小.

select使用示例

检测标准输入输出:

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/select.h>

int main()
{
    fd_set read_fds;
    FD_ZERO(&read_fds);
    FD_SET(0, &read_fds);

    for (;;)
    {
        printf("> ");
        fflush(stdout);
        int ret = select(1, &read_fds, NULL, NULL, NULL);
        if (ret < 0)
        {
            perror("select");
            continue;
        }
        if (FD_ISSET(0, &read_fds))
        {
            char buf[1024] = {0};
            read(0, buf, sizeof(buf) - 1);
            printf("input: %s", buf);
        }
        else
        {
            printf("error! invaild fd\n");
            continue;
        }
        FD_ZERO(&read_fds);
        FD_SET(0, &read_fds);
    }
    return 0;
}

SelectServer.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <sys/select.h>
#include <sys/time.h>
#include "Socket.hpp"

using namespace std;

static const uint16_t defaultport = 8888;
static const int fd_num_max = sizeof(fd_set) * 8;
int defaultfd = -1;

class SelectServer
{
public:
    SelectServer(const uint16_t &port = defaultport)
        : _port(port)
    {
        for (int i = 0; i < fd_num_max; i++)
        {
            fd_array[i] = defaultfd;
        }
    }
    void Init()
    {
        _listensock.Socket();
        _listensock.Bind(_port);
        _listensock.Listen();
    }
    void Accepter()
    {
        std::string clientip;
        uint16_t clientport = 0;
        int sock = _listensock.Accept(clientip, clientport);
        if (sock < 0)
            return;
        lg(Info, "accept success,%s:%d,sockfd:%d", clientip.c_str(), clientport, sock);
        int pos = 1;
        for (; pos < fd_num_max; pos++)
        {
            if (fd_array[pos] != defaultfd)
                continue;
            else
                break;
        }
        // 找到一个可填入的下标
        if (pos == fd_num_max)
        {
            lg(Warning, "server is full,close %d now!", sock);
            close(sock);
        }
        else
        {
            fd_array[pos] = sock; // 填入
            PrintFd();            //
        }
    }
    void Recver(int fd, int pos)
    {
        char buffer[1024];
        ssize_t n = read(fd, buffer, sizeof(buffer) - 1); 
        if (n > 0)
        {
            buffer[n] = 0;
            cout << "get a message: " << buffer << endl;
        }
        else if (n == 0)
        {
            lg(Info, "client quit,me too,close fd: %d", fd);
            close(fd);
            fd_array[pos] = defaultfd; // 本质是从select中移除。
        }
        else
        {
            lg(Warning, "recv error,fd is: %d", fd);
            close(fd);
            fd_array[pos] = defaultfd;
        }
    }
    void Dispather(fd_set &rfds)
    {
        for (int i = 0; i < fd_num_max; i++)
        {
            int fd = fd_array[i];
            if (fd == defaultfd)
                continue;
            if (FD_ISSET(fd, &rfds))
            {
                if (fd == _listensock.Fd())
                {
                    Accepter(); // 连接管理器
                }
                else
                {
                    Recver(fd, i);
                }
            }
        }
    }
    void PrintFd()
    {
        cout << "online fd list: ";
        for (int i = 0; i < fd_num_max; i++)
        {
            if (fd_array[i] == defaultfd)
                continue;
            cout << fd_array[i] << " ";
        }
        cout << endl;
    }
    void Start()
    {
        int listensock = _listensock.Fd();
        fd_array[0] = listensock;
        while (true)
        {
            fd_set rfds;
            FD_ZERO(&rfds); // 读位图置零
            int maxfd = fd_array[0];
            for (int i = 0; i < fd_num_max; i++)
            {
                if (fd_array[i] == defaultfd)
                    continue;
                FD_SET(fd_array[i], &rfds);
                if (maxfd < fd_array[i])
                {
                    maxfd = fd_array[i];
                    lg(Info, "max fd update,max fd is: %d", maxfd);
                }
            } // 这个循环为了每次重新设置要等待的文件描述符到位图中,并更新maxfd的值以便后续调用。
            struct timeval timeout = {1, 0};
            // 如果事件就绪,上层不处理。select就会一直通知。
            int n = select(maxfd + 1, &rfds, nullptr, nullptr, &timeout);
            switch (n)
            {
            case 0:
                cout << "time out,timeout: " << timeout.tv_sec << " " << timeout.tv_usec << endl;
                break;
            case -1:
                cerr << "select error" << endl;
                break;
            default:
                // 有事情就绪
                cout << "get a new link!!!" << endl;
                Dispather(rfds);
                break;
            }
        }
    }
    ~SelectServer()
    {
        _listensock.Close();
    }

private:
    Sock _listensock;
    uint16_t _port;
    int fd_array[fd_num_max];
};
相关推荐
我叫唧唧波2 小时前
【自动化部署】CI/CD 实战(三):让 Argo CD 接管 CD,Jenkins 镜像自动同步到集群
运维·前端·ci/cd·docker·自动化·jenkins·argocd
t***5442 小时前
如何验证Clang是否在Dev-C++中正常工作
开发语言·c++
dualven_in_csdn2 小时前
【docker】docker下如何使用宿主主机的GPU
运维·docker·容器
cyber_两只龙宝2 小时前
【Oracle】Oracle之SQL的集合运算符
linux·运维·数据库·sql·云原生·oracle
mount_myj2 小时前
按位与【C语言】
c语言
Hello.Reader2 小时前
Ubuntu 安装 Miniconda 完整从零开始把 Conda 环境搭起来
linux·ubuntu·conda
charlie1145141912 小时前
嵌入式C++开发第17篇:C++23特性收尾 —— 属性、链接与零开销抽象的最终证明
开发语言·c++·stm32·学习·c++23
小小的木头人2 小时前
Nginx 访问控制及安全配置文档
运维·nginx·安全
菱玖2 小时前
K8s集群部署与应用运维实战
运维·容器·kubernetes