Linux——多路复用之select

news2024/11/16 6:51:09

目录

前言

一、select的认识

二、select的接口

三、select的使用

四、select的优缺点


前言

在前面,我们学习了五种IO模型,对IO有了基本的认识,知道了select效率很高,可以等待多个文件描述符,那他是如何等待的呢?我们又该如何使用呢?

一、select的认识

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

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

select只负责等待,不负责拷贝,一次可以等待多个文件描述符。他的作用是让read和write不再阻塞

二、select的接口

select的调用接口如下

参数 1 int nfds:值最大的文件描述符+1。

参数 2 fd_set* readfds:fd_set本质是一张位图。代表select需要关心的读事件

参数 3 fd_set* writefds:代表select需要关心的读事件

参数 4 fd_set* execptfdsfds:代表select需要关心的异常事件,我们暂时不考虑

参数 5 struct timeval* timeout:时间结构体,成员有秒和微秒,代表等待的时间

                                                  {n,m}为阻塞等待n秒m微秒,时间结束后返回

                                                  {0,0}为非阻塞等待

                                                  nullptr为阻塞等待

参数2,3,4类似,都是输入输出型参数,参数5也是输入输出型参数,输出的是剩余时间

以readfds为例

输入时:比特位的位置,表示文件描述符的值,比特位的内容(0/1),用户关心内核,是否关心这个fd的读事件。

输出时:比特位的位置,表示文件描述符的值,比特位的内容(0/1),内核告诉用户,哪些文件fd上的读事件是否就绪

返回值:

  1. ret  >  0 :select等待的多个fd中,已经就需要的fd个数
  2. ret == 0 :select超时返回
  3. ret  <  0 :select出错

同时,fd_set 是特定的类型,我们对其赋值时,是不方便赋值的,因此库里面也给提供的一个函数,方便我们处理。

FD_CLR                    从文件描述符集合 set 中清除文件描述符 fd。

FD_ISSET                 检查文件描述符 fd 是否在文件描述符集合 set 中。

FD_SET                    将文件描述符 fd 添加到文件描述符集合 set 中。

FD_ZERO                 清空文件描述符集合 set,将其所有位都设置为零。

三、select的使用

Log.hpp

#pragma once

#include <iostream>
#include <cstdarg>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <pthread.h>
using namespace std;

enum
{
    Debug = 0,
    Info,
    Warning,
    Error,
    Fatal
};

enum
{
    Screen = 10,
    OneFile,
    ClassFile
};

string LevelToString(int level)
{
    switch (level)
    {
    case Debug:
        return "Debug";
    case Info:
        return "Info";
    case Warning:
        return "Warning";
    case Error:
        return "Error";
    case Fatal:
        return "Fatal";

    default:
        return "Unknown";
    }
}

const int default_style = Screen;
const string default_filename = "Log.";
const string logdir = "log";

class Log
{
public:
    Log(int style = default_style, string filename = default_filename)
        : _style(style), _filename(filename)
    {
        if (_style != Screen)
            mkdir(logdir.c_str(), 0775);
    }

    // 更改打印方式
    void Enable(int style)
    {
        _style = style;
        if (_style != Screen)
            mkdir(logdir.c_str(), 0775);
    }

    // 时间戳转化为年月日时分秒
    string GetTime()
    {
        time_t currtime = time(nullptr);
        struct tm *curr = localtime(&currtime);
        char time_buffer[128];
        snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",
                 curr->tm_year + 1900, curr->tm_mon + 1, curr->tm_mday, curr->tm_hour, curr->tm_min, curr->tm_sec);
        return time_buffer;
    }

    // 写入到文件中
    void WriteLogToOneFile(const string &logname, const string &message)
    {
        FILE *fp = fopen(logname.c_str(), "a");
        if (fp == nullptr)
        {
            perror("fopen failed");
            exit(-1);
        }
        fprintf(fp, "%s\n", message.c_str());

        fclose(fp);
    }

    // 打印日志
    void WriteLogToClassFile(const string &levelstr, const string &message)
    {
        string logname = logdir;
        logname += "/";
        logname += _filename;
        logname += levelstr;
        WriteLogToOneFile(logname, message);
    }

    pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    void WriteLog(const string &levelstr, const string &message)
    {
        pthread_mutex_lock(&lock);
        switch (_style)
        {
        case Screen:
            cout << message << endl; // 打印到屏幕中
            break;
        case OneFile:
            WriteLogToClassFile("all", message); // 给定all,直接写到all里
            break;
        case ClassFile:
            WriteLogToClassFile(levelstr, message); // 写入levelstr里
            break;
        default:
            break;
        }
        pthread_mutex_unlock(&lock);
    }

    // 提供接口给运算符重载使用
    void _LogMessage(int level, const char *file, int line, char *rightbuffer)
    {
        char leftbuffer[1024];
        string levelstr = LevelToString(level);
        string currtime = GetTime();
        string  idstr = to_string(getpid());

        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%s][%s][%s:%d]", levelstr.c_str(), currtime.c_str(), idstr.c_str(), file, line);

        string messages = leftbuffer;
        messages += rightbuffer;
        WriteLog(levelstr, messages);
    }

    // 运算符重载
    void operator()(int level, const char *file, int line, const char *format, ...)
    {
        char rightbuffer[1024];
        va_list args;                                              // va_list 是指针
        va_start(args, format);                                    // 初始化va_list对象,format是最后一个确定的参数
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, args); // 写入到rightbuffer中
        va_end(args);
        _LogMessage(level, file, line, rightbuffer);
    }

    ~Log()
    {
    }

private:
    int _style;
    string _filename;
};

Log lg;

class Conf
{
public:
    Conf()
    {
        lg.Enable(Screen);
    }
    ~Conf()
    {
    }
};

Conf conf;

// 辅助宏
#define lg(level, format, ...) lg(level, __FILE__, __LINE__, format, ##__VA_ARGS__)

Socket.hpp 

#pragma once

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <unistd.h>
using namespace std;
namespace Net_Work
{
    static const int default_backlog = 5;
    static const int default_sockfd = -1;
    using namespace std;

    enum
    {
        SocketError = 1,
        BindError,
        ListenError,
        ConnectError,
    };

    // 封装套接字接口基类
    class Socket
    {
    public:
        // 封装了socket相关方法
        virtual ~Socket() {}
        virtual void CreateSocket() = 0;
        virtual void BindSocket(uint16_t port) = 0;
        virtual void ListenSocket(int backlog) = 0;
        virtual bool ConnectSocket(string &serverip, uint16_t serverport) = 0;
        virtual Socket *AcceptSocket(string *peerip, uint16_t *peerport) = 0;
        virtual int GetSockFd() = 0;
        virtual void SetSockFd(int sockfd) = 0;
        virtual void CloseSocket() = 0;
        virtual bool Recv(string *buff, int size) = 0;
        virtual void Send(string &send_string) = 0;

        // 方法的集中在一起使用
    public:
        void BuildListenSocket(uint16_t port, int backlog = default_backlog)
        {
            CreateSocket();
            BindSocket(port);
            ListenSocket(backlog);
        }

        bool BuildConnectSocket(string &serverip, uint16_t serverport)
        {
            CreateSocket();
            return ConnectSocket(serverip, serverport);
        }

        void BuildNormalSocket(int sockfd)
        {
            SetSockFd(sockfd);
        }
    };

    class TcpSocket : public Socket
    {
    public:
        TcpSocket(int sockfd = default_sockfd)
            : _sockfd(sockfd)
        {
        }
        ~TcpSocket() {}

        void CreateSocket() override
        {
            _sockfd = socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd < 0)
                exit(SocketError);
        }
        void BindSocket(uint16_t port) override
        {
            int opt = 1;
            setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));

            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;

            int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
            if (n < 0)
                exit(BindError);
        }
        void ListenSocket(int backlog) override
        {
            int n = listen(_sockfd, backlog);
            if (n < 0)
                exit(ListenError);
        }
        bool ConnectSocket(string &serverip, uint16_t serverport) override
        {
            struct sockaddr_in addr;
            memset(&addr, 0, sizeof(addr));
            addr.sin_family = AF_INET;
            addr.sin_port = htons(serverport);
            // addr.sin_addr.s_addr = inet_addr(serverip.c_str());
            inet_pton(AF_INET, serverip.c_str(), &addr.sin_addr);
            int n = connect(_sockfd, (sockaddr *)&addr, sizeof(addr));

            if (n == 0)
                return true;
            return false;
        }
        Socket *AcceptSocket(string *peerip, uint16_t *peerport) override
        {
            struct sockaddr_in addr;
            socklen_t len = sizeof(addr);
            int newsockfd = accept(_sockfd, (sockaddr *)&addr, &len);
            if (newsockfd < 0)
                return nullptr;

            // *peerip = inet_ntoa(addr.sin_addr);

            // INET_ADDRSTRLEN 是一个定义在头文件中的宏,表示 IPv4 地址的最大长度
            char ip_str[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, &addr.sin_addr, ip_str, INET_ADDRSTRLEN);
            *peerip = ip_str;

            *peerport = ntohs(addr.sin_port);

            Socket *s = new TcpSocket(newsockfd);
            return s;
        }
        int GetSockFd() override
        {
            return _sockfd;
        }
        void SetSockFd(int sockfd) override
        {
            _sockfd = sockfd;
        }
        void CloseSocket() override
        {
            if (_sockfd > default_sockfd)
                close(_sockfd);
        }

        bool Recv(string *buff, int size) override
        {
            char inbuffer[size];
            ssize_t n = recv(_sockfd, inbuffer, size - 1, 0);
            if (n > 0)
            {
                inbuffer[n] = 0;
                *buff += inbuffer;
                return true;
            }
            else
                return false;
        }

        void Send(string &send_string) override
        {
            send(_sockfd, send_string.c_str(),send_string.size(),0);
        }

    private:
        int _sockfd;
        string _ip;
        uint16_t _port;
    };
}

        select只负责等待,不负责处理,最初我们有一个listen_sock需要交给select去管理,当有新链接到来是,listen_sock要去接受新链接,但是接受后,不能立刻read或者write,因为不确定当前事件是否就绪,需要将新链接也交给select管理

        如何将新链接交给select呢?我们得有一个数据结构(这里用的数组),把所有的fd都管理起来,新链接到来时,都可以往这个数组里面添加文件描述符fd。后面select遍历数组,就可以找到需要管理的fd了,但这样,我们需要经常遍历这个数组

  1. 添加时需要遍历找到空再插入
  2. select传参,需要遍历查找最大的文件描述符
  3. select等待成功后调用处理函数时,也需遍历查找就绪的文件描述符

        同时,由于select的事件参数是一个输入输出型参数,因此我们每次都得重新对该参数重新赋值。

如下是SelectServer.hpp的核心代码 

SelectServer.hpp

#pragma once
#include <iostream>
#include <string>
#include <sys/select.h>
#include "Log.hpp"
#include "Socket.hpp"

using namespace Net_Work;
const static int gdefaultport = 8888;
const static int gbacklog = 8;
const static int num = sizeof(fd_set) * 8;

class SelectServer
{
public:
    SelectServer(int port) : _port(port), _listensock(new TcpSocket())
    {
    }
    void HandlerEvent(fd_set rfds)
    {
        for (int i = 0; i < num; i++)
        {
            if (_rfds_array[i] == nullptr)
                continue;

            int fd = _rfds_array[i]->GetSockFd();
            // 判断事件是否就绪
            if (FD_ISSET(fd, &rfds))
            {
                // 读事件分两类,一类是新链接到来,一类是新数据到来
                if (fd == _listensock->GetSockFd())
                {
                    // 新链接到来
                    lg(Info, "get a new link");
                    // 获取连接
                    std::string clientip;
                    uint16_t clientport;
                    Socket *sock = _listensock->AcceptSocket(&clientip, &clientport);
                    if (!sock)
                    {
                        lg(Error, "accept error");
                        return;
                    }
                    lg(Info, "get a client,client info is# %s:%d,fd: %d", clientip.c_str(), clientport, sock->GetSockFd());
                    // 此时获取连接成功了,但是不能直接read write,sockfd仍需要交给select托管 -- 添加到数组_rfds_array中
                    int pos = 0;
                    for (; pos < num; pos++)
                    {
                        if (_rfds_array[pos] == nullptr)
                        {
                            _rfds_array[pos] = sock;
                            lg(Info, "get a new link, fd is : %d", sock->GetSockFd());
                            break;
                        }
                    }
                    if (pos == num)
                    {
                        sock->CloseSocket();
                        delete sock;
                        lg(Warning, "server is full, be carefull...");
                    }
                }
                else
                {
                    // 普通的读事件就绪
                    std::string buffer;
                    bool res = _rfds_array[i]->Recv(&buffer, 1024);
                    if (res)
                    {
                        lg(Info,"client say# %s",buffer.c_str());
                        buffer+=": 你好呀,同志\n";
                        _rfds_array[i]->Send(buffer);
                        buffer.clear();
                    }
                    else
                    {
                        lg(Warning,"client quit ,maybe close or error,close fd: %d",fd);
                        _rfds_array[i]->CloseSocket();
                        delete _rfds_array[i];
                        _rfds_array[i] = nullptr;
                    }
                }
            }
        }
    }
    void InitServer()
    {
        _listensock->BuildListenSocket(_port, gbacklog);
        for (int i = 0; i < num; i++)
        {
            _rfds_array[i] = nullptr;
        }
        _rfds_array[0] = _listensock.get();
    }

    void Loop()
    {
        _isrunning = true;
        // 循环重置select需要的rfds
        while (_isrunning)
        {
            // 不能直接获取新链接,因为accpet可能阻塞
            // 所有的fd,都要交给select,listensock上面新链接,相当于读事件
            // 因此需要将listensock交给select

            // 遍历数组, 1.找最大的fd  2. 合法的fd添加到rfds集合中
            fd_set rfds;
            FD_ZERO(&rfds);
            int max_fd = _listensock->GetSockFd();
            for (int i = 0; i < num; i++)
            {
                if (_rfds_array[i] == nullptr)
                {
                    continue;
                }
                else
                {
                    // 添加fd到集合中
                    int fd = _rfds_array[i]->GetSockFd();
                    FD_SET(fd, &rfds);
                    if (max_fd < fd) // 更新最大值
                    {
                        max_fd = fd;
                    }
                }
            }

            // 定义时间
            struct timeval timeout = {0, 0};

            PrintDebug();

            // rfds是输入输出型参数,rfds是在select调用返回时,不断被修改,所以每次需要重置rfds
            int n = select(max_fd + 1, &rfds, nullptr, nullptr, /*&timeout*/ nullptr);
            switch (n)
            {
            case 0:
                lg(Info, "select timeout...,last time: %u.%u", timeout.tv_sec, timeout.tv_usec);
                break;
            case -1:
                lg(Error, "select error!!!");
            default:
                // 正常就绪的fd
                lg(Info, "select success,begin event handler,last time: %u.%u", timeout.tv_sec, timeout.tv_usec);
                HandlerEvent(rfds); 
                break;
            }
        }
        _isrunning = false;
    }

    void Stop()
    {
        _isrunning = false;
    }

    void PrintDebug()
    {
        std::cout << "current select rfds list is :";
        for (int i = 0; i < num; i++)
        {
            if (_rfds_array[i] == nullptr)
                continue;
            else
                std::cout << _rfds_array[i]->GetSockFd() << " ";
        }
        std::cout << std::endl;
    }

private:
    std::unique_ptr<Socket> _listensock;
    int _port;
    bool _isrunning;

    // select 服务器要被正确设计,需要程序员定义数据结构,来吧所有的fd管理起来
    Socket *_rfds_array[num];
};

 Main.cc

#include <iostream>
#include <memory>
#include "SelectServer.hpp"

void Usage(char* argv)
{
    
    std::cout<<"Usage: \n\t"<<argv<<" port\n"<<std::endl;
}
// ./select_server 8080
int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        return -1;
    }
    uint16_t localport = std::stoi(argv[1]);
    std::unique_ptr<SelectServer> svr = std::make_unique<SelectServer>(localport);
    svr->InitServer();
    svr->Loop();

    return 0;
}

四、select的优缺点

优点:select只负责等待,可以等待多个fd,IO的时候,效率会比较高一些。

缺点:

  1. 由于select是输入输出型参数,因此我们每次都要对select的参数重新设置。
  2. 编写代码时,select因为要使用第三方数组,充满了遍历,这可能会影响select的效率。
  3. 用户到内核,内核到用户,每次select调用和返回,都要对位图重新设置,用户和内核之间,要一直进行数据拷贝。
  4. select让OS在底层遍历需要关心所有的fd,这也会造成效率低下,这也是为何第一个参数需要传入max_fd + 1,就是因为select的底层需要遍历。
  5. fd_set 是系统提供的类型,fd_set大小是固定的,就意味着位图的个数是固定的,也就是select最多能够检测到fd的总数是有上限的。

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

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

相关文章

JavaScript 获取 url(get)参数

https://andi.cn/page/621584.html

Gitee 使用教程1-SSH 公钥设置

一、生成 SSH 公钥 1、打开终端&#xff08;Windows PowerShell 或 Git Bash&#xff09;&#xff0c;通过命令 ssh-keygen 生成 SSH Key&#xff1a; ssh-keygen -t ed25519 -C "Gitee SSH Key" 随后摁三次回车键&#xff08;Enter&#xff09; 2、查看生成的 SSH…

大鲸鱼docker-compose单机容器集群编排工具

目录 一、Docker-compose 概述 二、Docker-compose简介 三、YML文件格式及编写注意事项 1.yml文件是什么 2.yml问价使用注意事项 3.yml文件的基本数据结构 四、Docker-compose 配置 1.Docker-Compose 配置常用字段 2.Docker Compose常用命令 3.使用Docker-compose创建…

Python解释器:CPython 解释器

一、什么是python解释器 Python解释器是一种用于执行Python代码的程序。 它将Python源代码转换为机器语言或字节码&#xff0c;从而使计算机能够执行。 1.1 Python解释器分类 1、CPython CPython 是 Python 的主要实现&#xff0c;由 C 语言编写。大多数用户在日常开发中使…

django实现用户的注册、登录、注销功能

创建django项目的步骤&#xff1a;Django项目的创建步骤-CSDN博客 一、前置工作 配置数据库&#xff0c;设置数据库引擎为mysql 1、在settings文件中找到DATABASES, 配置以下内容 DATABASES {"default": {ENGINE: django.db.backends.mysql, # 数据库引擎NAME: dja…

色彩与故乡的对话 —— 钱华个人油画展正式开展

色彩与故乡的对话 —— 钱华个人油画展正式开展 2024年7月17日 &#xff0c;在宁波这座历史与现代交织的城市里&#xff0c;艺术与文化的碰撞再次绽放出耀眼的光芒。由宁波海曙区美术家协会主办&#xff0c;宁波市海纳广场开发经营有限公司协办的“色彩与故乡的对话——钱华个人…

【SpringBoot Web开发之静态资源访问】笔记

详细内容见官方文档&#xff1a;Static Content SpringBoot Web开发之静态资源访问 1.准备工作&#xff1a;创建WebDemo2.静态资源目录2.1官网原文2.2静态资源目录第一步&#xff1a;依照上面2.1官网原文中创建如下目录第二步&#xff1a;复制粘贴图片到静态资源目录中第三步…

二叉树的前、中、后序遍历(递归法、迭代法)leetcode144/94/145

leetcode144、二叉树的前序遍历 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,2,3] 示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[] 示例 3&#xff1a;…

海外媒体发稿-瑞典SEO破茧成蝶:从0到10的实战精要-大舍传媒

海外媒体发稿-瑞典SEO破茧成蝶:从0到10的实战精要 一、迷茫与意义的探寻 有一天我找了王老师聊天&#xff0c;谈到生活迷茫和人生的意义。老师说了一段话&#xff1a;当全情投入于一件事情时&#xff0c;是没有时间去迷茫或思索人生意义的。我感触很深&#xff0c;当总感到迷…

UI设计中的响应式布局策略:让您的界面在各种设备上都表现出色

UI界面设计它是人与机器之间交互的媒介&#xff0c;也是客户体验的媒介&#xff08;UX&#xff09;一个组成部分。操作界面由两个主要部分组成&#xff1a;视觉设计&#xff08;即传达产品的外观和感觉&#xff09;和交互设计&#xff08;即元素功能和逻辑组织&#xff09;。用…

自定义注解 + Redis 实现业务的幂等性

1.实现幂等性思路 实现幂等性有两种方式&#xff1a; ⭐ 1. 在数据库层面进行幂等性处理&#xff08;数据库添加唯一约束&#xff09;. 例如&#xff1a;新增用户幂等性处理&#xff0c;username 字段可以添加唯一约束. ⭐ 2. 在应用程序层面进行幂等性处理. 而在应用程序…

Go语言并发编程-Channel通信_2

Channel通信 Channel概述 不要通过共享内存的方式进行通信&#xff0c;而是应该通过通信的方式共享内存 这是Go语言最核心的设计模式之一。 在很多主流的编程语言中&#xff0c;多个线程传递数据的方式一般都是共享内存&#xff0c;而Go语言中多Goroutine通信的主要方案是Cha…

人工智能算法工程师(高级)课程1-单类目标识别之人脸检测识别技术MTCNN模型介绍与代码详解

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能算法工程师(高级)课程1-单类目标识别之人脸检测识别技术MTCNN模型介绍与代码详解。本文深入探讨了基于PyTorch的人脸检测与识别技术&#xff0c;详细介绍了MTCNN模型、Siamese network以及center loss、sof…

趣谈linux操作系统 9 网络系统-读书笔记

文章目录 网络协议栈基础知识回顾网络分层网络分层的目的各层作用简介延伸-ip地址,有类,无类,cidr socket实现分析tcp/udp回顾socket编程回顾TCP编程回顾UDP编程回顾差异 socket相关接口实现浅析sokcet实现解析创建socket的三个参数socket函数定义及其参数创建socket结构体关联…

element UI :el-table横向列内容超出宽度,滚动条不显示问题

是否能解决你问题的前提 **看到这篇文章的解决问题的方案之前&#xff0c;请先回忆你是否在项目中的全局样式或者私有组件中去单独设置过滚动条样式。如果有 请继续往下看&#xff1a;**单独设置过滚动条样式代码实例&#xff1a; ::-webkit-scrollbar {/*滚动条整体样式*/wi…

STM32智能家居电力管理系统教程

目录 引言环境准备智能家居电力管理系统基础代码实现&#xff1a;实现智能家居电力管理系统 4.1 数据采集模块 4.2 数据处理与控制模块 4.3 通信与网络系统实现 4.4 用户界面与数据可视化应用场景&#xff1a;电力管理与优化问题解决方案与优化收尾与总结 1. 引言 智能家居电…

代码随想录——最后一块石头的重量(Leetcode1046)

题目链接 class Solution {public int lastStoneWeight(int[] stones) {int len stones.length;if(len 1){return stones[0];}if(len 2){return Math.abs(stones[0] - stones[1]);}while(true){Arrays.sort(stones);if(stones[len - 2] 0){break;}stones[len - 1] stone…

一维差分的实现

这是C算法基础-基础算法专栏的第十三篇文章&#xff0c;专栏详情请见此处。 引入 上次我们学习了前缀和的实现&#xff0c;它可以快速解决求区间和问题。这次我们要学习差分&#xff0c;它是前缀和的逆运算&#xff0c;可以快速解决对序列的一个区间同时加或减一个数这样的问题…

LabVIEW在CRIO中串口通讯数据异常问题

排查与解决步骤 检查硬件连接&#xff1a; 确保CRIO的串口模块正确连接&#xff0c;并且电缆无损坏。 确认串口模块在CRIO中被正确识别和配置。 验证串口配置&#xff1a; 在LabVIEW项目中&#xff0c;检查CRIO目标下的串口配置&#xff0c;确保波特率、数据位、停止位和校验…

用杰理芯片发不出100hz~1200hz频率的PWM波

思路&#xff1a; 问原厂工程师 回复&#xff1a; 看下是不是数据做除法的时候越界了&#xff0c;如果有用户手册&#xff0c;直接看下那几个定时器的寄存器算下就知道为什么 芯片&#xff1a; AD155A 行动&#xff1a; 相关文档和代码&#xff1a; TMR_PR&#xff1a;1…