【计算机网络】五种IO模型与IO多路转接之select

文章目录

  • 一、五种IO模型
  • 二、非阻塞IO
    • 1.fcntl
    • 2.实现函数SetNoBlock
    • 3.轮询方式读取标准输入
  • 三、I/O多路转接之select
    • 1.初识select
    • 2.select函数原型
    • 3.socket就绪条件
    • 4.select的特点
    • 5.select缺点
    • 6.select使用案例--只读取数据的server服务器
      • 1.err.hpp
      • 2.log.hpp
      • 3.sock.hpp
      • 4.select_server.hpp
      • 5.main.cc

一、五种IO模型

什么是IO?什么是高效的IO?

我们知道,我们调用read/recv的时候,如果缓冲区中没有数据,那么就会阻塞住,有数据的时候,read/recv会进行拷贝,完成之后返回。所以 IO = 等 + 数据拷贝

那么如何让IO更加的高效呢,只要减少等待的比重即可。

我们以几个人钓鱼为例来说明五种IO模型

张三:钓鱼的时候一直盯着鱼竿,不做任何其他事情

李四:钓鱼的时候,偶尔看看书,看看手机

王五:在鱼竿上方一个铃铛,铃铛响了就说明有鱼,在铃铛没有的时间内,王五也看看书,看看手机

赵六:同时投放了多个鱼竿,来回进行检测看哪个鱼竿上有鱼

田七和小王:田七是一个老板,想吃鱼,就让小王去钓,钓满一桶之后,小王联系田七,田七来取鱼

对于上面的几种钓鱼方式,鱼就是数据,河为内核空间,鱼漂:鱼就绪,数据就绪事件,鱼竿:文件描述符

钓鱼的动作:read/recv调用

张三是阻塞式IO,李四是非阻塞式IO,王五是信号驱动式IO,赵六是多路转接/多路复用,田七是异步IO

张三李四王五在效率上没有差别,但是李四和王五可以做其他的事情,张三李四王五赵六每个人都等了,每个人都参与了IO的过程,称为同步IO。田七没有参与IO的两个阶段的任何一个阶段,称为异步IO

在我们看来赵六的钓鱼效率是最高的,因为他等的比重比较低,单位时间内钓鱼的效率就高

为什么多路转接/多路复用是高效的代名词:IO = 等(减少等的比重) + 拷贝

阻塞IO是最常见的IO模型

在这里插入图片描述

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

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

同步通信vs异步通信(synchronous communication/ asynchronouscommunication)

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

所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回. 但是一旦调用返回,就得到返回值了; 换句话说,就是由调用者主动等待这个调用的结果;

异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果; 换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果; 而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用.

另外, 我们回忆在讲多进程多线程的时候, 也提到同步和互斥. 这里的同步通信和进程之间的同步是完全不想干的概念

进程/线程同步也是进程/线程之间直接的制约关系

是为完成某种任务而建立的两个或多个线程,这个线程需要在某些位置上协调他们的工作次序而等待、传递信息所产生的制约关系. 尤其是在访问临界资源的时候

阻塞 vs 非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态

阻塞调用是指调用结果返回之前,当前线程会被挂起. 调用线程只有在得到结果之后才会返回.

非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程

其他高级IO

非阻塞IO,纪录锁,系统V流机制,I/O多路转接(也叫I/O多路复用),readv和writev函数以及存储映射IO(mmap),这些统称为高级IO

二、非阻塞IO

1.fcntl

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

函数原型如下

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );

传入的cmd的值不同, 后面追加的参数也不相同.

fcntl函数有5种功能:

复制一个现有的描述符(cmd=F_DUPFD).

获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).

获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).

获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).

获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW)

我们此处只是用第三种功能, 获取/设置文件状态标记, 就可以将一个文件描述符设置为非阻塞

2.实现函数SetNoBlock

基于fcntl, 我们实现一个SetNoBlock函数, 将文件描述符设置为非阻塞

void SetNoBlock(int fd)
{ 
    int fl = fcntl(fd, F_GETFL); 
	if (fl < 0)
    { 
 		perror("fcntl");
 		return; 
 	}
 	fcntl(fd, F_SETFL, fl | O_NONBLOCK); 
}

使用F_GETFL将当前的文件描述符的属性取出来(这是一个位图).

然后再使用F_SETFL将文件描述符设置回去. 设置回去的同时, 加上一个O_NONBLOCK参数.

3.轮询方式读取标准输入

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

void SetNonBlock(int fd)
{
    int fl = fcntl(fd, F_GETFL);
    if (fl < 0)
    {
        perror("fcntl");
        return;
    }
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}

int main()
{
    SetNonBlock(0);
    while (true)
    {
        char buffer[1024];
        ssize_t s = read(0, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            std::cout << buffer << std::endl;
        }
        else
        {
            perror("read");
            sleep(1);
            continue;
        }
    }
    return 0;
}

三、I/O多路转接之select

1.初识select

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

select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;

程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变

select : IO = 等 + 拷贝

select 只负责等待,可以一次等待多个fd,select本身没有数据拷贝的能力,拷贝要read,write来完成

2.select函数原型

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

参数解释:

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

readfds,writefds,exceptfds分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描述符的集合;

参数timeout为结构timeval,用来设置select()的等待时间

fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout都是输入输出型参数

参数timeout取值:

NULL:则表示select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件;

0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。

特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回。

关于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的全部位

我们以读事件为例来说明readfds,writefds,exceptfds

作为输入时:表示用户告诉内核,你要帮我关心一下,我给你的集合中的所有的fd的读事件–哪些fd上的读事件内核你要关心,比特位的位置,表示fd的数值,比特位的内容,表示是否关心

作为输出时:内核告诉用户,你所关心的多个fd中,有哪些已经就绪了。比特位的位置,表示fd的数值,比特位的内容,表示哪些fd上面的读事件已经就绪了

这样让用户和内核之间相互沟通,互相知晓对方要的或者关心的。

fd_set是一种类型,既然是一种类型,必定有大小,而且是固定的,所以能够添加的fd的个数也是有上限的

关于timeval结构

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

在这里插入图片描述

struct timaval
{
    time_t tv_sec;  /* seconds */
    suseconds_t tv_usec; /* microseconds */
};

传输参数:
nullptr : 阻塞式
struct timeval timeout = {0,0}; 非阻塞
struct timeval timeout = {5,0};
5s以内阻塞式,超过5s,非阻塞返回一次

函数返回值:

执行成功则返回文件描述词状态已改变的个数

如果返回0代表在描述词状态改变前已超过timeout时间,没有返回

当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds, exceptfds和timeout的值变成不可预测。

ret > 0 : 有几个fd就绪了

ret == 0 : 超时返回了

ret < 0 : select 调用失败了

错误值可能为:

EBADF 文件描述词为无效的或该文件已关闭

EINTR 此调用被信号所中断

EINVAL 参数n 为负值。

ENOMEM 核心内存不足

3.socket就绪条件

读就绪

socket内核中, 接收缓冲区中的字节数, 大于等于低水位标记SO_RCVLOWAT. 此时可以无阻塞的读该文件描述符, 并且返回值大于0;

socket TCP通信中, 对端关闭连接, 此时对该socket读, 则返回0;

监听的socket上有新的连接请求;

socket上有未处理的错误;

写就绪

socket内核中, 发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小), 大于等于低水位标记

SO_SNDLOWAT, 此时可以无阻塞的写, 并且返回值大于0;

socket的写操作被关闭(close或者shutdown). 对一个写操作被关闭的socket进行写操作, 会触发SIGPIPE信号;

socket使用非阻塞connect连接成功或失败之后;

socket上有未读取的错误;

异常就绪

socket上收到带外数据. 关于带外数据, 和TCP紧急模式相关(TCP协议头中, 有一个紧急指针的字段),

4.select的特点

1.可监控的文件描述符个数取决与sizeof(fd_set)的值. 我这边服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。即select能同时等待的文件fd是有上限的,除非重新改变内核,否则无法解决。

2.将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,

2.1一是用于再select 返回后,array作为源数据和fd_set进行FD_ISSET判断。即必须借助第三方数组,来维护合法的fd

2.2二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。select第一个参数为什么是最大fd+1呢?确定遍历范围 – 内核层面

3.select的大部分参数是输入输出型的,调用select前,要重新设置所有的fd,调用之后,我们还要检查更新所有的fd,这就带来遍历成本

4.select 采用位图,用户 -> 内核,内核 -> 用户,来回的进行数据拷贝,拷贝成本问题

备注: fd_set的大小可以调整,可能涉及到重新编译内核

5.select缺点

每次调用select, 都需要手动设置fd集合, 从接口使用角度来说也非常不便.

每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大

同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大

select支持的文件描述符数量太小,有上限

6.select使用案例–只读取数据的server服务器

1.err.hpp

#pragma once

enum
{
    USAGE_ERR = 1,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR
};

2.log.hpp

#pragma once

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <stdarg.h>

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

#define LOG_NORMAL "./log.txt"
#define LOG_ERR "./err.txt"

#define NUM 1024

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, ...)
{
    // [日志等级] [时间戳/时间] [pid] [messge]
    char logprofix[NUM];
    snprintf(logprofix, sizeof logprofix, "[%s][%ld][pid:%d]", to_levelstr(level), (long int)time(nullptr), getpid());

    char logcontent[NUM];
    va_list arg;
    va_start(arg, format);

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

    std::cout << logprofix << logcontent << std::endl;

    FILE *log = fopen(LOG_NORMAL, "a");
    FILE *error = fopen(LOG_ERR, "a");

    if (log && error)
    {
        FILE *cur = nullptr;
        if (level == DEBUG || level == NORMAL || level == WARNING)
            cur = log;
        if (level == ERROR || level == FATAL)
            cur = error;
        if (cur)
            fprintf(cur, "%s%s\n", logprofix, logcontent);

        fclose(log);
        fclose(error);
    }
}

3.sock.hpp

#pragma once

#include <iostream>
#include <cstring>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "log.hpp"
#include "err.hpp"

class Sock
{
    static const int backlog = 32;

public:
    // 1. 创建socket文件套接字对象
    static int Socket()
    {
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        if (sock < 0)
        {
            LogMessage(FATAL, "create socket error");
            exit(SOCKET_ERR);
        }
        LogMessage(NORMAL, "create socket success:%d", sock);
		
        // 允许地址重用,使得在套接字关闭后,该套接字所使用的地址可以立即被其他套接字使用
        int opt = 1;
        setsockopt(sock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof opt);

        return sock;
    }

    // 2.bind自己的网络信息
    static void Bind(int sock, const uint16_t &port)
    {
        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(sock, (struct sockaddr *)&local, sizeof local);
        if (n < 0)
        {
            LogMessage(FATAL, "socket bind error");
            exit(BIND_ERR);
        }
        LogMessage(NORMAL, "socket bind success");
    }

    // 3. 设置socket 为监听状态
    static void Listen(int sock)
    {
        int n = listen(sock, backlog);
        if (n < 0)
        {
            LogMessage(FATAL, "socket listen error");
            exit(LISTEN_ERR);
        }
        LogMessage(NORMAL, "socket listen success");
    }
	
    // 4.获取连接
    static int Accept(int listensock, std::string *clientip, uint16_t *clientport)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);

        int sock = accept(listensock, (struct sockaddr *)&peer, &len);
        if (sock < 0)
        {
            LogMessage(ERROR, "socket accept error,next");
        }
        else
        {
            LogMessage(NORMAL, "accept a new link success, get new sock: %d", sock);
            *clientip = inet_ntoa(peer.sin_addr);
            *clientport = ntohs(peer.sin_port);
        }

        return sock;
    }
};

4.select_server.hpp

#pragma once

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cerrno>
#include <string>
#include <functional>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/select.h>

#include "sock.hpp"
#include "log.hpp"
#include "err.h"

namespace select_ns
{
    const static int defaultport = 8080;
    const static int fdnum = sizeof(fd_set) * 8;
    const static int defaultfd = -1;

    typedef std::function<std::string(std::string)> func_t;

    class selectServer
    {
    public:
        selectServer(const func_t &func, const uint16_t &port = defaultport)
            : _port(port), _func(func), _listensock(-1), _fdarray(nullptr)
        {
        }

        void initServer()
        {
            _listensock = Sock::Socket();
            Sock::Bind(_listensock, _port);
            Sock::Listen(_listensock);

            _fdarray = new int[fdnum];

            for (int i = 0; i < fdnum; i++)
                _fdarray[i] = defaultfd;
            _fdarray[0] = _listensock;
        }
        void Print()
        {
            std::cout << "fd list: ";
            for (int i = 0; i < fdnum; i++)
            {
                if (_fdarray[i] != defaultfd)
                {
                    std::cout << _fdarray[i] << " ";
                }
            }
        }
        void Accepter(int listensock)
        {
            std::string clientip;
            uint16_t clientport;
            int sock = Sock::Accept(listensock, &clientip, &clientport);

            if (sock < 0)
                return;
            LogMessage(NORMAL, "accept success[%s:%d]", clientip.c_str(), clientport);

            // sock我们能直接recv/read 吗?不能,整个代码,只有select有资格检测事件是否就绪
            // 将新的sock 托管给select!
            // 将新的sock托管给select的本质,其实就是将sock,添加到fdarray数组中即可!
            int i = 0;
            for (; i < fdnum; i++)
            {
                if (_fdarray[i] != defaultfd)
                    continue;
                else
                    break;
            }
            if (i == fdnum)
            {
                LogMessage(WARNING, "server is full,please wait");
                close(sock);
            }
            else
                _fdarray[i] = sock;
            Print();
        }
        void Recver(int sock, int pos)
        {
            // 1. 读取request
            char buffer[1024];
            ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);
            if (s > 0)
            {
                buffer[s] = 0;
                LogMessage(NORMAL, "client#: %s", buffer);
            }
            else if (s == 0)
            {
                close(sock);
                _fdarray[pos] = defaultfd;
                LogMessage(NORMAL, "client quit");
                return;
            }
            else
            {
                close(sock);
                _fdarray[pos] = defaultfd;
                LogMessage(ERROR, "client error:%s", strerror(errno));
                return;
            }

            // 2. 处理request
            std::string response = _func(buffer);

            // 3. 返回response
            write(sock, response.c_str(), response.size());
        }

        void HandlerEvent(fd_set &rfds)
        {
            for (int i = 0; i < fdnum; i++)
            {
                if (_fdarray[i] == defaultfd)
                    continue;

                if (FD_ISSET(_fdarray[i], &rfds) && _fdarray[i] == _listensock)
                    Accepter(_listensock);
                else if (FD_ISSET(_fdarray[i], &rfds))
                    Recver(_fdarray[i], i);
                else
                {
                }
            }
        }

        void start()
        {
            for (;;)
            {
                fd_set rfds;
                FD_ZERO(&rfds);
                int maxfd = _fdarray[0];

                for (int i = 0; i < fdnum; i++)
                {
                    if (_fdarray[i] == defaultfd)
                        continue;
                    FD_SET(_fdarray[i], &rfds); // 合法 fd 全部添加到读文件描述符集中

                    if (maxfd < _fdarray[i])
                        maxfd = _fdarray[i]; // 更新所有fd中最大的fd
                }

                int n = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr);
                switch (n)
                {
                case 0:
                    LogMessage(NORMAL, "timeout...");
                    break;
                case -1:
                    LogMessage(WARNING, "select error,code: %d, err string: %s", errno, strerror(errno));
                    break;
                default:
                    // 说明有事件就绪了,目前只有一个监听事件就绪了
                    LogMessage(NORMAL, "have event ready!");
                    HandlerEvent(rfds);
                    break;
                }
            }
        }
        ~selectServer()
        {
            if (_listensock > 0)
                close(_listensock);
            if (_fdarray)
                delete[] _fdarray;
        }

    private:
        int _port;
        int _listensock;
        int *_fdarray;
        func_t _func;
    };
}

5.main.cc

#include "selectServer.hpp"
#include "err.hpp"
#include <memory>

using namespace std;
using namespace select_ns;

static void Usage(const string proc)
{
    std::cerr << "Usage:\n\t" << proc << " port\n\n";
}

string transaction(const string &request)
{
    return request;
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }

    // std::unique_ptr<selectServer> svr(new selectServer(transaction,atoi(argv[0])));
    std::unique_ptr<selectServer> svr(new selectServer(transaction));
    svr->initServer();
    svr->start();

    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

游泳耳机哪种款式好?简单6招教你选到高品质机型!

游泳是一项全身性的运动&#xff0c;不仅能帮助我们保持身体健康&#xff0c;还能让我们在水中放松心情&#xff0c;享受水中的乐趣。而音乐则是人们生活中不可或缺的一部分&#xff0c;它能带给我们快乐和力量。当游泳与音乐相结合&#xff0c;游泳耳机应运而生&#xff0c;为…

MySQL-MHA搭建、故障测试

一、架构说明 MHA&#xff08;Master High Availability&#xff09;是一个用于 MySQL 主从复制管理和自动故障转移的开源工具集。MHA 的主要目的是提供 MySQL 环境的高可用性和自动故障转移功能&#xff0c;确保在主库发生故障时能够快速切换到备库&#xff0c;降低业务中断时…

机器人组装、充电桩组装行业生产管理MES系统免费用

​随着工业4.0和智能制造的兴起&#xff0c;企业对于生产过程的数字化、智能化需求日益迫切。传统的MES系统实施周期长、成本高&#xff0c;成为许多企业数字化转型的瓶颈。而低代码开发平台的出现为这一问题提供了新的解决思路。 ​一、万界星空科技低代码平台的优势&#xff…

【市工信】2024年青岛市绿色工厂、绿色工业园区等绿色制造示范申报

科大睿智小编从青岛市工信局了解到&#xff0c;为深入贯彻绿色发展理念&#xff0c;牢固树立绿色低碳发展导向&#xff0c;进一步完善绿色制造体系&#xff0c;培育绿色制造先进典型&#xff0c;根据《工业和信息化部关于印发<绿色工厂梯度培育及管理暂行办法>的通知》&a…

nginx出现 “414 request-uri too large”

nginx出现 “414 request-uri too large” 1.修改传参方式 POST 2.字段能变成后端获取就自己获取&#xff0c;不用前端传 3.修改nginx配置&#xff0c;添加client_header_buffer_size 512k;large_client_header_buffers 4 512k;配置

zephyr学习

zephyr内核对象学习 定时器 类似linux的定时器&#xff0c; 可以分别设置第一次到期时间和后续的周期触发时间&#xff0c; 可以注册到期回调和停止回调 还有一个计数状态&#xff0c;用于标记timer到期了多少次 duration&#xff1a;设定timer第一次到期的时间。 period: …

【电机仿真】空间矢量脉宽调制(SVPWM)算法与实现

前言 文章【电机仿真】永磁同步电机模型中所提及了PMSM数学模型&#xff0c;模型算法是电机控制的理论基础&#xff0c;但在实际控制中&#xff0c;需要将这两部分具象化。实际电机所需要的总是三相电流或者电压&#xff0c;控制对象为逆变器中的开关器件&#xff0c;我们需要将…

C/C++ Zlib库调用Minzip来封装MyZip压缩类

文章目录 1、C/C Zlib库调用Minzip来封装MyZip压缩类1.1、类的功能实现1.1.1、ZIP压缩函数 Compress1.1.2、ZIP解压函数 UnCompress1.1.3、代码如下1.1.4、如何使用类 1、C/C Zlib库调用Minzip来封装MyZip压缩类 Zlib是一个开源的数据压缩库&#xff0c;提供了一种通用的数据压…

金仕达与 DolphinDB 建立深度合作,共筑 FICC 科技创新新篇章

从“关起门做交易”到“打开门做服务”&#xff0c;国内 FICC 业务正经历从自营到市场化服务的转变&#xff0c;借助数据分析、算法交易等技术的快速发展&#xff0c;交易团队能够更加主动地发现市场需求&#xff0c;为不同客群提供更好的做市业务&#xff0c;FICC 交易电子化已…

用于制作耳机壳的倒模专用UV树脂有什么特点?

制作耳机壳的UV树脂耳机壳UV胶具有以下特点&#xff1a; 快速固化&#xff1a;UV树脂可以在紫外线的照射下迅速固化&#xff0c;大大缩短了制作时间。高硬度与高耐磨性&#xff1a;UV树脂具有较高的硬度和耐磨性&#xff0c;能够提供良好的保护效果。透明度高&#xff1a;UV树…

靶机渗透之ConnectTheDots

对于vulnhub中的靶机&#xff0c;我们都需先下载镜像&#xff0c;然后导入VM&#xff0c;并将网络连接改为NAT模式。首先我们再来看一下靶机渗透的步骤&#xff1a;信息收集-漏洞分析-漏洞利用-提权。基本都是这个三个步骤&#xff0c;接下来开始我们今天的靶机渗透吧&#xff…

qt QRadioButton 及QButtonGroup 使用

QRadioButton 放在组合框QGroupBox中&#xff0c;再点击时&#xff0c;即使有多个QRadioButton按钮&#xff0c;同时选中的也就只有一个。 如下图所示&#xff0c; 对于多个QRadioButton&#xff0c;每个按钮都写一个槽函数是不太明智的选择&#xff0c;需要将QRadioButton放在…

续上一篇---Jetson Xavier NX 开发板板载摄像头教程

测试摄像头&#xff0c;打开 NX 的终端 写入命令&#xff1a;nvgstcapture-1.0 摄像头就会起来了&#xff0c;想关掉摄像头的话&#xff0c;直接在终端输入 q 再按回车&#xff1b;想捕获图片的话&#xff0c;在终端输入 j 再按回车&#xff0c;图片将保存当前目录下。 手册中…

html基础操练和进阶修炼宝典

文章目录 1.超链接标签2.跳锚点3.图片标签4.表格5.表格的方向属性6.子窗口7.音视频标签8.表单9.文件上传10.input属性 html修炼必经之路—各种类型标签详解加展示&#xff0c;关注点赞加收藏&#xff0c;防止迷路哦 1.超链接标签 <!DOCTYPE html> <html lang"en…

使用Python语言实现一个基于动态数组的序列队列

一、动态数组的实现 首先&#xff0c;我们需要创建一个DynamicArray类&#xff0c;该类将管理我们的动态数组。 动态数组能够动态地调整其大小&#xff0c;以容纳更多的元素。 目录 一、动态数组的实现 代码示例&#xff1a; 二、序列队列的实现 接下来&#xff0c;我…

Flutter Text 下划线

IntrinsicWidth(child: Column(mainAxisAlignment:MainAxisAlignment.center,children: [Text("工单名称",style: TextStyle(overflow: TextOverflow.fade,color: AppColors.baseColor,fontSize: 15.sp,// decorationStyle: TextDecorationStyle.dashed),),Container…

PCIE Retimer

1 Retimers retimer是一种PCIE的扩展设备&#xff0c;用于长距离高效地传输数据&#xff0c;起到一种中继器的作用。在EP和RC中间最多允许两级retimers级联&#xff0c;其可以分为纯模拟类&#xff0c;它是一种对物理层协议无关的芯片&#xff1b;另一种便是与物理层交互的芯片…

xsslabs第四关

测试 "onclick"alert(1) 这与第三关的代码是一样的&#xff0c;但是每一关考的点是不一样的所以我们看一下源代码 <!DOCTYPE html><!--STATUS OK--><html> <head> <meta http-equiv"content-type" content"text/html;ch…

奇安信发布《2024人工智能安全报告》,AI深度伪造欺诈激增30倍

2024年2月29日&#xff0c;奇安信集团对外发布《2024人工智能安全报告》&#xff08;以下简称《报告》&#xff09;。《报告》认为&#xff0c;人工智能技术的恶意使用将快速增长&#xff0c;在政治安全、网络安全、物理安全和军事安全等方面构成严重威胁。 《报告》揭示了基于…

生成式AI与仿真

仿真模型是物理对象、系统或过程的虚拟表示&#xff0c;可预测其在不同场景中的行为和性能。 如今&#xff0c;仿真模型广泛应用于各行各业&#xff0c;以优化流程、为决策提供信息并创建数字孪生。 几十年来&#xff0c;仿真模型一直被用来对复杂的系统和过程进行建模。 这些…