【网络】:高级IO(一)

news2024/12/24 10:24:30

高级IO

  • 一.五种IO模型
  • 二.多路转接(select)
  • 三.非阻塞IO(funcl)
  • 四.POLL

IO=等待+拷贝。单位时间内,IO过程中,等的比例越小,IO就越高效。几乎所有提高IO效率的方式本质都是基于此。

一.五种IO模型

举个例子:一群人在河边钓鱼。

在这里插入图片描述

同步IO:要参与IO。例如上面例子同步都参与了钓鱼这一IO。
异步IO:不参与IO,只发起IO。

1.阻塞IO

阻塞IO: 在内核将数据准备好之前, 系统调用会一直等待. 所有的套接字, 默认都是阻塞方式。

在这里插入图片描述

2.非阻塞IO

非阻塞IO: 如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK错误码。

非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询. 这对CPU来说是较大的浪费, 一般只有特定场景下才使用。

在这里插入图片描述

3.信号驱动IO

信号驱动IO: 内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作。

在这里插入图片描述

4.IO多路转接

IO多路转接: 虽然从流程图上看起来和阻塞IO类似. 实际上最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态。

在这里插入图片描述

5.异步IO

异步IO: 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)。

在这里插入图片描述

小结:

任何IO过程中, 都包含两个步骤. 第一是等待, 第二是拷贝. 而且在实际的应用场景中, 等待消耗的时间往往都远远高于拷贝的时间. 让IO更高效, 最核心的办法就是让等待的时间尽量少。

二.多路转接(select)

系统提供select函数来实现多路复用输入/输出模型.

select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;
程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变;

select的函数原型如下: #include <sys/select.h>

在这里插入图片描述
nfds

参数nfds是需要监视的最大的文件描述符值+1;

fd_set

rdset(读合集),wrset(写合集),exset(错误合集)分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描述符的集合;(输入输出型)

其实这个结构就是一个整数数组, 更严格的说, 是一个 “位图”. 使用位图中对应的位来表示要监视的文件描述符。

在这里插入图片描述
比特位的位置表示文件描述符编号,例如输入0000 1111,代表让操作系统关注0,1,2,3号文件;如果2号文件准备就绪,就会输出0000 0100。

提供了一组操作fd_set的接口, 来比较方便的操作位图。

在这里插入图片描述

一般我们关心fd的三种状态,读,写和异常。如果我们只需要关注读,就把文件描述符设置到readfds即可。

timeout

参数timeout为结构timeval,用来设置select()的等待时间。NULL:则表示select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件;0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回。(输入输出型)

在这里插入图片描述

timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。

返回值

返回值:n>0代表有n个fd就绪了;n==0代表超时,没有错误也没有就绪;n<0代表等待出错。

在这里插入图片描述

使用例子

#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(uint16_t port = defaultport) : _port(port)
    {
        for (int i = 0; i < fd_num_max; i++)
        {
            fd_array[i] = defaultfd;
            // std::cout << "fd_array[" << i << "]" << " : " << fd_array[i] << std::endl;
        }
    }
    bool Init()
    {
        _listensock.Socket();
        _listensock.Bind(_port);
        _listensock.Listen();

        return true;
    }
    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, sock fd: %d", clientip.c_str(), clientport, sock);

        // sock -> fd_array[]
        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();
            // TODO
        }
    }
    void Recver(int fd, int pos)
    {
        // demo
        char buffer[1024];
        ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // bug?
        if (n > 0)
        {
            buffer[n] = 0;
            cout << "get a messge: " << buffer << endl;
        }
        else if (n == 0)
        {
            lg(Info, "client quit, me too, close fd is : %d", fd);
            close(fd);
            fd_array[pos] = defaultfd; // 这里本质是从select中移除
        }
        else
        {
            lg(Warning, "recv error: fd is : %d", fd);
            close(fd);
            fd_array[pos] = defaultfd; // 这里本质是从select中移除
        }
    }
    void Dispatcher(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 // non listenfd
                {
                    Recver(fd, i);
                }
            }
        }
    }
    void Start()
    {
        int listensock = _listensock.Fd();
        fd_array[0] = listensock;
        for (;;)
        {
            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);
                }
            }

            // accept?不能直接accept!检测并获取listensock上面的事件,新连接到来,等价于读事件就绪

            // struct timeval timeout = {1, 0}; // 输入输出,可能要进行周期的重复设置
            struct timeval timeout = {0, 0}; // 输入输出,可能要进行周期的重复设置
            // 如果事件就绪,上层不处理,select会一直通知你!
            // select告诉你就绪了,接下来的一次读取,我们读取fd的时候,不会被阻塞
            // rfds: 输入输出型参数。 1111 1111 -> 0000 0000
            int n = select(maxfd + 1, &rfds, nullptr, nullptr, /*&timeout*/ nullptr);
            switch (n)
            {
            case 0:
                cout << "time out, timeout: " << timeout.tv_sec << "." << timeout.tv_usec << endl;
                break;
            case -1:
                cerr << "select error" << endl;
                break;
            default:
                // 有事件就绪了,TODO
                cout << "get a new link!!!!!" << endl;
                Dispatcher(rfds); // 就绪的事件和fd你怎么知道只有一个呢???
                break;
            }
        }
    }
    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;
    }
    ~SelectServer()
    {
        _listensock.Close();
    }

private:
    Sock _listensock;
    uint16_t _port;
    int fd_array[fd_num_max];   // 数组, 用户维护的!
    // int wfd_array[fd_num_max];
};

在这里插入图片描述

三.非阻塞IO(funcl)

一个文件描述符, 默认都是阻塞IO。

在这里插入图片描述
传入的cmd的值不同, 后面追加的参数也不相同。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

使用F_GETFL将当前的文件描述符的属性取出来(这是一个位图).
然后再使用F_SETFL将文件描述符设置回去. 设置回去的同时, 加上一个O_NONBLOCK参数

设置文件描述符非阻塞的方式非常多,例如:open时采用非阻塞方式。但functl方式是最常用的。

四.POLL

它的特点是将输入事件和输出事件进行了分离(不像select用同一个位图标识输入和输出)。
在这里插入图片描述

poll的作用与select一样,它的设计只是为了解决select的缺点。fds是一个poll函数监听的结构列表. 每一个元素中, 包含了三部分内容:
fds 文件描述符, 监听的事件集合, 返回的事件集合.
nfds表示fds数组的长度.
timeout表示poll函数的超时时间, 单位是毫秒(ms)设为-1代表阻塞。

在这里插入图片描述

同select不同的是,它通过宏的方式告诉系统,该文件是什么类型。例如:event_fds[1].events = POLLIN,代表该输入事件关心读。

在这里插入图片描述

在这里插入图片描述

使用例子

#pragma once

#include <iostream>
#include <poll.h>
#include <sys/time.h>
#include "Socket.hpp"

using namespace std;

static const uint16_t defaultport = 8888;
static const int fd_num_max = 64;
int defaultfd = -1;
int non_event = 0;

class PollServer
{
public:
    PollServer(uint16_t port = defaultport) : _port(port)
    {
        for (int i = 0; i < fd_num_max; i++)
        {
            _event_fds[i].fd = defaultfd;
            _event_fds[i].events = non_event;
            _event_fds[i].revents = non_event;

            // std::cout << "fd_array[" << i << "]" << " : " << fd_array[i] << std::endl;
        }
    }
    bool Init()
    {
        _listensock.Socket();
        _listensock.Bind(_port);
        _listensock.Listen();

        return true;
    }
    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, sock fd: %d", clientip.c_str(), clientport, sock);

        // sock -> fd_array[]
        int pos = 1;
        for (; pos < fd_num_max; pos++) // 第二个循环
        {
            if (_event_fds[pos].fd != 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;
            _event_fds[pos].fd = sock;
            _event_fds[pos].events = POLLIN;
            _event_fds[pos].revents = non_event;
            PrintFd();
            // TODO
        }
    }
    void Recver(int fd, int pos)
    {
        // demo
        char buffer[1024];
        ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // bug?
        if (n > 0)
        {
            buffer[n] = 0;
            cout << "get a messge: " << buffer << endl;
        }
        else if (n == 0)
        {
            lg(Info, "client quit, me too, close fd is : %d", fd);
            close(fd);
            _event_fds[pos].fd = defaultfd; // 这里本质是从select中移除
        }
        else
        {
            lg(Warning, "recv error: fd is : %d", fd);
            close(fd);
            _event_fds[pos].fd = defaultfd; // 这里本质是从select中移除
        }
    }
    void Dispatcher()
    {
        for (int i = 0; i < fd_num_max; i++) // 这是第三个循环
        {
            int fd = _event_fds[i].fd;
            if (fd == defaultfd)
                continue;

            if (_event_fds[i].revents & POLLIN)
            {
                if (fd == _listensock.Fd())
                {
                    Accepter(); // 连接管理器
                }
                else // non listenfd
                {
                    Recver(fd, i);
                }
            }
        }
    }
    void Start()
    {
        _event_fds[0].fd = _listensock.Fd();
        _event_fds[0].events = POLLIN;
        int timeout = 3000; // 3s
        for (;;)
        {
            int n = poll(_event_fds, fd_num_max, timeout);
            switch (n)
            {
            case 0:
                cout << "time out... " << endl;
                break;
            case -1:
                cerr << "poll error" << endl;
                break;
            default:
                // 有事件就绪了,TODO
                cout << "get a new link!!!!!" << endl;
                Dispatcher(); // 就绪的事件和fd你怎么知道只有一个呢???
                break;
            }
        }
    }
    void PrintFd()
    {
        cout << "online fd list: ";
        for (int i = 0; i < fd_num_max; i++)
        {
            if (_event_fds[i].fd == defaultfd)
                continue;
            cout << _event_fds[i].fd << " ";
        }
        cout << endl;
    }
    ~PollServer()
    {
        _listensock.Close();
    }

private:
    Sock _listensock;
    uint16_t _port;
    struct pollfd _event_fds[fd_num_max]; // 数组, 用户维护的!
    // struct pollfd *_event_fds;

    // int fd_array[fd_num_max];
    // int wfd_array[fd_num_max];
};

但实际上,它依然存在遍历问题,所以为此我们又引入了epoll,请看下一篇博客。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1620583.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深入解析:前端跨域问题及其CORS、代理、JSONP、Nginx反向代理等解决方案

前端跨域是指在浏览器环境下&#xff0c;当一个网页&#xff08;源&#xff09;尝试访问与自身源不同的服务器资源&#xff08;目标源&#xff09;时&#xff0c;由于浏览器的同源策略限制而产生的访问限制现象。同源策略&#xff08;Same-Origin Policy&#xff09;是浏览器实…

芜湖市夜间景区、文娱主题活动、夜读空、精品文艺演出、数字促销补助等夜间经济奖励政策申报条件、材料

芜湖市示范街区、示范门店、夜间景区、文娱主题活动、体育赛事、夜读空、精品文艺演出、数字促销补助等夜间经济奖励政策申报条件、材料及补贴标准整理如下 芜湖市2023年促进夜间经济发展若干政策申报时间&#xff1a; 针对2023年度促进夜间经济发展若干政策&#xff08;商务局…

❤️新版Linux零基础快速入门到精通——第一部分❤️

❤️新版Linux零基础快速入门到精通——第一部分❤️ 非科班的我&#xff01;Ta&#xff01;还是来了~~~1. 来认识一下Linux吧!1.1 操作系统概述1.1.1 操作系统概述1.1.2 操作系统的发展史1.1.2.1 Unix1.1.2.2 Minix1.1.2.3 Linux 1.1.3 操作系统的发展 1.2 Linux初识1.2.1 Lin…

二叉检索树的实现——增删改查、读取命令文件、将结果写入新文件

看这篇文章前的知识储备 链接: 二叉树的性质和分类 链接: 二叉检索树的概念 、insert方法的图解、实现、时间代价分析 链接: 二叉检索树的search、remove方法的图解、实现、时间代价分析 1、中序遍历及中序遍历写进文件的区别 两者思路一致&#xff0c;将二叉树分为三部分&…

Linux信号(产生)

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 目录 信号是什么&#xff1f; 为什么要有信号&#xff1f; 信号是如何产生的&#xff1f; kill命令 键盘产生信号 系统调用 kill系统调用 raise函数 abort函数 自制kill命令 ​编辑 软件条件 举例一&#xff1…

C++ :设计模式实现

文章目录 原则单一职责原则开闭原则依赖倒置原则接口隔离原则里氏替换原则 设计模式单例模式观察者模式策略模式代理模式 原则 单一职责原则 定义&#xff1a; 即一个类只负责一项职责 问题&#xff1a; 类 T 负责两个不同的职责&#xff1a;职责 P1&#xff0c;职责 P2。当…

大数据第六天

这里写目录标题 问题解决问题查询插入(时间慢)练习sql数据清理 问题 FAILED: ParseException line 1:16 mismatched input ‘input’ expecting INPATH near ‘local’ in load statement MismatchedTokenException(24!155) 加载数据的时候出现了这个错误&#xff0c;我们解释…

【六十】【算法分析与设计】用一道题目解决dfs深度优先遍历,dfs中节点信息,dfs递归函数模板进入前维护出去前回溯,唯一解的剪枝飞升返回值true

路径之谜 题目描述 小明冒充X星球的骑士,进入了一个奇怪的城堡。 城堡里边什么都没有,只有方形石头铺成的地面。 假设城堡地面是nn个方格。如下图所示。 按习俗,骑士要从西北角走到东南角。可以横向或纵向移动,但不能斜着音走,也不能跳跃。每走到一个新方格,就要向正北 方和正西…

短信视频提取批量工具,免COOKIE,博主视频下载抓取,爬虫

痛点&#xff1a;关于看了好多市面的软件&#xff0c;必须要先登录自己的Dy号才能 然后找到自己的COOKIE 放入软件才可以继续搜索&#xff0c;并且无法避免长时间使用 会导致无法正常显示页面的问题。 有没有一种方法 直接可以使用软件&#xff0c;不用设置的COOKIE的方法呢 …

对于地理空间数据,PostGIS扩展如何在PostgreSQL中存储和查询地理信息?

文章目录 一、PostGIS扩展简介二、PostGIS存储地理空间数据1. 创建空间数据表2. 插入空间数据 三、PostGIS查询地理空间数据1. 查询指定范围内的地理空间数据2. 计算地理空间数据之间的距离3. 对地理空间数据进行缓冲区分析 四、总结 地理空间数据是指描述地球表面物体位置、形…

开源社区与开发者的故事

开源社区与开发者的故事 什么是开源社区你参加开源社区的主要目的你是否在开源社区中贡献&#xff0c;或者开源自己的项目&#xff1f;你认为个人开发者是否应该从开源中获利&#xff1f;如果是&#xff0c;该如何获利&#xff1f; 今天要谈及的主题是开源社区&#xff0c;那么…

2024年新算法-牛顿-拉夫逊优化算法(NRBO)优化BP神经网络回归预测

亮点&#xff1a; 输出多个评价指标&#xff1a;R2&#xff0c;RMSE&#xff0c;MSE&#xff0c;MAPE和MAE 满足需求&#xff0c;分开运行和对比的都有对应的主函数&#xff1a;main_BP, main_NRBO, main_BPvsBP_NRBO&#xff0c;并且详细中文注释 方便快捷&#xff1a;替换…

打破企业差旅管理困局,让金融CEO眼前一亮的出行方案

在国内券商投行部工作是怎样一种体验&#xff1f; “长期出差&#xff0c;而且出长差&#xff0c;时常让人有漂泊的孤独感。”这是某问答平台上的高赞回答的第一条。 对金融人来说&#xff0c;说走就走的旅行可能根本没有什么吸引力&#xff0c;时刻准备着说走就走的出差才是生…

MVCC的执行原理

MVCC的执行原理 MVCC简介事务的隔离级别MVCC作用当前读和快照读MVCC实现原理Undo LogUndo Log 版本链Read View判断方法判断规则 小结 MVCC简介 MVCC&#xff08;Multi-Version Concurrency Control&#xff09;是一种并发控制机制&#xff0c;用于解决数据库并发访问中&#…

pyqt 动态更换表头和数据

目录 pyqt 动态更换表头和数据代码 效果图&#xff1a; pyqt 动态更换表头和数据代码 from PyQt5.QtGui import QColor, QBrush from PyQt5.QtWidgets import QApplication, QTableWidget, QVBoxLayout, QWidget, QPushButton, QTableWidgetItemclass Example(QWidget):def _…

如何诊断并解决PostgreSQL中的磁盘空间不足问题?

文章目录 诊断磁盘空间不足问题1. 检查服务器磁盘空间2. 检查PostgreSQL数据目录大小3. 检查PostgreSQL中的大表和大对象 解决磁盘空间不足问题1. 清理不必要的文件和日志2. 清理或压缩大表和大对象3. 扩展磁盘容量4. 优化数据库配置和查询 在使用PostgreSQL数据库时&#xff0…

华为云实验 -- 对云硬盘数据盘进行备份

文章目录 备份Linux系统备份1.购买Linux操作系统的ESC(云服务器)2.挂载数据盘--初始化--分区--格式化2.1.点击"远程登录"a.查看/dev/vdb数据盘b.新建主分区/dev/vdb1 2.2.查看新建分区大小,分区格式信息a.确定之前的分区操作是否正确b.确认完成后&#xff0c;将分区结…

【MATLAB源码-第32期】基于matlab的通信及雷达中常用伪随机码m序列的仿真。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 M序列&#xff0c;也称为最大长度序列或者伪随机序列&#xff0c;是一种特殊的二进制序列。它的特点是在有限的长度内&#xff0c;尽管它是伪随机的&#xff0c;但它会在特定的周期内不重复地循环。 在数学上&#xff0c;M序…

利用fft算法重写公式并理解频率和像素变化率的关系(完美解决问题)

算法我就不贴了。算法就是算法导论的内容。 我直接写推导过程。 假设变化率为f(n1)-f(n) 首先计算二进制数&#xff0c;这里我假设为3位二进制。 例如:f(5)-f(4)&#xff0c; 5和4的二进制为101,100。所以逆序数为101&#xff0c;001 101对应的频率为5, 001对应的频率为1…

力扣HOT100 - 236. 二叉树的最近公共祖先

解题思路&#xff1a; dfs 节点p,q异侧时&#xff0c;节点root为它们的公共祖先。 class Solution {public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {if (root null || p root || q root) return root;TreeNode left lowestCommonAncest…