linux中对信号的认识

信号的概念与相关知识认识

信号是向目标进程发送消息通知的的一种机制。 信号可以以异步的方式发送给进程,也就是说,进程无需主动等待,而是在任何时间都可以接收到信号。

信号的种类

用kill-l命令查看系统定义的信号列表:

前台进程和后台进程的认识

我们首先要知道,打开xshell软件时,默认就已经执行了shell进程,而这就是前台进程,而且前台进程只有一个。当我们运行一个我们写的死循环进程时:

#include <iostream>
#include <unistd.h>
using namespace std;

int main()
{
    while(1)
    {
        cout << "haha" << endl;
        sleep(1);
    }
    return 0;
}

其实此时该进程就自动变成了前台进程,而操作系统会自动的将shell切换成后台进程。而且此时我们呢无论输入什么指令都不会被执行,因为我们所输入的指令默认都会被前台进程接收。但是我们可以通过kill -9 进程pidCtrl c来终止此前台进程,终止以后,操作系统还会自动的将shell设置为前台进程。记住Ctrl c并不能终止前台进程shell。

如何生成后台进程

如果我们想要将进程执行成后台进程的话可以在运行进程的后面加上&符号:

而对于这两个数字对应的分别就是:后台进程编号和该后台进程pid。

 而且我们可以同时可以执行多个后台进程。

查看后台进程 

通过jobs指令查看后台进程

后台进程与前台进程的转换 

通过fg(front ground)+后台进程编号将后台进程变成前台进程:


通过Ctrl z指令和bg(back ground)+暂停进程编号将前台进程变成后台进程:

 我们可以通过Ctrl z将前台进程暂停但是我们的前台必须要有进程执行,否则系统就会挂掉了,此时操作系统会默认将shell设置为前台进程,因此原先的前台进程就被暂停在了后台中。

 操作系统是怎么知道外设有数据的

操作系统通常通过中断机制来确定外设是否有数据可用。中断是计算机系统中的一种重要机制,它是指在计算机执行某个任务时,由于发生了某种特殊事件或接收信号,硬件或软件会中断当前任务的执行,转而去处理需要优先处理的事件或任务。中断可以打破程序的顺序执行,提供了一种异步(随时)处理的方式。而且中断处理程序执行结束后,处理器会恢复之前保存的上下文,继续原来的任务执行。

我们知道CPU包括运算器和控制器的,对于控制器其实就是其他设备的控制的(因为是要控制信息,所以也会和外设相交互)。CPU在硬件上是和内存关联,而自身会提供一个个从0开始编号的针脚。针脚是和主板直接相连,而主板上插入着很多的硬件电路(相当于USB口)。而这些硬件电路就和我们的外设直接关联着。

当我们的外设在使用的时候,此时对应的针脚就会接收外设传来的高电频,从而点亮针脚(只有能进行IO的外设才能向CPU发消息的)。而我们的每个针脚都有对应的编号(中断号),所以此时的编号就会被写到寄存器里,就可以被操作系统读取。所以我们的外设有数据的时候本质上就是通过发送中断号的方式来判断

而我们中断号的作用其实就是调用对应硬件的方法的。在操作系统内部提供了一张中断表,该表是一个函数指针数组,数组下标对应的就是中断号(0号是正常运行),其中存放的就是特定硬件的读取方法。

信号的产生 

键盘产生

信号的产生其实就是通过键盘产生。当我们运行一个死循环程序时,按住Ctrl c(其实就是发送2号信号组合键时就可以中断进程。我们一般在shell上输入的指令都会放到进程的缓冲区中进行读取,而当我们输入Ctrl c组合键,并没有将数据输入给进程,而是直接转换成某种动作,向进程发送信号的方式。而进程接收信号并处理,从而终止进程。

我们操作系统当中,每一个进程都会维护一张处理信号的方法表:其实就是函数指针数组,该数组中的每一个下标对应的数据内容都是信号的处理方法。

认识signal函数:

该函数的返回值是一个参数为int的函数指针 。而参数一是信号编号,参数二的类型和函数返回值类型一样,是一个函数指针。其实该函数的功能就是重置(自定义)进程接收信号后的反应机制,也就是重置信号处理的函数指针数组里的方法但是一些信号,如9号信号不能被自定义捕捉修改

void handler(int signo)
{
    cout << "进程收到了" << signo << "信号" << endl;
    exit(1);
}
int main()
{
    signal(2, handler); // 自定义捕捉2号信号
    while (1)
    {
        cout << "死循环" << endl;
        sleep(1);
    }

    return 0;
}


信号的本质 

 信号实质是操作系统(进程的管理者)向目标进程发送的,进程也需要对信号进行管理。而底层其实就是信息的输入与输出,操作系统向对应的进程发送信号编号,然后进程PCB中存在中管理信号的结构体,其结构体内部会存在一张位图,而每一位都对应着各个编号的信号,如果比特位内容为1就表明收到该位置对应的信号。

所以一个进程含有信号的函数指针数组和信号位图。

系统调用产生

通过kill函数对指定进程发送信号

该函数就是我们shell命令行中的kill指令,所以我们的实现使用就可以通过命令行参数:

//kill模拟实现
//kill -9 pid
int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        cout<<"输入方式错误"<<endl;
        cout<<"please input "<<argv[0]<<" + "<<"signum + process_id"<<endl;
        return -1;
    }
    int signum = stoi(argv[1]+1);//去掉'-'字符
    int process_id = stoi(argv[2]);
    kill(process_id,signum);
    
    return 0;
}

 通过raise函数对当前进程发送信号

void handler(int signo)
{
    cout<<"向当前进程发送信号:"<<signo<<endl;
}
int main()
{
    signal(2,handler);//重置当前进程的信号处理表中的2号方法
    while(1)
    {
        raise(2);
        sleep(1);
    }

    return 0;
}

通过abort函数对当前进程发生终止信号

void handler(int signo)
{
    cout<<"向当前进程发送信号:"<<signo<<endl;
}
int main()
{
    signal(6,handler);//重置当前进程的信号处理表中的6号方法
    abort();
    while(1)
    {
        cout<<"运行中..."<<endl;
    }
    return 0;
}

 代码编译异常产生信号

除0异常

int main()
{
    int a=10;
    int ret=a/0;//0不能作为除数

    return 0;
}

我们知道CPU包含运算器和控制器。所以CPU会存在很多的寄存器会临时存储调度进程中需要进行运算的数据。而CPU中还存在着一种状态寄存器(status),状态寄存器中有很多的标志位,很多标志位是用来衡量运算的结果状态的。而溢出标记为也是其中。当我们的操作系统调度进程时,将改进程放到运行队列中,如果CPU的运算器发现除数为0时,状态寄存器中的溢出标志位就被置为1。而此时寄存器中的标志位出问题时,CPU中的内部模块或针脚就会向操作系统通知,而操作系统就会根据针脚为下标的函数指针数组表的内容执行对应的中断方法。操作系统接下来就会将溢出标志位解释为kill+目标进程+信号的方式向目标进程发信号。

 这对应的恰好就是信号表中的8号信号。


更改8号信号处理方式:

void handler(int signo)
{
    cout<<"进程错误信号:"<<signo<<endl;
    sleep(1);
}
int main()
{
    signal(8,handler);//自定义8号信号处理方法
    int a=10;
    a/=0;//0不能作为除数

    return 0;
}

现象与解释:

 我们发现运行可执行程序以后就开始间隔一秒的死循环输出错误信息,而并没有退出。

对此现象我们要知道,寄存器硬件是属于CPU的,而寄存器里的内容是属于当前调度的进程的,寄存器≠寄存器里的内容

当正常情况下:我们除0错误时,状态寄存器的对应标志位会置为1,所以此时就会向操作系统发送错误信号,最终终止进程,而其他进程被CPU调度运行时,对应的寄存器数据就会被覆盖改写,所以寄存器也就供当前进程使用了。

而对于以上情况时:我们进行了自定义的信号处理方式,所以我们的状态寄存器发现了除0错误以后就会向操作系统发送信号,操作系统就会解释为8号信号发送给进程,而进程会通过调用handler函数来处理8号错误信号。而该进程并没有退出,且CPU一直在调度该进程,但是异常是一直存在的,所以CPU就会一直向操作系统发送信号 

CPU发现异常会通过操作系统向进程发送信号,而进程也会对信号进行处理,但是进程没有退出,CPU中标记进程执行位置的寄存器并不会往下调度。

软件条件产生信号

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM(14号)信号, 该信号的默认处理动作是终止当前进程。

闹钟的设置使用

int n=0;
void handler(int signo)
{
    n = alarm(0);//参数等于0表示取消之前设置的闹钟,返回值n还是之前闹钟的剩余时间
    //参数大于0表示重设闹钟
    cout<<"alarm result : "<<n<<endl;
    exit(1);
}
int main()
{
    cout<<"mypid = "<<getpid()<<endl;
    signal(14,handler);
    alarm(20);//返回值是闹钟剩余的时间,一般都是0
    while(1)
    {

    }
    return 0;
}

信号的保存 

我们操作系统向进程传输信号的时候,进程一般可能并不会立即处理会再合适的时候处理。

信号常见概念

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

信号递达 

信号递达分为信号的忽略(信号接收以后取消响应)、信号的默认(无论之前信号应答方式如何,一旦默认就恢复最初的处理方式)、信号的自定义捕捉(我们自己实现handler方法)。而这些递达方式均可以通过signal函数进行实现。


void handler(int signo)
{
    cout<<"信号自定义捕捉处理 "<<signo<<endl;
    //exit(1);
}
int main()
{
    cout<<"mypid = "<<getpid()<<endl;
    signal(2,handler);
    sleep(10);
    cout<<"默认处理"<<endl;
    //signal(2,SIG_IGN);//忽略信号处理
    signal(2,SIG_DFL);//默认信号处理
    while(1)
    {
        sleep(1);
    }
    return 0;
}

信号未决 

信号未决顾名思义就是进程收到信号但还未对信号做出决策。也就是我们的操作系统向进程信号后,将信号标识到进程PCB中的信号位图中。

信号阻塞

信号被阻塞就是指当前进程不对所接收的信号做任何处理,但是依旧记录着该信号。也就是说明该信号在未处理时一直处于未决阶段,不递达。

而递达中的信号忽略并不是阻塞,因为忽略其实是递达的一种,也就是已经对信号做了处理,而阻塞是还没做处理。

信号在内核中的表示

 内核中会存在数据结构为其进程维护这三张表,分别是block表(该位图表标识着该位置处信号是否阻塞和屏蔽),pending表(和block表的结构一样,其实就是未决表,标识该进程是否收到该信号),handler表(这是一张函数指针数组表,其中的数组小标对应着信号的编号,其中的内容就是对应信号的处理方法)。

对信号集的操作

我们的handler表可以通过我们实现的函数进行传参,从而对该表的内容进行修改。对于阻塞位图表与未决位图表,我们也有对应的函数进行修改。

既然要设置信号集,而信号集的结构又是位图结构,所以我们肯定是要用到该结构的对象,然后通过系统调用函数将该信号集对象设置到进程的内核中。

sigset_t类型

sigset_t是一种数据类型,其实底层也就是我们的位图结构,0对应的就是无该位置对应的信号,1对应的就是有。而且提供了相关函数将该类型的数据进行处理。

int sigemptyset(sigset_t *set);//初始化set所指向的信号集,表示该信号集不包含任何有效信号
int sigfillset(sigset_t *set);//初始化,表示该信号集包含所有系统支持的有效信号
int sigaddset (sigset_t *set, int signo);//添加信号signo
int sigdelset(sigset_t *set, int signo);//删除信号signo
int sigismember(const sigset_t *set, int signo);//判断是否存在信号signo

在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的
状态。 

更改屏蔽字(阻塞信号集)sigprocmask

int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1

假设当前信号屏蔽字为mask: 

how说明
 SIG_BLOCK

set包含了我们希望阻塞的附加信号,相当

mask=mask|set

 SIG_UNBLOCK

set包含了我希望解除阻塞的信号,相当于

mask=mask&(~set)

SIG_SETMASK

设置当前信号屏蔽字为set所指向的值,相当于

mask=set

对于参数二和三,如果oset是非空指针,则读取进程的当前信号屏蔽字,通过oset参数传出。

如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。

如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字

使用该函数:

//sigprocmask信号屏蔽

void handler(int signo)
{
    cout<<"当前收到"<<signo<<"号信号"<<endl;
}
int main()
{
    cout<<"mypid = "<<getpid()<<endl;
    sigset_t set,oset;//创建临时位图block表
    signal(2,handler);//自定义二号信号的方法

    //清空临时信号集
    sigemptyset(&set);
    sigemptyset(&oset);

    //设置信号集到临时变量
    sigaddset(&set,2);

    sigprocmask(SIG_SETMASK,&set,&oset);//将信号集设置到该进程中

    while(1)
    {}

    return 0;
}

当我们发送二号信号时,该进程是屏蔽此信号的,也就是并没有做出任何的反应。 

而且我们的9号信号是不会被屏蔽的。

获取未决信号集sigpending 

其实对于pending表,我们之前已经了解可以通过键盘上的组合键等方式来设置pending表,所以我们现在想知道的就是pending表中的内容。

sigpending(sigset_t *set)
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1

函数使用:

//sigpending获取未决信号集

void handler(int signo)
{
    cout<<"当前收到"<<signo<<"号信号"<<endl;
}
void print(const sigset_t& tmp)
{
    for(int i=1;i<=32;i++)
    {
        if(sigismember(&tmp,i))
            cout<<1;
        else
            cout<<0;
    }
    cout<<endl;
}
int main()
{
    signal(2,handler);//自定义

    //屏蔽2号信号
    cout<<"mypid = "<<getpid()<<endl;
    sigset_t set,tmp,oset;
    sigemptyset(&set);
    sigemptyset(&tmp);
    sigemptyset(&oset);
    sigaddset(&set,2);
    sigprocmask(SIG_BLOCK,&set,&oset);//将信号集添加到该进程中

    int count=0;
    while(1)
    {
        sigpending(&tmp);
        print(tmp);
        //取消屏蔽,信号处理
        if(count==3)
        {
            cout<<"解除2号信号的屏蔽状态"<<endl;
            sigprocmask(SIG_UNBLOCK,&set,&oset);//解除屏蔽后,进程立马就递达了
        }
        sleep(1);
        count++;
    }

    return 0;
}


对于非屏蔽的信号,也就是在pending集中处于未决状态,当我们发送某一个信号时,该pending表的对应信号位图数据瞬间由1置为0(在执行handler方法之前)。 

信号的处理

认识进程内核态和用户态

我们对于信号的处理需要先了解进程状态,存在内核态和用户态。我们进程默认都是用户态的,也是一种受控的状态,访问的资源都是有限的。内核态是一种操作系统的工作状态,能够访问大部分系统资源,如系统调用函数。

我们的进程地址空间的0到3GB都属于用户空间,3到4GB属于内核空间。而对应的就分别是用户态和内核态。当我们进程在启动的时候,内存中的操作系统会首先被映射到内核空间的,而这之中虚拟和物理地址之间通过内核级页表进行转换。所以当我们在调用系统函数的时候就可以在地址空间从正文代码中去访问内核空间里的数据。但是此时是需要将进程从用户态切换到内核态

我们CPU是实现对进程的调度的,而且CPU中存在很多的寄存器。对于进程的用户态还是内核态,CPU会通过一个CS寄存器中两个比特位数据进行判定,01表示内核,11表示用户。所以对应状态切换就是改变CS寄存器中的数据。同时进行页表(也同样保存在寄存器中)的切换。

信号捕捉处理

我们知道信号的处理是要在内核中进行的,因为我们内核中有关于信号的三张表,block、pending、handler。所以我们信号处理实际就是在进程从内核态返回到用户态的时候

进程在执行接收的信号时,首先回去切换到内核态去遍历三张表,判断是否接受信号,是否阻塞,是否自定义了handler方法。如果没有自定义handler方法就是直接在内核中进行执行。但是如果自定义了handler方法的话就需要对信号方法进行捕捉处理。此时进程时时需要返回到用户态去遍历handler中的代码的(如果不转换成用户态而是保持内核态去访问handler方法的话,可能会造成非法资源的访问,有危险),当执行完handler方法之后依旧是要切换回来的, 而切换回来是因为通过了特殊的系统调用执行了sigreturn返回到内核此时信号处理完毕最终进程返回到用户态。

 而且对于进程在运行期间是会存在很多次的进程切换的。就例如代码执行死循环,然后通过Ctrl c命令直接终止进程的过程。此时进程运行,被CPU调度,但是每个进程都有时间片,所以在CPU执行的过程中,时钟中断会不断进行检测,如果该进程时间片到了,操作系统就会终止此进程,然后将该进程从CPU中剥离(此时进程上下文等数据都会保存到进程PCB中,好在下次调度是直接将数据拷贝到寄存器中),在运行队列重新排队。此时CPU会执行Ctrl c命令,切换到内核态执行该命令,终止进程。

sigaction读取和修改与指定信号相关联的处理动作

int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);


struct sigaction 
{    
    void     (*sa_handler)(int);
    void     (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t   sa_mask;
    int        sa_flags;
    void     (*sa_restorer)(void);
};

 我们主要了解一下第二个参数sigaction类型,其中该类型是一个结构体,其中主要认识一下sa_handler字段和sa_mask字段。

对于sigaction结构体sa_handler的使用就相当于是signal函数,对信号自定义捕捉:

void handler(int signo)
{
    cout<<" recieve a signal: "<<signo<<endl;
}
int main()
{

    struct sigaction act,oact;
    act.sa_handler=handler;
    sigaction(2,&act,&oact);
    while(1);

    return 0;
}

 对于sigaction结构体的sa_mask的使用表明需要额外屏蔽的信号:

当某个信号的处理函数被调用时(还没调用完成),内核会自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时则会自动恢复原来的信号屏蔽字。这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞,直到当前该信号handler方法处理完为止。

验证:

void print(const sigset_t& tmp)
{
    for(int i=31;i>=1;i--)
    {
        if(sigismember(&tmp,i))
            cout<<1;
        else
            cout<<0;
    }
    cout<<endl;
}
void handler(int signo)
{
    cout<<" recieve a signal: "<<signo<<endl;   
    while(1)//执行不退出
    {
       sigset_t pending;
       sigpending(&pending);
       print(pending);
       sleep(1);
    }
    
}

int main()
{
    cout<<"pid = "<<getpid()<<endl;
    struct sigaction act,oact;
    act.sa_handler=handler;
    sigaction(2,&act,&oact);
    sleep(5);

    return 0;
}

分析:当我们 第一次按住Ctrl c时,会执行2号信号的自定义方法,此时在执行之前就会将pending集中的2号由1置为0,开始执行对应的方法。而接下来再次接收该信号时就会发生阻塞,不会执行2号信号的方法,而且会将pending表中的2号位置置为1.

如果在调用信号处理函数时,除了当前信号被自动屏蔽(正在执行当前信号且没退出时)之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。 

以上代码加上:同时屏蔽三四号信号

int main()
{
    cout<<"pid = "<<getpid()<<endl;
    struct sigaction act,oact;
    act.sa_handler=handler;
    sigemptyset(&act.sa_mask);//初始化
    sigaddset(&act.sa_mask,3);//同时屏蔽三四号信号
    sigaddset(&act.sa_mask,4);

    sigaction(2,&act,&oact);
    sleep(10);

    return 0;
}

 当我们进程sleep(10)的过程中接受了信号的话,那么就会执行信号的处理方法,处理完以后就会停止继续sleep,而是执行sleep之后剩余代码。

认识SIGCHLD信号 

我们知道可以通过wait和waitpid函数清理僵尸进程,父进程可以阻塞等待子进程结束,也可以非阻塞地查询是否有子进程结束等待清理(也就是轮询的方式)。其实,子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数,从而在此实现回收子进程的工作。

void handler(int sig)
{
    pid_t id;
    while( (id = waitpid(-1, NULL, WNOHANG)) > 0)//循环等待,回收所有子进程,防止多个进程同时退出,形成阻塞,导致不会调用handler方法。WNOHANG等待方式防止子进程一直不退出而导致父进程无法执行后续代码。
    {
        printf("wait child success: %d\n", id);
    }
    printf("child is quit! %d\n", getpid());
}

而且对于linux平台中还有一种方式:父进程不用进行等待回收子进程的资源数据,当子进程退出时,操作系统会自动的回收子进程的资源。

signal(SIGCHLD, SIG_IGN);//忽略

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

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

相关文章

初识Hive

官网地址为&#xff1a; Design - Apache Hive - Apache Software Foundation 一、架构 先来看下官网给的图&#xff1a; 图上显示了Hive的主要组件及其与Hadoop的交互。Hive的主要组件有&#xff1a; UI&#xff1a; 用户向系统提交查询和其他操作的用户界面。截至2011年&…

Linux - 安装 maven(详细教程)

目录 一、下载二、安装三、配置环境变量四、镜像资源配置 一、下载 官网&#xff1a;https://maven.apache.org/download.cgi 打开 maven 的官网下载页面&#xff0c;点击 bin.tar.gz 文件链接 即可下载最新版本的 maven 如果想要下载旧版本的 meven&#xff0c;则点击 Maven…

【短时交通流量预测】基于GRNN神经网络

课题名称&#xff1a;基于GRNN神经网络的短时交通流量预测 版本时间&#xff1a;2023-04-27 代码获取方式&#xff1a;QQ&#xff1a;491052175 或者 私聊博主获取 模型简介&#xff1a; 城市交通路网中交通路段上某时刻的交通流量与本路段前几个时段的交通流量有关&#x…

Python类 __init__() 是一个特殊的方法

设计者&#xff1a;ISDF工软未来 版本&#xff1a;v1.0 日期&#xff1a;2024/3/5__init__() 是一个特殊的方法 类似c# C的构造函数 两头都包含两个下划线&#xff0c;这是约定&#xff0c;用于与普通的函数保持区分class User:用户类def __init__(self,first_name,last_name):…

软件应用,财务收支系统试用版操作教程,佳易王记录账单的软件系统

软件应用&#xff0c;财务收支系统试用版操作教程&#xff0c;佳易王记录账单的软件系统 一、前言 以下软件操作教程以 佳易王账单记账统计管理系统V17.0为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 如上图&#xff0c;统计报表包含 收支汇…

JavaScript基础2之运算符、函数

JavaScript基础 运算符一元操作符递增/递减一元加和减 布尔操作符逻辑非逻辑与逻辑或 乘性操作符乘法操作符除法操作符取模操作符 加性操作符加法操作符减法操作符 比较操作符相等操作符关系操作符 函数函数声明函数表达式箭头函数函数的实参和形参arguments 默认参数参数的拓展…

QUIC来了!

什么是QUIC QUIC&#xff0c;快速UDP网络连接(Quick UDP Internet Connection)的简称&#xff0c;即RFC文档描述它为一个面向连接的安全通用传输协议。其基于UDP协议实现了可靠传输及拥塞控制&#xff0c;简单来说&#xff0c;QUIC TCP TLS。 为什么有了QUIC HTTP2.0为了为了…

如何处理微服务之间的通信和数据一致性?

✨✨祝屏幕前的兄弟姐妹们每天都有好运相伴左右&#xff0c;一定要天天开心哦&#xff01;✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; 目录 引言 一、微服务通信 1、同步通信&#xff1a;HTTP 1.1.同步通信示例代码&#xf…

第四十九回 吴学究双掌连环计 宋公明三打祝家庄-Python与HTTP服务交互

吴用请戴宗从梁山请来铁面孔目裴宣、圣手书生萧让、通臂猿侯健、玉臂匠金大坚来帮忙。又告诫扈家庄的扈成&#xff0c;打起来不要去帮祝家庄。 孙立把旗号改成“登州兵马提辖孙立”&#xff0c;来祝家庄找峦廷玉&#xff0c;被热情接待。 第三天&#xff0c;宋江派小李广花荣…

Vue 路由功能

安装路由 npm install vue-router4创建路由器并导出 //导入vue-router import { createRouter, createWebHistory } from vue-router //导入组件 import LoginVue from /views/Login.vue import LayoutVue from /views/Layout.vue//定义路由关系 const routes [{ path: /log…

安卓玩机工具推荐----ADB状态读写分区 备份分区 恢复分区 查看分区号 工具操作解析

在以往玩机过程中。很多机型备份分区 备份固件需要借助adb手动指令或者第三方手机软件或者特定的一些工具来操作。有些朋友需要查看当前机型分区名称和对应的分区号。此类操作我前面的博文专门说过对应的adb指令。但有些界面化的工具比较方便简单。 相关分区同类博文&#xff…

WPF中如何设置自定义控件(二)

前一篇文章中简要讲解了圆角按钮、圆形按钮的使用,以及在windows.resource和app.resource中设置圆角或圆形按钮的样式。 这篇主要讲解Polygon(多边形)、Ellipse(椭圆)、Path(路径)这三个内容。 Polygon 我们先看一下的源码: namespace System.Windows.Shapes { pu…

性能问题分析排查思路之机器(3)

本文是性能问题分析排查思路的展开内容之一&#xff0c;第2篇&#xff0c;主要分为日志1期&#xff0c;机器4期、环境2期共7篇系列文章&#xff0c;本期是第三篇&#xff0c;讲机器&#xff08;硬件&#xff09;的网络方面的排查方法和最佳实践。 主要内容如图所示&#xff1a…

【短时交通流量预测】基于单层BP神经网络

课题名称&#xff1a;基于单层BP神经网络的短时交通流量预测 版本时间&#xff1a;2023-04-27 代码获取方式&#xff1a;QQ&#xff1a;491052175 或者 私聊博主获取 模型简介&#xff1a; 城市交通路网中交通路段上某时刻的交通流量与本路段前几个时段的交通流量有关&…

【计算机学习】-- 电脑的组装和外设

系列文章目录 文章目录 系列文章目录前言一、电脑的组装1.CPU2.主板3.显卡4.硬盘5.内存6.散热器7.电源8.机箱 二、电脑外设选用1.显示器2.鼠标3.键盘4.音响 总结 前言 一、电脑的组装 1.CPU 返回目录 认识CPU CPU&#xff0c;即中央处理器&#xff0c;负责电脑资源的调度安…

器件选型【电容,电阻篇】

电阻篇&#xff1a; 一句话先做总结&#xff1a;电阻的选型主要考虑额定电压和过流能力&#xff08;基于封装大小&#xff09; 电阻封装规格越大功率越大。但其功率也与温度有关&#xff0c;如果温度超过 70℃&#xff0c;其额定功率是会下降的。并且&#xff0c;R01005 和 R0…

#QT(串口助手-实现)

1.IDE&#xff1a;QTCreator 2.实验 3.记录 &#xff08;1&#xff09;在widget.h中加入必要文件&#xff0c;并且定义一个类指针 &#xff08;2&#xff09;如果有类的成员不知道怎么写&#xff0c;可以通过以下途径搜索 &#xff08;2&#xff09;设置串口数据 void Widget…

AI大全-通往AGI之路

背景 自从AI大模型出来之后&#xff0c;就有很多做资源整理的社区&#xff0c;整理学习资料&#xff0c;整理各种AI工具大全&#xff0c;我也整理过一段时间的最新AI的资讯&#xff0c;也曾尝试去弄一个AI的入口类的东西。但是最近看到一个在飞书上的分享&#xff0c;我觉得他…

IDEA自带 .http 请求工具文档

基础语法 请求格式 基础格式 Method Request-URI HTTP-Version Header-field: Header-valueRequest-Body其中&#xff0c;GET 请求可以省略 Method 不写&#xff1b;HTTP-Version 可以省略不写&#xff0c;默认使用 1.1 版本。 示例&#xff1a; GET https://www.baidu.co…

【Python】成功解决TypeError: list indices must be integers or slices, not float

【Python】成功解决TypeError: list indices must be integers or slices, not float &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&…