高级IO---五种IO模型多路转接之Select

news2024/11/29 20:52:36

文章目录

  • 五种IO模型
    • 1、阻塞IO
    • 2、非阻塞IO
    • 3、信号驱动IO
    • 4、多路转接IO
    • 5、异步IO
    • 总结IO
  • 同步与异步
  • 阻塞与非阻塞
  • 设置非阻塞
    • 利用fcntl接口实现一个设置非阻塞的函数
  • 多路转接之Select
    • select函数原型
      • fd_set结构
      • 返回值
    • socket就绪条件
      • 读就绪
      • 写就绪
    • select的特点
    • select使用示例
      • Util.hpp(工具类,将用到的函数放在该类中)
      • Server.hpp(实现服务器)
      • log.hpp(日志类)
      • Server.cc
      • 效果演示

五种IO模型

1、阻塞IO

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

也就是说,在数据准备好之前,系统调用只会静静的等待着数据的到来并不会去干其他的事情

就好比去钓鱼,将鱼饵丢进水里后,啥也不干就静静的看着鱼饵随时准备鱼上钩

2、非阻塞IO

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

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

就好比去钓鱼,将鱼饵丢进水里后,不会一直去盯着鱼饵,一边干着其他事看看书啥的偶尔看看鱼饵

3、信号驱动IO

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

这就好比在鱼竿上系上一个铃铛,然后去干别的事,当鱼上钩时拉动鱼线就会使铃铛摇晃发出声音提醒鱼上钩了

4、多路转接IO

最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态。

这就好比拿着多条鱼竿去钓鱼,全部丢进水里然后巡视所有鱼竿,一有鱼上钩就拉动对应的鱼竿

5、异步IO

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

这就好比老板想吃鱼,不用自己去钓,让手下去钓鱼调到了之后交给他

总结IO

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

所以综上而言,多把钓竿同时等待,鱼上钩的概率就越大上钩的时间也就越快,所以多路转接IO效率高

同步与异步

同步和异步关注的是消息通信机制

  1. 同步:就是在发出一个调用时,在没有得到结果之前,该调用不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由调用者主动等待这个调用的结果;
  2. 异步:正好相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果; 换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果; 而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。

注意这里的同步通信和进程之间的同步是完全不想干的概念

阻塞与非阻塞

  1. 阻塞:调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回
  2. 非阻塞:在不能立刻得到结果之前,该调用不会阻塞当前线程。

设置非阻塞

需要利用系统调用 ---- fcntl

#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ );

参数一为:需要设置的文件描述符

参数二为:想要让fcntl实现的功能

  1. 复制一个现有的描述符(cmd = F_DUPFD) .
  2. 获得/设置文件描述符标记(cmd = F_GETFD或F_SETFD).
  3. 获得/设置文件状态标记(cmd = F_GETFL或F_SETFL).
  4. 获得/设置异步I/O所有权(cmd = F_GETOWN或F_SETOWN).
  5. 获得/设置记录锁(cmd = F_GETLK,F_SETLK或F_SETLKW)

其中设置非阻塞为第三个功能。

后面的为追加参数

利用fcntl接口实现一个设置非阻塞的函数

void SetNoBlock(int fd) {
	// 使用F_GETFL将当前的文件描述符的属性取出来(这是一个位图)
    int fl = fcntl(fd, F_GETFL);
    
    if (fl < 0) {
        perror("fcntl");
        return;
    }
    // 再使用F_SETFL将文件描述符设置回去. 设置回去的同时, 加上一个O_NONBLOCK参数
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}

多路转接之Select

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

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

select函数原型

 #include <sys/select.h>

/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,
          fd_set *exceptfds, struct timeval *timeout);
  1. 参数nfds是需要监视的最大的文件描述符值+1
  2. rdset、wrset、exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集合及异常文件描述符的集合
  3. 参数timeout为结构timeval,用来设置select()的等待时间,如果在指定的时间段里没有事件发生, select将超时返回

fd_set结构

typedef struct
  {
    /* XPG4.2 requires this member name.  Otherwise avoid the name
       from the global namespace.  */
#ifdef __USE_XOPEN
    __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
#else
    __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif
  } fd_set;

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

提供了一组操作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的全部位

例如取fd_set为1个字节,为1字节, fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd

返回值

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

socket就绪条件

读就绪

  1. socket内核中, 接收缓冲区中的字节数, 大于等于低水位标记SO_RCVLOWAT. 此时可以无阻塞的读该文件描述符, 并且返回值大于0;
  2. socket TCP通信中, 对端关闭连接, 此时对该socket读, 则返回0;
  3. 监听的socket上有新的连接请求;
  4. socket上有未处理的错误

写就绪

  1. socket内核中, 发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小), 大于等于低水位标记SO_SNDLOWAT, 此时可以无阻塞的写, 并且返回值大于0;
  2. socket的写操作被关闭(close或者shutdown). 对一个写操作被关闭的socket进行写操作, 会触发SIGPIPE信号;
  3. socket使用非阻塞connect连接成功或失败之后;
  4. socket上有未读取的错误;

select的特点

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

select的缺点:

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

select使用示例

这里以只关心读事件为例,写事件同理

Util.hpp(工具类,将用到的函数放在该类中)

#pragma once

#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <string>
#include <sys/socket.h>
#include <functional>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"
#include <cstring>
#include <vector>

using namespace std;

#define INITPORT 8000
#define FDNUM 1024
#define DEFAULTFD -1

// 打印函数调试
void Print(const vector<int> &fdv)
{
    cout << "fd list: ";
    for (int i = 0; i < FDNUM; i++)
    {
        if (fdv[i] != DEFAULTFD)
            cout << fdv[i] << " ";
    }
    cout << endl;
}

class Util
{
public:
    static void Recv(vector<int> &fdv, int sock, int i)
    {
        // 读取
        // 读取失败就关闭sock并且修改集合组里的数据
        char buff[1024];
        ssize_t s = recv(sock, buff, sizeof(buff) - 1, 0);
        if (s > 0)
        {
            buff[s] = 0;
            cout << "client: " << buff << endl;
            LogMessage(NORMAL, "client: %s", buff);
        }
        else if (s == 0)
        {
            close(sock);
            fdv[i] = DEFAULTFD;
            LogMessage(NORMAL, "client quit");
            return;
        }
        else
        {
            close(sock);
            fdv[i] = DEFAULTFD;
            LogMessage(ERROR, "client quit: %s", strerror(errno));
            return;
        }

        // 写回数据
        // 这里不考虑写事件
        string response = buff;

        write(sock, response.c_str(), response.size());
        LogMessage(DEBUG, "Recver end");
    }

    // 将通信sock添加进集合组
    static void AddSock(vector<int> &fdv, int listensock)
    {
        // listensock读事件就绪
        string clientip;
        uint16_t clientport;
        int sock = Util::GetSock(listensock, &clientip, &clientport);
        if (sock < 0)
            return;
        else
        {
            LogMessage(NORMAL, "accept success [%s:%d]", clientip.c_str(), clientport);
            // 遍历数组,要考虑满的情况
            // 遇到为-1的位置插入新的sock
            int i = 0;
            for (; i < FDNUM; ++i)
                if (fdv[i] == DEFAULTFD)
                    break;
            if (i == FDNUM)
            {
                LogMessage(WARNING, "server if full, please wait");
                close(sock);
            }
            else
                fdv[i] = sock;
        }

        Print(fdv);
        LogMessage(DEBUG, "Accepter out");
    }

    // 获取新连接创建通信sock
    static int GetSock(int listensock, string *clientip, uint16_t *clientport)
    {
        struct sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        socklen_t len = sizeof(peer);
        int sock = accept(listensock, (struct sockaddr *)&peer, &len);
        if (sock < 0)
            LogMessage(ERROR, "accept socket error, next");
        else
        {
            LogMessage(NORMAL, "accept socket %d success", sock);
            cout << "sock: " << sock << endl;
            *clientip = inet_ntoa(peer.sin_addr);
            *clientport = ntohs(peer.sin_port);
        }

        return sock;
    }

    // 设置监听套接字为监听状态
    static void setListen(int listensock)
    {
        if (listen(listensock, 5) < 0)
        {
            LogMessage(FATAL, "listen socket error!");
            exit(3);
        }
        LogMessage(NORMAL, "listen socket success");
    }

    // 绑定网络信息
    static void bindSock(int port, int listensock)
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        local.sin_addr.s_addr = INADDR_ANY;
        if (bind(listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            LogMessage(FATAL, "bind socket error!");
            exit(2);
        }
        LogMessage(NORMAL, "bind sock success");
    }

    // 创建监听套接字
    static void createSock(int *listensock)
    {
        *listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (listensock < 0)
        {
            LogMessage(FATAL, "create socket error!");
            exit(1);
        }
        LogMessage(NORMAL, "create socket success");

        // 设置进程可以立即重启
        int opt = 1;
        setsockopt(*listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
    }

    // 设置非阻塞
    static void SetNonBlock(int fd)
    {
        int f = fcntl(fd, F_GETFL);
        if (f < 0)
        {
            cerr << "fcntl" << endl;
            return;
        }
        fcntl(fd, F_SETFL, f | O_NONBLOCK);
    }
};

Server.hpp(实现服务器)

#pragma once

#include <iostream>
#include "Util.hpp"
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

using namespace std;

class Server
{
public:
    Server(const uint16_t port = INITPORT)
        : _port(port), _listensock(-1)
    {
    }

    void HandlerEvent(fd_set &rfds)
    {
        int i = 0;
        for (auto e : fdv)
        {
            // 过滤掉非法的fd
            if (e == DEFAULTFD)
                continue;
            if (FD_ISSET(e, &rfds) && e == _listensock) // 判断listensock在不在就绪的集合中
                Util::AddSock(fdv, _listensock);
            else if(FD_ISSET(e, &rfds)) // 如果为其他的文件描述符则读取数据
                Util::Recv(fdv, e, i);
            else
            {}

            ++i;
        }
    }

    void Init()
    {
        // 创建监听套接字
        Util::createSock(&_listensock);

        // 绑定网络信息
        Util::bindSock(_port, _listensock);

        // 设置监听套接字为监听状态
        Util::setListen(_listensock);

        fdv.resize(FDNUM, DEFAULTFD);
        fdv[0] = _listensock;
    }

    void start()
    {
        while (1)
        {
            fd_set rfds;
            // 清除描述词组set的全部位
            FD_ZERO(&rfds);
            // 记录下最大的文件描述符
            int max = fdv[0];
            // 遍历数组,将合法的fd插入到事件集中
            // 并记录最大的fd为调用select接口做准备
            for (auto e : fdv)
            {
                if (e == DEFAULTFD)
                    continue;
                // 设置描述词组set中相关fd的位
                FD_SET(e, &rfds);
                if (e > max)
                    max = e;
            }
            // 设置等待时间结构
            struct timeval timeout = {1, 0};
            int n = select(max + 1, &rfds, nullptr, nullptr, &timeout);
            switch (n)
            {
            case 0:
                cout << "timeout...." << endl;
                LogMessage(NORMAL, "timeout....");
                break;
            case -1:
                printf("select error, code: %d, err string: %s", errno, strerror(errno));
                LogMessage(WARNING, "select error, code: %d, err string: %s", errno, strerror(errno));
                break;
            default:
                // 有事件就绪
                cout << "event readly" << endl;
                LogMessage(NORMAL, "event readly");
                // 处理事件
                HandlerEvent(rfds);
                break;
            }
        }
    }

    ~Server()
    {
        if (_listensock < 0)
            close(_listensock);
    }

private:
    int _listensock;
    uint16_t _port;
    vector<int> fdv;
};

log.hpp(日志类)

#pragma once

#include <iostream>
#include <string>
#include <cstdarg>
#include <ctime>
#include <unistd.h>

using namespace std;

#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4

const char *to_levelstr(int level)
{
    switch (level)
    {
    case DEBUG:
        return "DEBUG";
    case NORMAL:
        return "NORMAL";
    case WARNING:
        return "WARNING";
    case ERROR:
        return "ERROR";
    case FATAL:
        return "FATAL";
    default:
        return nullptr;
    }
}

void LogMessage(int level, const char *format, ...)
{
#define NUM 1024
    char logpre[NUM];
    snprintf(logpre, sizeof(logpre), "[%s][%ld][%d]", to_levelstr(level), (long int)time(nullptr), getpid());

    char line[NUM];
    // 可变参数
    va_list arg;
    va_start(arg, format);

    vsnprintf(line, sizeof(line), format, arg);

    // 保存至文件
    FILE* log = fopen("log.txt", "a");
    FILE* err = fopen("log.error", "a");

    if(log && err)
    {
        FILE *curr = nullptr;
        if(level == DEBUG || level == NORMAL || level == WARNING) 
            curr = log;
        if(level == ERROR || level == FATAL) 
            curr = err;
        if(curr) fprintf(curr, "%s%s\n", logpre, line);

        fclose(log);
        fclose(err);
    }
}

Server.cc

#include "Server.hpp"
#include <memory>

// 输出命令错误函数
void Usage(string proc)
{
    cout << "Usage:\n\t" << proc << " local_port\n\n";
}

int main(int argc, char *argv[])
{
    // 启动服务端不需要指定IP
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }

    uint16_t port = atoi(argv[1]);
    unique_ptr<Server> sptr(new Server(port));

    sptr->Init();
    sptr->start();

    return 0;
}

效果演示

image-20230907205551697

首先由客户端连接,listen套接字就绪,建立连接

然后客户端发送数据,负责通信的套接字就绪,读取数据后再发回去

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

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

相关文章

Flowable 之任务分配

文章目录 一、任务分配和流程变量1.1 任务分配1.1.1 固定分配1.1.2 表达式分配① 值表达式② 方法表达式 1.1.3 监听器分配 1.2 流程变量1.2.1 全局变量1.2.2 局部变量1.2.3 案例讲解 二、候选人和候选人组2.1 候选人2.1.1 部署和启动流程2.1.2 任务的查询2.1.3 任务的拾取2.1.…

FRR+BFD+OSPF与BGP联动

1.拓扑设计 2.拓扑介绍 如图&#xff0c;LY集团 由核心机房与接入层网络组成&#xff0c;集团网络需要通过核心机房访问互联网&#xff0c;集团网络运行OSPF与IBGP协议&#xff1b;集团网络中接入层网络正常经过R2访问互联网&#xff0c;如果R2设备失效后&#xff0c;应立即换到…

Git版本管理

Git版本介绍 Git 是一个分布式版本控制系统&#xff0c;它被广泛用于协作软件开发和管理代码的变更。Git 的设计目标是为了处理速度快、灵活性强、数据完整性好的版本管理需求。以下是 Git 版本管理的详细介绍&#xff1a; 版本控制系统 (VCS)&#xff1a; Git 是一种版本控制…

深度ESP32 PWM教程如何在ESP32 中使用PWM

关于ESP32PWM的简要说明 ESP32 SoC 满载了非常有用的外设&#xff0c;PWM 就是其中之一。是的。ESP32 的芯片中有一个专用的 PWM 硬件模块。脉宽调制或简称PWM是一种成熟且广泛使用的供电技术。 您可以使用 ESP32 的 PWM 来驱动 LED、电机&#xff08;普通直流电机和无刷电机…

Vue2项目练手——通用后台管理项目第六节

Vue2项目练手——通用后台管理项目 用户管理页table表格获取表格数据目录列表user.jsmock.jsindex.jsUsers.vue 新增和编辑功能Users.vue 删除功能使用的组件Users.vue 用户管理页 table表格 使用的组件和前面的表格使用的一致。 获取表格数据 目录列表 user.js import Mo…

基于SpringBoot的在线拍卖系统

基于SpringBootVue的在线拍卖系统&#xff0c;前后端分离 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 【主要功能】 后台&#xff1a;用户管理、商品类型管理…

无需设计经验,也能制作出精美的房地产电子传单

在数字化时代&#xff0c;传统的纸质传单已经不能满足人们对于互动和个性化的需求。为此&#xff0c;许多房地产公司开始将目光转向H5微传单&#xff0c;这是一种通过互联网和手机浏览器来传达信息的创新方式。今天&#xff0c;我们将教你如何使用乔拓云网制作房地产微传单H5&a…

STC单片机+EC11编码器实现调节PWM输出占空比

STC单片机+EC11编码器实现调节PWM输出占空比 📌相关篇《stc单片机外部中断+EC11编码器实现计数功能》 📍《STC15单片机特有的PWM寄存器和普通定时器实现PWM输出》 🎬效果演示: 🌼 通过逻辑分析仪获取P11引脚上的信号波形,查看其对应输出的占空比。 ✨本例程基于上面两…

Laravel 模型的关联写入多对多的关联写入 ⑩③

作者 : SYFStrive 博客首页 : HomePage &#x1f4dc;&#xff1a; THINK PHP &#x1f4cc;&#xff1a;个人社区&#xff08;欢迎大佬们加入&#xff09; &#x1f449;&#xff1a;社区链接&#x1f517; &#x1f4cc;&#xff1a;觉得文章不错可以点点关注 &#x1f44…

vue 页面加水印

首先创建一个waterMark.js文件&#xff0c;当然文件命名可自定义&#xff0c; use strictconst watermark {}/**** param {要设置的水印的内容} str* param {需要设置水印的容器} container*/ const setWatermark (str, container) > {const id 1.23452384164.123412415…

计算机网络第一章:概述

一.因特网概述 1.1网络、互联网(互连网)和因特网 网络由若干结点和连接这些结点的链路组成&#xff0c;可以是有线链路&#xff0c;也可以是无线链路 互连网&#xff1a;多个网络通过路由器互连起来&#xff0c;这样就构成了一个覆盖范围更大的网络&#xff0c;即互连网。因此…

spring注解@Component、@controller、@service、@repository

使用之前需要在spring.xml配置文件中配置 只有扫描正确&#xff0c;且应标记注解的类正确标记过后&#xff0c;spring容器才会帮组我们创建并且管理bean对象 Component注解 作用&#xff1a;把普通pojo实例化到spring容器中&#xff0c;相当于之前xml配置文件中的 &#xff…

【数据结构】链表C++编写的,它定义了一个链表,并实现了一些基本的链表操作,如创建新节点、插入节点、清空链表、输出链表以及查找节点

// 引入标准输入输出流库&#xff0c;用于输出操作 #include <iostream> // 引入标准库中的stdlib&#xff0c;包含了rand()函数和其他相关函数 #include <cstdlib> // 引入标准库中的time&#xff0c;包含了time()函数和其他相关函数 #include <ctim…

【Linux】高级IO --- Reactor网络IO设计模式

人其实很难抵制诱惑&#xff0c;人只能远离诱惑&#xff0c;所以千万不要高看自己的定力。 文章目录 一、LT和ET模式1.理解LT和ET的工作原理2.通过代码来观察LT和ET工作模式的不同3.ET模式高效的原因&#xff08;fd必须是非阻塞的&#xff09;4.LT和ET模式使用时的读取方式 二…

正反向代理理解

正向代理&#xff08;Forward Proxy&#xff09;和反向代理&#xff08;Reverse Proxy&#xff09;是两种用于网络通信的代理服务器&#xff0c;它们分别用于不同的场景和目的。 正向代理&#xff08;Forward Proxy&#xff09;&#xff1a; 正向代理是位于客户端和目标服务器…

球谐函数在环境光照中的使用原理

在三维空间中如何对场景光照进行球谐函数展开 图形学论文解析与复现【Spherical Harmonic Lighting:The Gritty Details】 首先&#xff0c;对场景中某像素点的漫反射光照进行计算。 L ( p , w o ) ∫ Ω L ( w i ) n ⋅ w i d w i L(p,w_o) \int_{\Omega}L(w_i)n\cdot w_i…

cad打印样式丢失怎么处理?

一提到CAD软件&#xff0c;我相信很多朋友都特别熟悉&#xff0c;因为在工作中很多的图纸设计都有它的功劳&#xff0c;经常从事cad设计的朋友对于cad打印样式都非常地精通了&#xff0c;在打印样式里包括了图纸的颜色、线条等&#xff0c;由于各种原因cad打印样式丢失了&#…

自行实现字符串转浮点数函数atof()

【重复造轮子的原因】 尽管atof是标准C中自带的函数,用于将字符串转为浮点数,但是在某些环境下有可能没法使用的(例如CUDA环境中,没有atof函数,但是math.h可以使用),因此自行实现。 【通过的测试用例】 【实现的代码】 #include <stdio.h> #include <math.h…

linux(rhel7)内核参数优化

内核参数 Linux sysctl.d 配置内核参数 rhel7中sysctl.d和sysctl.conf的执行顺序 执行顺序&#xff1a; sysctl.d > /etc/sysctl.conf sysctl.d的执行顺序&#xff1a; /etc/sysctl.d/run/sysctl.d/usr/local/lib/sysctl.d/usr/lib/sysctl.d/lib/sysctl.d/ 对于不同目录下…

交换排序——冒泡排序、快速排序

交换排序就是通过比较交换实现排序。分冒泡排序和快速排序两种。 一、冒泡排序&#xff1a; 1、简述 顾名思义就是大的就冒头&#xff0c;换位置。 通过多次重复比较、交换相邻记录而实现排序&#xff1b;每一趟的效果都是将当前键值最大的记录换到最后。 冒泡排序算法的原…