linux入门---信号的理解

目录标题

  • 如何理解计算机中的信号
  • 如何查看计算机中的信号
  • 初步了解信号的保存和发送
  • 如何向目标进程发送信号
    • 情景一:使用键盘发送信号
    • 情景二:系统调用发送信号
    • 情景三:硬件异常产生信号
    • 情景四:软件条件产生信号
  • 核心转储
  • 信号的两个问题
    • 问题一
    • 问题二

如何理解计算机中的信号

我们首先通过生活中的信号来理解一下计算机中的信号,生活中会遇到很多的信号,比如说运动会上的发令枪,晚上睡觉前定的闹钟,过马路开车需要查看的红绿灯,以及手机在收到信息发出的响声这也是信号,手机发出的响声是一个信号,并且我们人是可以识别信号的,这里的识别指是人能够认识这个信号,并且我们能因为这个信号而做出对应的行为,那我们为什么能够识别红绿灯呢?为什么当听到手机发出响声之后知道是手机接收到一些信息的呢?原因是有人教育过你有经验告诉过你,当这些信号产生的时候意味着一些事情要被处理,通过教育的手段和以往经验的积累,让你在大脑里面记住了一些信号的属性(为什么会产生这样的信号?)和与之对应处理行为(我们人该如何处理这个信号),比如说我们开车的过程中发现红灯亮了起来,那我们就得在合适的地方将车停下来了,红灯就是一个信号,将车停下来就是对这个信号的处理,那为什么会有红灯这个信号呢?原因是为了维持社会交通的便利,那为什么我们知道红灯是一个信号遇到红灯是就得停车呢?原因是从小爸爸妈妈和老师就教育我们遇到红灯的时候就得停止向前运动直到红灯变成了绿灯,这是一个教育的过程让我们知道了红灯是一个信号,以及信号的处理行为,再比如说手机发出的响声就是一个信号,当手机发出声响的时候我们就知道有人或者有软件给我们发了消息,我们就可以打开手机对消息进行查看,那么我们是如何知道手机发出声响是一个信号的呢?原因是在之前使用手机的过程中每当有人或者有软件给我们发消息时手机发出声响,即使我们不用手机将他放到一边当收到消息的时候他也会发出声响,这样我们人就会积攒经验知道手机发出声响就意味着我们收到了消息,该消息可能要被处理,那么这就是我们为什么会认识信号的原因,那接收到了信号就一定得立即对其进行处理吗?答案是不是的,因为信号时可以随便产生的,但是在信号产生的时候我们可能有更重要的事情要做,比如说手机叮咚响了一声我们就必须得马上打开手机进行查看吗?对吧!没必要我们可以选择忽略这个信号直到我们忙完了再查看这个信号都不晚。信号到来的时候我们不一定要立马处理这个信号,因为我们当前可能得做着更重要的事情,所以信号产生到信号被处理的过程中存在一个时间窗口,所以在这个时间窗口里面我们必须得记住这个信号,那么如何保存信号就是我们后面要学习的内容,我们知道什么是信号是因为有人教育过我们,那对信号的处理是固定的吗?我们可不可以直接忽略掉这个信号,比如说红灯亮起的时候我们依然选着向前移动,手机不停地响但是我们依然选继续干正在做的事,答案是可以的在处理信号的时候我们可以选择直接忽略掉信号,那么同样的道理我们可以修改以往对信号处理的行为吗?比如说之前有人一直教育我们遇到红灯就得停止向前运动,这是一个默认的信号处理行为,我们可以对这个行为进行修改,当红点亮了我们也对对其进行做出行为,但是这个行为是将运动向前变成向后,同样的道理当手机响的时候默认动作是打开手机并查看消息,那么我们能对这个行为进行修改,当手机响了之后我们可以直接将手机进行关机,那么这就是对信号默认行为的修改,看到这里想必大家应该能够理解信号,但是大家心里一定存在个疑问,人可以被教育可以从经验中总结结果,那计算机又是如何认识信号的呢?进程识别信号的方式就是:认识信号+处理信号,而信号是发送给进程的,那进程如何识别信号的呢?人能够认识信号是因为人受到过教育,进程能够认识信号则是因为进程本身是程序员编写的属性和逻辑的集合,这些都是程序员编码完成的,所以我们写的程序能够识别到信号,当进程收到信号的时候,进程可能正在执行更重要的代码,所以信号不一定会被立即处理,所以进程本身必须要有对信号的保存能力,进程在处理信号的时候一般有三种动作(默认,自定义,忽略),我们把处理信号的动作称为信号捕捉。那么接下来我们就来逐步逐步的理解信号。

如何查看计算机中的信号

kill -l可以查看所有的信号
在这里插入图片描述
数字就表示信号的编号,数字后面的名称就是数字对应的宏,所以未来我们既可以使用编号也可以使用宏来操控信号,通过观察我们可以看到一共有62个信号,我们把1-31称为普通信号 34-64称为实时信号(这个不学)。

初步了解信号的保存和发送

在前面的介绍中我们知道信号发送之后可能不会立即处理,所以得将信号保存起来,那信号应该保存在哪里呢?答案是保存在task_struct里面,那该保存什么内容呢?答案是保存是否收到了指定的信号,如果收到了指定的信号就保存1没有收到就保存0,那么这里有31个信号,所以在task_struct里面就有一个unsigned int signal来存储是否收到了信号,比特位的位置代表信号的编号,比特位的内容,代表是否收到了该信号,0表示没有,1表示有,既然存储信号的方法是通过PCB中的一个位图来进行存储,那么发送信号的本质就是修改PCB中的信号位图,将位图中的某个比特位由0变成1,PCB是内核维护的数据结构对象,所以PCB的管理者是操作系统,那谁有权利修改PCB的内容呢?答案是操作系统,所以无论未来我们学习了多少种发送信号的方式,本质都是通过操作系统向目标进程发送信号,所以操作系统必须要提供发送信号处理型号的相关系统调用,我们使用的kill命令底层一定是调用了对应的系统调用,比如说我们提供了下面这样的程序:

#include<iostream>
#include<unistd.h>
using namespace std;
int main()
{while(1){cout<<"我是一个死循环的进程"<<endl;sleep(1);}return 0;
}

生成可执行文件
在这里插入图片描述
然后运行一下程序就可以看到下面这样的场景:
在这里插入图片描述
可以看到这里在死循环的打印语句,那么想要结束这个进程我们就可以按下键盘上的ctrl+c,然后我们就可以程序终止了:
在这里插入图片描述
那么ctrl+c就是一个热键—本质就是一个组合键,操作系统会将这个组合键识别成成为二号信号发送给进程,通过man 7 signal可以查看所有信号的信息和与之对应的默认操作:
在这里插入图片描述
signal是信号的名称,value表示信号的值,action表示信号的动作,comment是信号的描述,通过这张图片我们便可以知道二号信号的名称就是sigint,动作是Term对这个信号的描述就是用键盘来中断当前运行的进程,所以我们可以看到程序停止向屏幕上进行打印了,那么为了验证这一点我们可以用signal函数来进行判断,signal函数的作用就是修改信号的处理动作
在这里插入图片描述
signal函数的第一个参数表示信号的编号也就是你要对哪个信号进行修改,第二个参数的类型为sighandler_t通过上面显示我们知道sighandler是一个重命名类型,该类型的本质就是一个函数指针,函数的返回类型是void参数就是一个整形,该参数的作用就是将信号原来的默认动作修改成函数指针指向的函数,比如说下面的程序:

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
using namespace std;
void handler(int sign)
{cout<<"我收到了信号,信号的编号为:"<<sign<<endl;
}int main()
{signal(2,handler);while(1){cout<<"我是一个死循环的进程,我的pid为:"<<getpid()<<endl;sleep(1);}return 0;
}

运行的结果如下:
在这里插入图片描述
可以看到程序的运行是没有问题的,然后当我们按下ctrl c给进程发送信号的时候程序并不会终止,而是执行了我们之前设置好的内容,打印了一句话:
在这里插入图片描述
打印完这句话后接着执行死循环:
在这里插入图片描述
那么这也就验证了,当我们使用键盘输入ctrl c的时候本质上就是给当前的进程发送2号信号,2号信号的作用就是将当前的程序终止。

如何向目标进程发送信号

情景一:使用键盘发送信号

通过前面的学习我们知道使用组合键ctrl c可以向前台进程发送信号从而终止进程:
在这里插入图片描述
那么这里还有一个ctrl \的快捷键,他的作用就是给前台进程发送3号信号从而终止进程,那么这里的代码如下:
在这里插入图片描述
那么这就是通过键盘向进程发送信号。

情景二:系统调用发送信号

1.kill函数
我们学习的第一个函数就是kill,该函数的声明形式如下:
在这里插入图片描述
第一个参数表示向哪个进程发送信号,第二个参数表示向进程发送几号信号,我们再看看这个函数的返回值:
在这里插入图片描述
可以看到当信号发送成功就返回0,如果失败就返回-1,那么这就是该函数的作用,那么接下来我们就可以写一个人程序,专门用来给指定的进程发送信号,程序使用的方法就是运行程序的时候传递两个参数第一个参数表示进程的pid第二个参数用来表示发送的具体信号比如说这样:./myproc 进程的pid 信号的编号,那么我们的程序就可以这样设计,因为运行程序的时候会传递参数,所以该程序中的main函数得包含两个参数:

#include<iostream>
using namespace std;
int main(int argc,char*argv[])
{}

然后在程序的开始我们得判断一下该程序的使用是否正确,如果传递argc的值不等于3的话我们就得调用函数来告诉使用者该程序的正确使用方法,所以该函数就得有一个参数用来告诉使用者是哪个进程的使用方法:

#include<iostream>
#include<string>
using namespace std;
void usage(const string& proc)
{cout<<"\nusage:"<<proc<<"pid signo"<<endl;
}
int main(int argc,char*argv[])
{if(argc!=3){usage(argv[0]);exit(1);}
}

然后我们就可以通过main函数的第二个参数来获取对应的pid和信号,然后将其转换成为整形最后就可以调用kill函数发送对应的信号即可:

#include<iostream>
#include<string>
#include<signal.h>
#include<stdlib.h>
#include<sys/types.h>
using namespace std;
void usage(const string& proc)
{cout<<"\nusage:"<<proc<<"pid signo\n"<<endl;
}
int main(int argc,char*argv[])
{if(argc!=3){usage(argv[0]);exit(1);}pid_t pid=atoi(argv[1]);int signo=atoi(argv[2]);kill(pid,signo);return 0;
}

那么接下来我们就可以先测试一下myproc.cc文件是否运行正常:
在这里插入图片描述
可以看到当前的程序是可以正常运行的,那么这里我们就可以先运行一下死循环程序:
在这里插入图片描述
然后再打开一个回话,并运行myproc程序并传递20261和2号信号:
在这里插入图片描述
然后就可以看到程序自动的终止了,同样的道理我们还可以发送3号信号给进程,这样我们就可以看到打印出来quit的字样:
在这里插入图片描述
那么这就是kill函数的用法。

第二个:raise
该函数的声明如下:
在这里插入图片描述
这个函数也可以给进程发送信号,但是他不能指定任意进程,他只能给自己发送对应的信号,比如说下面的代码:

int main()
{int cnt=1;while(true){cout<<"cnt的值为: "<<cnt<<endl;cnt++;if(cnt==4){raise(2);}}return 0;
}

那么这里我们就可以看到屏幕上打印了3句话话之后就自动的结束了进程,运行的结果如下:
在这里插入图片描述
那么这就是raise函数的用法。

第三个:abort
kill函数能够对任意的进程发送任意的信号,raise函数能够给本进程发送任意的信号,那么abort函数就只能给本进程发送指定的信号也就是6号新号,我们来看看这个函数的介绍:
在这里插入图片描述
我们可以看看6号新号的名字:
在这里插入图片描述
可以看到6号信号对应的宏就是SIGABRT,那么这里就不再介绍,大家理解了即可。

情景三:硬件异常产生信号

信号的产生,不一定非得用户显示的发送,比如说除0会终止进程的时候会终止进程,比如说下面的代码:

int main()
{int i=10;int j=0;int c=i/0;while(true){cout<<"如果没有收到信号就会死循环"<<endl;}return 0;
}

将程序运行起来便可以看到下面的场景:
在这里插入图片描述可以看到这里没有死循环的打印语句,而是报出了异常的内容,那么这就说明当出现除0情况是进程是收到信号的,那这个信号是谁发送的呢?答案是当前进程会受到来自操作系统的信号并且是8号SIGFPF信号,那么这里我们可以使用signal函数修改一下对应的8号信号,修改的操作就是不终止信号并输出信息:

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
using namespace std;
void handler(int sign)
{cout<<"我收到了信号,信号的编号为:"<<sign<<endl;
}int main()
{signal(8,handler);int i=10;int j=0;int c=i/0;while(true){cout<<"如果没有收到信号就会死循环"<<endl;}return 0;
}

程序的运行代码如下:
在这里插入图片描述
可以看到出现/0错误的时确实会收到8号新号,并且会不停的执行8号信号的处理方法,那么这里就存在一个问题我们只除了一次0,为什么会不停的捕捉呢?操作系统又是如何得知应该给当前进程发送8号信号的呢?那么要想解决这里的问题我们就得了解一下cpu的构造,cpu中存在着大量的寄存器,这些寄存器会存储大量的数据比如说各种表达式或者函数的计算结果,寄存器不仅得存储计算的结果还得保存表达式的计算状态,所以有一种寄存器称为状态寄存器,该寄存器中存在一个比特位称为溢出标记位,10除以0得到的结果是无穷大,那么无穷大这个结果就会导致状态寄存器中的溢出由0变成了1,虽然这次表达式计算出现了问题,但是寄存器中依然会存储一些数据(这里就体现出溢出标记位的作用,因为都会保存一些值,那么这个值是否是对的就有状态寄存器来决定),cpu出现运算异常,那么操作系统是肯定要知道,所以操作系统就可以通过状态寄存器上的标记位知道发生了什么异常?然后再查询当前运行的是哪个进程以及进程运行的位置这样便知道是哪个进程的哪里出现了异常,然后操作系统修改标记位并发送信号,那为什么会一直打印信息呢?我们之前说过收到信号进程不一定会退出,没有退出的话就说明该进程还会被调度,cpu内部的寄存器只有一份,但是寄存器中的内容属于当前进程的上下文,一旦出现了异常了我们修改之后的动作有能力或者行为来修改这个问题吗?答案是没有的,所以进程会被多次切换,与之对应的寄存器的内容就会被多次保存或者回复,所以每一次回复的时候操作系统就会识别到状态寄存器中的溢出标记位,然后修改进程pcb中的标记位并发送信号所以就可以看到当我们对8号信号的动作进行修改时会不停执行8号信号的处理动作。同样场景也会出现在对野指针解引用上,当我们对野指针进行解引用时也会出现崩溃,结束进程,比如说对nullptr地址进行解引用,那么这个时候就会发送11号信号,那操作系统为什么会知道出现野指针的错误呢?答案是虚拟地址转换到物理地址需要页表加上mmu,mmu是内存管理单元这是一个硬件集成在cpu上,当我们访问0号地址时,虚拟地址空间会拒绝我们的访问,然后mmu就会因为我们的越界访问出现了异常,当一个硬件发生了异常时,操作系统便会得知在地址转换的过程中出现了异常,然后操作系统就会发送11号信号,按摩这就是硬件层面上的异常。

情景四:软件条件产生信号

我们之前学习过进程之间的通信,在那里我们知道了管道文件的特点,当管道的读端关闭,写段一直写的时候,操作系统就会通过发送sigpipe信号的方式来终止进程,那么这就是一个经典的软件条件产生的异常,那么这里我们再举一个例子:alarm函数:
在这里插入图片描述

alarm函数的作用就是让程序运行一段时间,时间到了就会发送14号-信号来结束进程,那么alarm的参数就表示在多少秒之后发送信号,所以我们就可以使用闹钟来实现下面这样的代码:

int main()
{alarm(1);//程序1秒后会被信号终止int cnt=0;while(true){cout<<"cnt: "<<cnt++<<endl;}return 0;
}

然后程序的运行结果如下:
在这里插入图片描述
可以看到循环执行了6w多次,那么这段代码的意义就是统计1s左右,我们的计算机能够将数据累加多少次,但是大家不难发现1秒访问6万多次好想有点少,那么这里的原因就是在循环里面要不停的访问外设显示屏,所以运行的速度非常的慢,那么下面我们可以对代码进行修改将cnt改成全局变量,循环里面就不往屏幕上打印数据而是直接对变量++

int cnt=0;
void handler(int sign)
{cout<<"我收到了信号,信号的编号为:"<<sign<<endl;cout<<"cnt的值为: "<<cnt<<endl;exit(1);
}
int main()
{signal(SIGALRM,handler);alarm(1);//程序1秒后会被信号终止while(true){++cnt;}return 0;
}

代码的运行结果如下:
在这里插入图片描述
可以看到这时程序的运行速度就变得十分的快,但是我们为什么把这个函数发出的异常称为软件异常呢?因为闹钟其实就是用软件实现的,任意一个进程都能通过alarm系统调用在内核中设置闹钟,os中可能会存在很多的闹钟,那么操作系统要不要对这些闹钟进行管理呢?答案是要的,管理的方式就是先描述再组织,所以操作系统就会有对应的结构体来描述闹钟,然后操作系统中就会有数据结构来管理这些结构体,比如说链表,堆,操作系统就会不停的检查这些对象中是否有哪些闹钟超时,超时了操作系统就会给对应的进程发送信号,检查闹钟是否超时是操作系统这样的软件来执行的,条件就是现在超时这个条件,所以闹钟就是软件条件。

核心转储

在这里插入图片描述
在信号的行为中我们知道Term和Core都是将进程终止,那这两种终止方式有什么区别呢?term的终止表示是正常的结束,操作系统不会做额外的操作,而core的终止表示不是正常的终止,操作系统会做出额外的操作,比如说SIGFPF就是一个Core类型的终止,当我们写出这样的代码时操作系统就会给我们发送该类型的异常:

int main()
{while(true){int arr[10];arr[10000]=100;}return 0}

运行的结果如下:
在这里插入图片描述
可以看到这里确实发生了段错误,但是在云服务器上如果进程是core终止我们暂时看不到明显的现象,如果想看到我们得打开一个选项:ulimit -a查看云服务器的各种选项
在这里插入图片描述
这里会显示当前云服务器的各种性质,比如说管道的大小,最多能打开的文件个数,其中有个属性为core file size,他表示表示核心转储的大小,如果大小为0的话就表示云服务器默认关闭了核心转储,如果想要看到的话 ulimit -c 1024 表示打开核心转储并设计一个大小为1024的数据块
在这里插入图片描述
再运行一下当前的程序就会出现core dumped的标识

在这里插入图片描述

这个core dumped就是核心转储的意思,并且当前路径下还多出来了一个文件,文件名后面有一串数字表示引起核心转储的进程的pid

在这里插入图片描述

核心转储的意思就是:当进出现异常的时候,我们将进程对应的时刻在内存中的有效数据转储到磁盘中就称为核心转储。我们可以查看一下文件里面的内容:

在这里插入图片描述

文件里面的内容我们是看不懂的,那为什么要有核心转储呢?原因是当进程崩溃的时候,我们想知道进程为什么会崩溃,在哪里崩溃,所以操作系统为了方便我们查看程序崩溃的原因,操作系统就会该进程上下文的数据保存到磁盘中用来支持我们调试,那如何查看这些数据呢?答案是先调试我们的文件
在这里插入图片描述

然后输入core-file 新生成的核心转储文件

在这里插入图片描述

按下回车然后就会显示当前出错的原因:

在这里插入图片描述
大家仔细的观察一下便可以看到上面显示了在程序的第18行出现了异常,第18行的内容为arr[10000]=100;那么这就是核心转储的功能,他可以告诉我们程序在哪出现了异常。

信号的两个问题

问题一

在这里插入图片描述
通过上面的图片我们可以看到很多信号的处理行为都是直接将进程终止,那既然处理的行为都是终止的话那为什么还要分这么多种信号呢?意义是什么呢?答案是不同的信号代表不同的事件,但是对于不同的事件处理的动作可以是一样的,这就意味着虽然都是直接将进程终止,但是出现了不同的信号可以让我们知道是哪些原因导致了信号的发出,然后我们就可以根据这些原因来更改程序,那么这就是不同信号的意义。

问题二

我们能不能对所有的信号都进行自定义捕捉?比如说下面的程序:

void handler(int sign)
{cout<<"我收到了信号,信号的编号为:"<<sign<<endl;
}
int main()
{for(int i=0;i<=31;i++){signal(i,handler);}while(true){cout<<"我是一个进程,我的pid为:"<<getpid()<<endl;}return 0;
}

如果我们对所有的信号都做捕捉,那是不是就不能杀掉这个进程了呢?程序运行的结果如下:
在这里插入图片描述
我们之前讲过2号新号和3号新号都可以结束进程,那现在还能够结束掉吗?我们来尝试一下:
在这里插入图片描述
可以看到是不行的,并且其他的一些信号也不行
在这里插入图片描述
那么这是不是就说明这个进程无法被终止掉了呢?不会的我们使用9号信号依然可以将其 终止掉:
在这里插入图片描述
所以操作系统为了能够结束恶意进程是不允许修改某些信号的处理方法的我们把这样信号称为管理员信号,那么这就是本篇文章的全部内容希望大家能够理解。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

React项目部署 - Nginx配置

写在前面&#xff1a;博主是一只经过实战开发历练后投身培训事业的“小山猪”&#xff0c;昵称取自动画片《狮子王》中的“彭彭”&#xff0c;总是以乐观、积极的心态对待周边的事物。本人的技术路线从Java全栈工程师一路奔向大数据开发、数据挖掘领域&#xff0c;如今终有小成…

纯css实现3D鼠标跟随倾斜

老规矩先上图 为什么今天会想起来整这个呢?这是因为和我朋友吵架, 就是关于这个效果的,就是这个 卡片懸停毛玻璃效果, 我朋友认为纯css也能写, 我则坦言他就是在放狗屁,这种跟随鼠标的3D效果要怎么可能能用纯css写, 然后吵着吵着发现,欸,好像真能用css写哦,我以前还写过这种…

深度学习-卷积神经网络-AlexNET

文章目录 前言1.不同卷积神经网络模型的精度2.不同神经网络概述3.卷积神经网络-单通道4.卷积神经网络-多通道5.池化层6.全连接层7.网络架构8.Relu激活函数9.双GPU10.单GPU模型 1.LeNet-52.AlexNet1.架构2.局部响应归一化&#xff08;VGG中取消了&#xff09;3.重叠/不重叠池化4…

Nginx与Spring Boot的错误模拟实践:探索502和504错误的原因

文章目录 前言502和504区别---都是Nginx返回的access.log和error.log介绍SpringBoot结合Nginx实战502 and 504准备工作Nginx配置host配置SpringBoot 502模拟access.logerror.log 504模拟access.logerror.log 500模拟access.logerror.log 总结 前言 刚工作那会&#xff0c;最常…

beego-简单项目写法--后续放到git上

Beego案例-新闻发布系统 1.注册 后台代码和昨天案例代码一致。,所以这里面只写一个注册的业务流程图。 **业务流程图 ** 2.登陆 业务流程图 登陆和注册业务和我们昨天登陆和注册基本一样&#xff0c;所以就不再重复写这个代码 但是我们遇到的问题是如何做代码的迁移&…

Folium 笔记:MarkerCluster

在一张地图上以聚簇的形式显示大量的标记&#xff08;markers&#xff09; 举例&#xff1a; import folium from folium.plugins import MarkerCluster import randomm folium.Map(location[45.5236, -122.6750], zoom_start13) # 创建一个基本的地图marker_cluster Marker…

基于蝴蝶优化的BP神经网络(分类应用) - 附代码

基于蝴蝶优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于蝴蝶优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.蝴蝶优化BP神经网络3.1 BP神经网络参数设置3.2 蝴蝶算法应用 4.测试结果&#xff1a;5.M…

Python 列表推导式深入解析

Python 列表推导式深入解析 列表推导式是 Python 中的一种简洁、易读的方式&#xff0c;用于创建列表。它基于一个现有的迭代器&#xff08;如列表、元组、集合等&#xff09;来生成新的列表。 基本语法&#xff1a; 列表推导式的基本形式如下&#xff1a; [expression for…

SRT服务器SLS

目前互联网上的视频直播有两种&#xff0c;一种是基于RTMP协议的直播&#xff0c;这种直播方式上行推流使用RTMP协议&#xff0c;下行播放使用RTMP&#xff0c;HTTPFLV或者HLS&#xff0c;直播延时一般大于3秒&#xff0c;广泛应用秀场、游戏、赛事和事件直播&#xff0c;满足了…

nodejs+vue游戏测评交流系统elementui

可以实现首页、发布招募、公司资讯、我的等&#xff0c;另一方面来说也可以提高在游戏测评交流方面的效率给相关管理人员的工作带来一定的便利。在我的页面可以对游戏攻略、我的收藏管理、实际上如今信息化成为一个未来的趋势或者可以说在当前现代化的城市典范中,发布招募等功能…

中秋时节赏明月,五子棋戏月饼趣 — Flutter中秋限定版五子棋

前言 当中秋时节来临&#xff0c;我们都期待着与亲人朋友共度这个美好的节日。这个时候&#xff0c;除了传统的赏月和品尝美味的月饼&#xff0c;我还有一个特别的建议——尝试一款有趣的Flutter五子棋游戏&#xff01;这款五子棋游戏以中秋为主题&#xff0c;游戏的棋子也可爱…

微服务的初步使用

环境说明 jdk1.8 maven3.6.3 mysql8 idea2022 spring cloud2022.0.8 微服务案例的搭建 新建父工程 打开IDEA&#xff0c;File->New ->Project&#xff0c;填写Name&#xff08;工程名称&#xff09;和Location&#xff08;工程存储位置&#xff09;&#xff0c;选…

人工智能:创新之路

随着时光的推移&#xff0c;人工智能&#xff08;Artificial Intelligence&#xff0c;简称AI&#xff09;已然成为现代科技领域的焦点。AI不再是科幻小说或电影的幻想&#xff0c;而是如今社会生活和商业领域的重要一部分。本文将回顾人工智能的发展历程&#xff0c;探讨其现状…

Qt 综合练习小项目--反金币(2/2)

目录 4 选择关卡场景 4.2 背景设置 4.3 创建返回按钮 4.3 返回按钮 4.4 创建选择关卡按钮 4.5 创建翻金币场景 5 翻金币场景 5.1 场景基本设置 5.2 背景设置 5.3 返回按钮 5.4 显示当前关卡 5.5 创建金币背景图片 5.6 创建金币类 5.6.1 创建金币类 MyCoin 5.6.…

基于SpringBoot的图书进销存管理系统

目录 前言 一、技术栈 二、系统功能介绍 用户信息管理 图书类型管理 商品退货管理 客户信息管理 图书添加 客户添加 应收金额 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实…

对象存储,从单机到分布式的演进

关于数据存储的相关知识,请大家关注“数据存储张”,各大平台同名。 通过《什么是云存储?从对象存储说起》我们对对象存储的历史、概念和基本使用有了一个大概的认识。而且我们以Minio为例,通过单机部署的模式实际操作了一下对象存储的GUI,感受了一下对象存储的用法。 在上…

Linux Vi编辑器基础操作指南

Linux Vi编辑器基础操作指南 Linux中的Vi是一个强大的文本编辑器&#xff0c;虽然它有一些陡峭的学习曲线&#xff0c;但一旦掌握了基本操作&#xff0c;它就变得非常高效。以下是Vi编辑器的一些基本用法&#xff1a; 打开Vi编辑器&#xff1a; vi 文件名退出Vi编辑器&#xff…

安装matplotlib__pygame,以pycharm调入模块

安装pip 安装matplotlib 安装完毕&#xff0c;终端输入pip list检查 导入模块出现bug&#xff0c;发现不是matplotlib包的问题&#xff0c;pycharm版本貌似不兼容&#xff0c;用python编辑器可正常绘图&#xff0c;pygame也可正常导入。 ​​​​​​​ pycharm版本问题解决 终…

nodejs+vue中医体质的社区居民健康管理系统elementui

可以实现首页、中医体质量表、健康文章、健康视频、我的等&#xff0c;在我的页面可以对医生、小区单元、医疗药品等功能进行操作。目前主要的健康管理系统是以西医为主&#xff0c;而为了传扬中医文化&#xff0c;提高全民健康意识&#xff0c;解决人民日益增长的美好生活需要…

GPX可视化工具 GPX航迹预览工具

背景 当我们收到别人分享的航迹文档&#xff0c;即gpx文档时&#xff0c;如何快速的进行浏览呢&#xff1f;我们可以使用GIS软件来打开gpx文档并显示gpx中所记录的航迹&#xff0c;例如常用的GIS软件有googleEarth&#xff0c; Basecamp&#xff0c; GPXsee&#xff0c; GPX E…