【Linux】信号保存、信号处理、可重入函数、volatile关键字、SIGCHLD信号

news2024/11/20 4:24:42

目录

一、信号保存

1.1 信号相关的概念名词

1.2 在内核中的表示

1.3 sigset_t与操作函数

1.4 信号设定

二、信号处理

2.1 内核空间与用户空间

2.2 内核态和用户态

2.3 信号的捕捉流程

2.4 sigaction 函数

三、可重入函数

四、volatile

五、SIGCHLD信号


一、信号保存

1.1 信号相关的概念名词

当信号产生时,信号的处理可选操作有:1.忽略;2.默认处理;3.自定义处理。

  • 实际执行信号的处理动作称为信号递达(Delivery)
  • 信号从产生到递达之间的状态,称为信号未决(Pending)
  • 进程可以选择阻塞(Block)某个信号。
  • 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
  • 注意,阻塞和忽略是不同的,只要信号被则阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

1.2 在内核中的表示

在内核中,信号的产生、处理与下面两张位图和一个函数指针数组有关,示意图如下:

block 和 pending 位图就表示着信号状态,接下来我们就来分析这些结构存在的意义以及相应的信号处理动作。


pending 位图

该位图称为 pending 信号集

  • 用映射的比特位位置来表示对应的信号编号,用0和1来表示是否收到信号。
  • OS就是通过修改pending表中对应的比特位来进行信号的发送。

block 位图

block 位图 称之为阻塞信号集,也称为当前进程的信号屏蔽字(Signal Mask),这里的屏蔽应理解为阻塞。

block 位图与peding相同,位图中的内容代表的含义是对应的信号是否被阻塞。


handler 数组

handler 数组被称为 handler 处理方法表。

handler 本质就是一个函数指针数组,其中存放着对应信号的默认处理函数。当 pending 位图中某个比特位被修改时,就会去对应的 handler 数组调用该函数进行信号的处理。

所以,signal函数的本质:

根据信号编号将数组下标处的处理函数换为我们自定义的函数。示意图如下:

 而其中这些SIG_DEL、SIG_IGN是用于让OS进行信号判断的,确定接下来的处理动作。

(typedef void (*__sighandler_t) (int),__sighandler_t 被声明定义为函数指针)

步骤如下:


一个信号被处理,是怎样的一个处理过程呢?

  1. 发送信号:本质就是OS修改 pending 位图。
  2. 处理信号:检测 pending 位图是否有信号产生,再检查block位图判断该信号是否被阻塞,阻塞则不处理该信号;如果没有被阻塞再去对应的 handler 数组中判断是哪种处理方式,然后进行处理。

1.3 sigset_t与操作函数

是操作系统为我们提供的一种位图结构。

sigset_t位图只能通过系统调用接口进行操作,不允许用户自己的接口进行操作。

sigset的接口与普通位图操作非常相似,常用接口如下:

#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);

函数解释:

  • sigemptyset函数:初始化 set 所指向的信号集,使其中所有信号对应的比特位清零,表示该信号不包含任何有效信息。
  • sigfillset函数:初始化 set 所指向的信号集,使其中所有信号对饮的比特位置1,表示该信号集的有效信号包括系统支持的所有信号。
  • siaddset函数:在 set 所指向的信号集中添加某种有效信号。
  • sigdelset函数:在 set 所指向的信号集中删除某种有效信号。
  • sigismember函数:判断 set 所指向的信号集中是否包含某种信号,若包含则返回1,不包含则返回0,调用返回-1; 

以上是 signet_t 的操作接口,那signet_t 能完成什么功能呢?

接下来我们再来学习一些接口:

sigpending 接口:

功能:

        获取当前进程的 pending 信号集。即可以拿到内核中的pending位图

返回值:

        成功返回0,失败返回-1,错误码被设置。

sigprocmask 接口:

功能:

        检测并更改当前进程的 block信号集。

参数1: (进行以下哪种操作)

参数2:

        参与操作的位图,与参数1搭配使用

参数3:

        是一个输出型参数,返回修改前的位图结构,用于记录保存,不需要可以设为 NULL 。

返回值:

        成功返回0,失败返回-1,错误码被设置。

1.4 信号设定

有了上面的一系列接口,我们可以就可以实现一些信号相关的问题:

  1. 如果我们对所有的信号都进行了自定义捕捉,那这个进程是不是就不能被任何信号终止?可以实现吗?结果如何?
  2. 如果将2号信号block,并且不断获取当前进程的pending信号集;此时我们突然发送2号信号,可以看到pending信号集中有一个比特位由0变为1吗?
  3. 如果对所有的信号进行block,那这个进程是不是就不能被任何信号终止?可以实现吗?结果如何?

关于问题一的代码:

void catchSig(int signum)
{
    cout << "捕捉到一个信号" << signum << endl;
}

int main()
{
    // 对所有信号进行自定义捕捉
    for (int i = 1; i <= 31; i++)
    {
        signal(i, catchSig);
    }
    while (1)
        sleep(1);
    return 0;
}

结果如下:

结论:

  • 无法实现不受信号的控制的进程,虽然我们尝试设定了所有信号的自定义捕捉方式,但是9号信号是管理员信号,无法进行自定义捕捉,所以该假设无法实现。

问题二:

int main()
{
    // 1.定义信号集
    sigset_t bset, obset, pending;
    // 2.初始化
    sigemptyset(&bset);
    sigemptyset(&obset);
    // 3.添加要进行屏蔽的信号---屏蔽2号信号
    sigaddset(&bset, 2);
    // 4.设置set到内核对应的block信号集中,(默认情况进程不会对任何信号进行block)
    sigprocmask(SIG_BLOCK, &bset, &obset);
    // 5.重复打印当前进程的pending 信号集
    while (1)
    {
        // 初始化pending位图
        sigemptyset(&pending);
        // 将当前进程的pending信号集放置到pending位图中
        sigpending(&pending);
        // 打印pending位图.
        for (int sig = 1; sig <= 31; sig++)
        {
            // 如果该位是1,则打印1,反之亦然
            if (sigismember(&pending, sig))
                cout << "1";
            else
                cout << "0";
        }
        cout << endl;
        sleep(1);
    }
    return 0;
}

 打印结果:

 结论:

  • 可以看到比特位由0置1,因为pending位图就是信号的记录位图,而我们使用系统调用接口实时地打印pengding位图的情况,就可以看到2号信号比特位由0置1。

问题三:

将所有信号都block,能实现不受信号控制的进程吗?

void blockSig(int sig)
{
    sigset_t bset;
    sigemptyset(&bset);
    sigaddset(&bset, sig);
    sigprocmask(SIG_BLOCK, &bset, nullptr);
}

int main()
{
    // block所有信号
    for (int sig = 1; sig <= 31; sig++)
    {
        blockSig(sig);
    }
    // 打印block信号表
    sigset_t pending;
    while (1)
    {
        sigpending(&pending);
        for (int sig = 1; sig <= 31; sig++)
        {
            // 如果该位是1,则打印1,反之亦然
            if (sigismember(&pending, sig))
                cout << "1";
            else
                cout << "0";
        }
        cout << endl;
        sleep(1);
    }
    return 0;
}

一个简单获取进程pid的命令:pidof + 进程名

 接下来我们再写一个脚本,让脚本帮助我们发送1-32号信号

i=1; id=$(pidof mysignal); while [ $i -le 31 ] ; do kill -$i $id; echo "send signal $i" ; let i++; sleep 1; done

运行结果如下: (9号信号仍然不受影响)

 接下来再写一段脚本,跳过发送9号信号。

i=1
id=$(pidof mysignal)
while [ $i -le 31 ] 
do 
    if [ $i -eq 9 ];then
        let i++
        continue
    fi
    kill -$i $id
    echo "kill -$i $id"
    let i++
    sleep 1
 done

运行结果如下:

一个小现象是:19号和20号都是和暂停相关的信号,也是不允许阻塞的。

二、信号处理

2.1 内核空间与用户空间

每一个进程都有自己的进程地址空间,该进程地址空间由内核空间和用户空间构成:

  • 用户代码和数据位于用户空间,通过用户级页表与物理内存之间建立映射关系。
  • 操作系统代码和数据存储在内核空间,通过内核级页表与物理内存之间建立映射关系。

其中,内核级页表是一个全局页表,它是用来维护操作系统的代码和进程之间的关系的。

因此,每个进程的进程地址空间中,用户空间是属于当前进程的,每个进程看到的代码和数据是完全不同的,但内核间所存放的都是操作系统的代码和数据,所有进程看到的都是一样的内容。

 每个进程都能看到内核空间,但并不意味着每个进程都能随时对其进行访问。

那如何理解进程切换呢?

  1. 在当进程的进程地址空间中的内核空间,找到OS的代码和数据。
  2. 执行OS的代码,将当前进程的代码和数据剥离下来,并换上另一个进程的代码和数据。

当访问用户空间时处于用户态,而当你访问内核空间时必须处于内核态。

2.2 内核态和用户态

内核态与用户态:

  • 内核态通常用来执行操作系统的代码,是一种权限非常高的状态。
  • 用户态是一种用来执行普通用户代码的状态,是一种受监管的普通状态。

进程收到信号后,并不是立即处理信号,而是在合适的时候,进行信号的处理。

这个合适的时候就是指从内核态切换回用户态的时候

内核态和用户态之间是如何进行切换的?

用户态切换为内核态通常有以下几种情况:

  1. 需要进行系统调用时。
  2. 当进程进行时间片轮转时,导致进程切换进入内核态。
  3. 产生异常、中断、陷阱等情况时。

从内核态切换为用户态通常有以下几种情况:

  1. 系统调用结束返回。
  2. 进程切换完毕。
  3. 异常、中断、陷阱处理完毕。

2.3 信号的捕捉流程

信号的捕捉流程其实可以被分为两种

  • 一种是系统直接执行默认或忽略动作;
  • 另一种是执行我们的自定义动作;

默认处理或忽略处理时:

如果待处理信号的处理动作是默认或者忽略,则执行该信号的处理动作后清楚对应的pending标志位,如果没有新的信号进行递达,则直接返回用户态,从主控制流程中上次被中断的地方继续向下执行。

执行自定义动作:

如果待处理的信号是自定义捕捉的,即该信号的处理动作是用户提供的,那么处理该信号时就需要先返回用户态执行对应的自定义处理动作,执行完后再通过特殊的系统调用 sigretur 再次陷入内核并清除对应的 pending 位图标志位,如果没有新的信号要递达,就直接返回用户态,继续执行主流程的代码。

 注意:

sighandler 和 main 函数使用不同的堆栈空间,它们之间不存在调用与被调用的关系,是两个独立了的控制流程。

 通俗理解自定义捕捉流程与结论总结:

结论:

        其中该图形与直线有几个交点就代表在这期间有几次状态切换,而箭头的方向就代表着此次状态切换的方向,圆形中间的圆点就代表着检查 pending 位图表。

此时引入一个问题:

当识别到信号的处理动作是自定义时,能直接在内核态直接执行用户空间的代码吗?

  • 理论上是可以的,因为内核态是一种权限非常高的状态,但是绝对不能这样设计。
  • 如果允许内核态直接执行用户空间的代码,那么用户就可以在代码中设计一些非法操作,比如清空数据等,虽然用户态没有足够的权限做到清空数据,但是内核态有足够的权限能执行此类代码。
  • 所以为了防止此类操作,操作系统不会在内核态下执行用户代码。因为操作系统无法保证用户的代码是合法代码,即操作系统不信任用户的行为。

2.4 sigaction 函数

捕捉信号除了前面用过的 signal 函数之外,我们还可以使用 sigaction 函数对信号进行捕捉:

功能:

        检查并更改信号的处理动作。简而言之就是捕捉信号

参数:

        参数1: 要自定义的信号编号,传入宏或信号编号。

        参数2:输入型参数,传入信号的新处理方法。

        参数3:输出型参数,返回信号旧的处理方法。

返回值:

        成功返回0,失败返回-1,并设置错误码。

sigaciton 结构体:

成员1 (sa_handler):

       将sa_handler赋值为常数SIG_IGN传给 sigaction 表示忽略信号,赋值为常数 SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函数。

        即回调函数,传入我们自定义的信号处理函数即可,例act.sa_handler=handler(handler是一个函数)。

成员2 (sa_sigaction):

        实时信号的处理函数接口,因为暂时不处理实时信号,不用设置~

成员3 (sa_mask):

        是一个sigset_t(系统位图)结构,直接使用sigemptyset(&act.sa_mask)清空即可。

成员4 (sa_flags):

        与实时信号相关,暂时无关,设置为0即可。

成员5 (sa_restorer):

        暂时不用设置~

接下来我们使用一下 sigaction 函数,目的如下:

使用 sigaction 捕捉2号信号,并查看handler数组中的处理动作是什么:

void handler(int signum)
{
    cout << "获取了一个信号" << signum << endl;
}
int main()
{
    struct sigaction act, oact;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    //设置自定义信号处理函数
    act.sa_handler = handler;
    // 设置进当前进程的pcb中
    sigaction(2, &act, &oact);
    cout << "default action " << (int)(oact.sa_handler) << endl;
    while (1)
        sleep(1);
    return 0;
}

运行结果:

 block位图的意义:

  • 当某个信号的处理函数被调用时,内核自动将当前信号对应的 block 位图比特位置为1,表示阻塞,当信号处理函数返回时自动恢复原来的bloc位图状态,这样就保证了在处理莫格信号时,如果这个信号再次发生,那么它就会阻塞到当前处理结束为止
  • 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还系统自动屏蔽另外一些信号,就可以使用 sigaciton 结构体 中的 sa_mask 字段进行设置额外屏蔽的信号,则当一个信号发生时,这些被添加的信号再发生时,对应 block 位图的会被设置为1,而当信号处理函数返回时会自动恢复原来的状态,这便是sa_mask字段的作用以及block位图的意义。

接下来有一段代码可以验证sa_mask的作用:

设置了2号信号的自定义函数,并将sa_maks中设置3、4、5、6、7信号。

即,2号信号产生时,3、4、5、6、7信号的 block 位图被置1,通过打印pending位图,即使3、4、5、6、7号信号发生,这些信号也不会被处理。

代码如下:

// block位图的意义:
void showPending(sigset_t *pending)
{
    for (int sig = 1; sig <= 31; sig++)
    {
        if (sigismember(pending, sig))
            cout << "1";
        else
            cout << "0";
    }
    cout << endl;
}

void handler(int signum)
{
    cout << "获取了一个信号: " << signum << endl;
    cout << "获取了一个信号: " << signum << endl;
    cout << "获取了一个信号: " << signum << endl;
    cout << "获取了一个信号: " << signum << endl;

    sigset_t pending;
    int c = 20;
    while (true)
    {
        // 获取pending位图并打印
        sigpending(&pending);
        showPending(&pending);
        c--;
        if (!c)
            break;
        sleep(1);
    }
}

int main()
{
    cout << "getpid: " << getpid() << endl;
    // 内核数据类型,用户栈定义的
    struct sigaction act, oact;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    // 设置自定义函数
    act.sa_handler = handler;

    // sa_maks中设置3、4、5、6、7信号,即2号信号发生会阻塞这些信号
    sigaddset(&act.sa_mask, 3);
    sigaddset(&act.sa_mask, 4);
    sigaddset(&act.sa_mask, 5);
    sigaddset(&act.sa_mask, 6);
    sigaddset(&act.sa_mask, 7);

    // 设置进当前调用进程的pcb中
    sigaction(2, &act, &oact);

    cout << "default action : " << (int)(oact.sa_handler) << endl;
    while (true)
        sleep(1);
    return 0;
}

结果:

注意,信号捕捉,并没有创建新的进程或线程。

三、可重入函数

一个函数在一个时间段内被多个执行流重复进入,这种情况就叫做重入函数;

而重入时没有发生问题的叫可重入函数,会发生问题的叫做不可重入函数。可重入和不可重入是函数的一种特征,我们大部分编写的函数都是不可重入函数。

那什么特征的函数是不可重入函数?

  1. 调用了malloc或free,因为malloc也是用全局链表来管理堆的。
  2. 调用了标志I/O库函数,因为标准I/O库的很多实现都以不可重入的方式使用全局数据结构。
  3. 比如函数用了全局数据, errno 错误码就是一个全局变量,大部分函数不可重入函数。

举一个链表结点的插入例子来形象理解可重入/不可重入:

 如果是一个执行流该代码不会有什么问题,如果是一段时间被多个执行流反复跳转,则下面这个普通的链表插入结点都会产生错误。

四、volatile

volatile是C语言的一个关键字,该关键字的作用是保持内存的可见性。

首先我们写一段代码进行引入。

因为flag==0,所以main函数处于死循环状态,我们自定义设置2号信号的处理函数,当2号信号产生时我们将flag置为1,则主函数跳出死循环。

int flag = 0;
void changFlag(int signum)
{
    cout << "change flag:" << flag;
    flag = 1;
    cout << "->" << flag << endl;
}
int main()
{
    signal(2, changFlag);
    while (!flag)
        ;
    cout << "进程正常退出:" << flag << endl;
    return 0;
}

 我们使用的g++,有不同级别的优化选项:

接下来我们不使用默认的优化策略,设置优化选项为-O3,结果如下:

发现,使用Ctrl+C发送2号信号无法终止该进程。

必然是优化选项对flag做了特殊处理,导致该代码收到2号信号后仍无法终止。

原因如下:

因为我们对 flag 的频繁访问,编译器将flag放入了寄存器中。正常的优化是:需要检测flag时去内存中 将其读入寄存器然后进行检测,而2号信号产生时,改动了内存中!的flag,而寄存器中的flag没有被改动,所以检测时一直检测的寄存器中的flag,所以该死循环无法被终止。

总结一句话是,cpu无法看到内存中的flag了。

所以我们要使用关键字volatile显性地告诉编译器,不要将一些变量放入寄存器中,保持内存的可见性。

现在我们使用volatile修饰flag,再观察结果:

那这个优化是在编译时进行的还是执行时进行优化的呢?

编译时进行优化的,因为编译后,gcc让CPU将flag放入寄存器中,这个举动是在编译后就确定了,只不过是运行时才能体现出效果。

五、SIGCHLD信号

子进程暂停或退出时会主动向父进程发送SIGCHLD(17号)信号。而父进程对17号信号的默认处理动作是忽略。

  • 为了避免出现僵尸进程,父进程需要使用wait或waitpid函数等待子进程结束,父进程可以阻塞等待子进程结束,也可以非阻塞地查询的是否有子进程结束等待清理,即轮询的方式。采用第一种方式,父进程阻塞就不能处理自己的工作了;采用第二种方式,父进程在处理自己的工作的同时还要记得时不时地轮询一下,程序实现复杂。
  • 其实,子进程在终止时会给父进程发生SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理动作,这样父进程就只需专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait或waitpid函数清理子进程即可。

首先我们写一段代码验证一下子进程退出是否会给父进程发送17号信号:

//子进程退出会向父进程发送信号
void handler(int signum)
{
    cout << "子进程退出" << signum << endl;
}

int main()
{
    signal(SIGCHLD, handler);
    if (fork() == 0)
    {
        sleep(1);
        exit(0);
    }
    while (1)
    {
        sleep(1);
    }
}

SIGCHLD与waitpid使用场景:

那我们可以在捕捉信号中可以进行子进程的 等待wait 操作。那接下来,父进程下有10个子进程,

比如同时有5个子进程退出,位图中只有一个比特位记录退出的情况,而不会记录退出的子进程个数,所以我们要进行遍历检查10个子进程是否有退出的情况,而此时我们不能使用阻塞式等待,因为如果有一个子进程没有退出,那父进程就一直阻塞等待该进程退出了。

所以我们要使用while遍历所有的子进程,并使用waitpid采取非阻塞的方式进行等待。这样只要有子进程退出,父进程就会将该子进程回收,并且父进程自身不会阻塞。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/wait.h>

void handler(int signo)
{
	printf("get a signal: %d\n", signo);
	int ret = 0;
	while ((ret = waitpid(-1, NULL, WNOHANG)) > 0){
		printf("wait child %d success\n", ret);
	}
}
int main()
{
	signal(SIGCHLD, handler);
	if (fork() == 0){
		//child
		printf("child is running, begin dead: %d\n", getpid());
		sleep(3);
		exit(1);
	}
	//father
	while (1);
	return 0;
}

接下来我们会设置一下对SIGCHLD的忽略动作,我们实现的忽略动作是用户层的,而默认的忽略是系统层的,两者会有一些区别。

子进程退出会对父进程发送信号,然后父进程执行默认的忽略。所以我们设置当子进程退出时,捕捉该信号,然后对子进程进行回收,

观察操作系统的忽略动作和用户层设定的忽略动作有何不同:

// 系统默认忽略动作:
int main()
{
    if (fork() == 0)
    {
        cout << "child:" << getpid() << endl;
        sleep(5);
        exit(0);
    }
    while (1)
    {
        cout << "parent:" << getpid() << "father process:执行任务......" << endl;
        sleep(1);
    }
    return 0;
}

脚本监视代码:

while :; do ps ajx | head -1 && ps axj | grep SIGCHLD | grep -v grep ; sleep 1; echo "--------------------------------"; done

操作系统默认的忽略动作现象(子进程处于僵尸状态):

事实上,由于UNIX的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调用signal或sigaction函数将SIGCHLD信号的处理动作设置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。系统默认的忽略动作和用户用signal或sigaction函数自定义的忽略通常是没有区别的,但这是一个特列。此方法对于Linux可用,但不保证在其他UNIX系统上都可用。

接下来就是我们设置当SIGCHLD信号产生时对SIGCHLD进行忽略(代码):

用户层设置对子进程退出SIGCHLD信号的忽略(子进程被回收):

由上面对比发现,OS默认的忽略就是忽略,不进行子进程僵尸状态的回收,而我们设置的忽略动作进行了僵尸状态的回收。

可以理解为操作系统的忽略和用户级的忽略程度不同。

操作系统的忽略就是什么都不做,即使子进程进入了僵尸状态也不做处理,如果我们设置了忽略操作,操作系统会先进行回收子进程,然后进行忽略,两者忽略的程度不一样。

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

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

相关文章

当今主流的网络服务应用

文件传输协议 主机之间传输文件是IP网络的一个重要功能&#xff0c;如今人们可以方便地使用网页、邮箱进行文件传输。 然而在互联网早期&#xff0c;Web&#xff08;World Wide Web&#xff0c;万维网&#xff09;还未出现&#xff0c;操作系统使用命令行的时代&#xff0c;…

webpack前端应用之基础打包

目录 前言&#xff1a;初识 Webpack 5 一、前端工程化 1、webpack ​ &#xff08;2&#xff09;主要功能&#xff1a; 2、webpack的使用&#xff1a;配置文件所需要的信息&#xff08;五大配置属性&#xff09; 3、示例 强调&#xff1a; 4、webpack中使用的loader 二…

【Java基础】003 -- Java基础概念(计算机的存储规则)

目录 计算机的存储规则 1、什么是二进制&#xff1f; 2、为什么计算机要使用二进制存储数据&#xff1f; 3、进制之间可以转换吗&#xff1f; 4、码表&#xff08;Text文本&#xff09; 5、图片数据 6、声音数据 计算机的存储规则 在计算机中&#xff0c;任意的数据都是…

java集成RSA非对称加密数据传输

使用场景: 前端请求后端接口时如:登录接口,这时候需要传账号密码到后端接口请求这样就会暴露请求的数据。RSA非对称加密分公钥和私钥,公钥将数据进行加密,私钥对加密的数据进行解密 (当然前端最好是封装一下不要暴露出来公钥) 代码实现: 1、RSA工具类(或访问http:…

大数据舆情监控应用平台,TOOM大数据舆情监控系统的作用

大数据舆情监控应用是利用大数据技术对社会舆情的收集、分析、挖掘和展示的工具。它通常会收集和分析各种社交媒体、新闻媒体、博客等信息&#xff0c;以了解舆情动态和趋势。大数据舆情监控应用可以帮助企业和政府了解市场和社会动态&#xff0c;为决策提供支持。然而&#xf…

聚观早报 |比亚迪预计去年营收超4200亿元;美股三大指数集体收跌

今日要闻&#xff1a;比亚迪预计去年营收超 4200 亿元&#xff1b;美股三大指数集体收跌&#xff1b;王凤英正式加入小鹏汽车出任总裁&#xff1b;苹果计划在印度生产 25% 的 iPhone 手机&#xff1b;LVMH老板放狠话坚决打击代购行为比亚迪预计去年营收超 4200 亿元 1 月 30 日…

(Java高级教程)第四章必备前端基础知识-第三节3:JavaScript之DOM和BOM

文章目录一&#xff1a;WebAPI概述二&#xff1a;DOM&#xff08;1&#xff09;获取元素&#xff08;2&#xff09;事件&#xff08;3&#xff09;操作元素A&#xff1a;获取&#xff08;修改&#xff09;元素内容B&#xff1a;获取&#xff08;修改&#xff09;元素属性C&…

LeetCode 刷题系列 -- 108. 将有序数组转换为二叉搜索树

给你一个整数数组 nums &#xff0c;其中元素已经按 升序 排列&#xff0c;请你将其转换为一棵 高度平衡 二叉搜索树。高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。示例 1&#xff1a;输入&#xff1a;nums [-10,-3,0,5,9]输出&a…

粒子群优化(PSO)算法例题实现

目录 一、实验要求 二、算法流程 三、案例实现及结果 完整程序&#xff1a; 一、实验要求 二、算法流程 粒子群算法流程&#xff1a; 1、初始化&#xff1a;初始化粒子群&#xff1b;给每个粒子赋予初始位置和速度 2、计算适应值&#xff1a;根据适应度函数&#xff0c;计…

新范式+新标准=世界级产品|StarRocks年度总结

岁序常易&#xff0c;华章日新。虎年即将落幕&#xff0c;雄关漫道&#xff0c;我们携手社区斗志昂扬&#xff0c;并肩虎跃雄关。兔年新岁将至&#xff0c;黎明破晓&#xff0c;我们协力社区蓄势待发&#xff0c;昂首共赴新程。值此送虎迎兔的新春佳节之际&#xff0c;感恩与St…

电子技术——MOS放大器的DC偏置

电子技术——MOS放大器的DC偏置 正如前几节我们学习的&#xff0c;MOS放大器的小信号模型的参数取决于正确的DC偏置&#xff0c;这个步骤称为偏置设计。一个好的偏置设计要满足一个稳定的漏极DC电流 IDI_DID​ 和设置正确的 VDSV_{DS}VDS​ 保证MOS管在放大信号的时候处在饱和区…

Qt StyleSheet介绍

文章目录前言纠错技巧可以使用 , 号来同时指明多个同一类型控件的样式表qss注释前言 本文主要以这篇博客为基础。添加一些自己使用的心得和使用样式表的一些技巧 纠错 ID选择器这里类型选择器可以省略&#xff0c;因为每个控件的objectName是不一样的&#xff0c;所以无需指定…

高性能消息队列中间件MQ

毕业后工作半年&#xff0c;在自己的讲课中需要介绍消息队列&#xff0c;以前在大学也有经常接触message queen&#xff0c;但却还不够深入了解掌握&#xff0c;这次写个专门针对mq的文章理清头绪。 以下是学习mq的知识框架&#xff0c;我会不定时更新补充 RabbitMQ概念_MQ 消…

TwinCAT3串口通讯EL6021模块使用-和串口调试助手自由协议通讯

目录 一、简介 二、环境介绍 三、接线连接 四、创建TwinCAT3程序工程 1、IO扫描和参数设置 2、创建PLC程序 &#xff08;1&#xff09;库文件添加 &#xff08;2&#xff09;创建任务和程序 &#xff08;3&#xff09;变量关联 &#xff08;4&#xff09;重新激活工程、运…

adb的一些基本操作

adb的一些基本操作 Android使用的系统一般是debian系列操作系统&#xff0c;所以使用adb shell连接到手机后&#xff0c;可以使用大部分Debian系列的命令进行相关的操作 列出所有应用&#xff1a;adb shell pm list packages 列出第三方(系统)应用&#xff1a;adb shell pm li…

1611_PC汇编语言_math例程分析

全部学习汇总&#xff1a; GreyZhang/g_unix: some basic learning about unix operating system. (github.com) 这一次分析后带有注释的代码我会在笔记最后做一个完整的附加。 这一个例程&#xff0c;主要是为了阐述前面讲到的数学运算。但是从这些操作中&#xff0c;很多底层…

介绍golang限流库以及漏桶与令牌桶的实现原理

RateLimit 限流中间件 前言 为什么需要限流中间件&#xff1f; 在大数据量高并发访问时&#xff0c;经常会出现服务或接口面对大量的请求而导致数据库崩溃的情况&#xff0c;甚至引发连锁反映导致整个系统崩溃。或者有人恶意攻击网站&#xff0c;大量的无用请求出现会导致缓…

Spark JDBC采用分区读取数据库时partitionColumn, lowerBound, upperBound, numPartitions参数理解

partitionColumn是应该用于确定分区的列。 lowerBound并upperBound确定要获取的值的范围。完整数据集将使用与以下查询对应的行&#xff1a; SELECT * FROM table WHERE partitionColumn BETWEEN lowerBound AND upperBound numPartitions确定要创建的分区数。lowerBound和之间…

Unicode 和 UTF-8 详解

结论 Unicode 是 字符集 UTF-8 是 编码规则 字符集&#xff1a;为每一个字符分配唯一的ID&#xff08;如 SCII 码&#xff09; 编码规则&#xff1a;将 码位转换为字节序列的规则 背景 老规矩&#xff0c;我们用图文并茂的方式来讲解&#xff1a; ASCII 这个字符集 由于仅能…

[Android Studio] Android Studio设置杂项

&#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea; Android Debug&#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea; Topic 发布安卓学习过程中遇到问题解决过程&#xff0c;希望我的解决方案可以对小伙伴们有帮助。 &#x1f4cb;笔记目…