Linux 二十一章

server/2024/9/22 23:10:20/

🐶博主主页:@ᰔᩚ. 一怀明月ꦿ 

❤️‍🔥专栏系列:线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C++,linux

🔥座右铭:“不要等到什么都没有了,才下定决心去做”

🚀🚀🚀大家觉不错的话,就恳求大家点点关注,点点小爱心,指点指点🚀🚀🚀

目录

非阻塞等待

当父进程进行非阻塞等待的时候,父进程完成其他的任务,比如download、printlog、show

进程等待的必要性

进程程序替换

execl

execlp

execv

execvp

替换c++程序

替换shell语言程序

exec族函数为什么可以替换不同语言的程序?

当一个程序运行的整个过程


非阻塞等待

在 Linux 中,可以使用非阻塞方式等待子进程退出或状态改变。这通常通过设置 waitpid() 函数的选项参数中的 WNOHANG 标志来实现。这样,waitpid() 函数将立即返回,而不会阻塞当前进程。

事例

[BCH@hcss-ecs-6176 11_7]$ cat myprocess.c
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
void Worker(int cnt)
{printf("I am a child ,pid: %d, cnt:%d\n",getpid(),cnt);}
int main()
{pid_t id=fork();if(id==0)//child{int cnt=5;while(cnt--){Worker(cnt);sleep(2);}exit(0);}while(1){//fateherint status=0;pid_t rid=waitpid(id,&status,WNOHANG);if(rid>0){printf("child quit success,exit code:%d exit sign:%d\n",(status>>8)&0xFF,status&0x7F);}else if(rid==0){printf("father do other thing......\n");}else{printf("wait fail\n");}sleep(1);}return 0;
}[BCH@hcss-ecs-6176 11_7]$ ./myprocess
father do other thing......
I am a child ,pid: 20429, cnt:4
father do other thing......
I am a child ,pid: 20429, cnt:3
father do other thing......
father do other thing......
I am a child ,pid: 20429, cnt:2
father do other thing......
father do other thing......
I am a child ,pid: 20429, cnt:1
father do other thing......
father do other thing......
I am a child ,pid: 20429, cnt:0
father do other thing......
father do other thing......
child quit success,exit code:0 exit sign:0
wait fail
wait fail
wait fail
wait fail
wait fail
wait fail
wait fail
wait fail[BCH@hcss-ecs-6176 ~]$ while :; do ps ajx | grep myprocess | grep -v grep ; sleep 1 ; echo "================================="; done
=================================
=================================
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
20428 20429 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
20428 20429 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
20428 20429 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
20428 20429 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
20428 20429 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
20428 20429 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
20428 20429 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
20428 20429 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
20428 20429 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
20428 20429 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess
=================================2499 20428 20428  2499 pts/4    20428 S+    1000   0:00 ./myprocess

当父进程进行非阻塞等待的时候,父进程完成其他的任务,比如download、printlog、show

[BCH@hcss-ecs-6176 11_7]$ cat myprocess.c
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>//定义父进程的任务的个数
#define TASK_NUM 5//重定义函数指针类型
typedef void(*task_t)();//这里的这些功能并没有真正实现,而是输出一段字符串
//执行下载的任务
void download()
{printf("this is a download task is running!\n");
}//执行写入的任务
void printlog()
{printf("this is a write log task is running!\n");
}//执行查看的任务
void show()
{printf("this is a info task is runing!\n ");
}//将函数指针数组初始化
void initTask(task_t tasks[])
{int i=0;for(;i<TASK_NUM;i++){tasks[i]=NULL;}
}//添加任务到,函数指针数组中
int addTask(task_t tasks[],task_t t)
{int i=0;for(;i<TASK_NUM;i++){if(tasks[i]==NULL){tasks[i]=t;return 1;}}return 0;
}//执行函数指针数组里的任务
void executeTask(task_t tasks[],int num)
{int i=0;for(;i<num;i++){if(tasks[i])tasks[i]();}
}//子进程的执行的任务
void Worker(int cnt)
{printf("I am a child ,pid: %d, cnt:%d\n",getpid(),cnt);}int main()
{//创建函数指针数组,用于存储各种任务(函数)task_t tasks[TASK_NUM];initTask(tasks);addTask(tasks,download);addTask(tasks,printlog);addTask(tasks,show);//创建子进程,id>0执行父进程,id==0执行子进程pid_t id=fork();if(id==0)//child{int cnt=5;while(cnt--){Worker(cnt);sleep(1);}_exit(0);}while(1){//fateherint status=0;//记录子进程退出的退出码和信号pid_t rid=waitpid(id,&status,WNOHANG);//通过waitpid系统调用去等待子进程,采用WNOHANG非阻塞的方式等待if(rid>0){//wait success, child quit nowprintf("child quit success,exit code:%d exit sign:%d\n",(status>>8)&0xFF,status&0x7F);}else if(rid==0){printf("##############################################################################################\n");//wait success, but child not quitprintf("father do other thing......\n");executeTask(tasks,TASK_NUM);//也可以在内部进行自己移除&&新增对应的任务printf("##############################################################################################\n");}else{//wait failed, child unknowprintf("wait fail\n");}sleep(1);}return 0;
}[BCH@hcss-ecs-6176 11_7]$ ./myprocess
##############################################################################################
father do other thing……//说明此时rid==0,父进程开始执行其他任务
this is a download task is running!
this is a write log task is running!
this is a info task is runing!##############################################################################################
I am a child ,pid: 23948, cnt:4
##############################################################################################
father do other thing......
this is a download task is running!
this is a write log task is running!
this is a info task is runing!##############################################################################################
I am a child ,pid: 23948, cnt:3
##############################################################################################
I am a child ,pid: 23948, cnt:2
father do other thing......
this is a download task is running!
this is a write log task is running!
this is a info task is runing!##############################################################################################
I am a child ,pid: 23948, cnt:1
##############################################################################################
father do other thing......
this is a download task is running!
this is a write log task is running!
this is a info task is runing!##############################################################################################
I am a child ,pid: 23948, cnt:0
##############################################################################################
father do other thing......
this is a download task is running!
this is a write log task is running!
this is a info task is runing!##############################################################################################
##############################################################################################
father do other thing......
this is a download task is running!
this is a write log task is running!
this is a info task is runing!##############################################################################################
child quit success,exit code:0 exit sign:0//rid>0,此时子进程退出,父进程接收到子进程的退出信息和退出码
wait fail//子进程结束,waitpid捕获不到该子进程的信息,所以rid<0
wait fail

进程等待的必要性

如果不进行进程的等待,有可能形成僵尸进程

进程等待我们现在一般使用的阻塞等待,因为阻塞等待足够简单!

进程程序替换

我们所创建的所有子进程,执行的代码,都是父进程代码的一部分!

如果我们想让子进程执行新的程序呢???执行全新的代码和访问全新的数据,不在和父进程有瓜葛

那需要程序替换

execl

execl 是一个系统调用,用于在当前进程中执行一个新的程序。它的原型如下:

#include <unistd.h>
int execl(const char *path, const char *arg0, const char *arg1, ..., const char *argn, (char *) NULL);path 参数是要执行的新程序的路径。
arg0, arg1, ..., argn 是新程序的命令行参数,最后一个参数必须是空指针 (char *) NULL,表示参数列表的结束。


execl 函数会取代当前进程的内存映像,加载并执行指定路径的新程序,新程序的参数由 arg0 到 argn 指定。执行 execl 后,当前进程的代码、数据、堆栈等内容都被替换为新程序的内容,新程序开始执行。

如果 execl 函数执行成功,它不会返回;如果发生错误,返回值为 -1,并且设置全局变量 errno 表示具体的错误类型。

这个函数在创建子进程后常用于在子进程中执行其他程序,因为在子进程中执行 execl 后,子进程的内容就被替换为新程序的内容,从而达到执行其他程序的效果。

事例

我们可以用“语言”调用其他程序
[BCH@hcss-ecs-6176 11_7_1]$ cat test.c
#include<stdio.h>
#include<unistd.h>
int main()
{printf("pid:%d,excel command begin\n",getpid());execl("/usr/bin/ls","ls","-a","-l",NULL);//execl调用的ls程序printf("pid:%d,excel command end\n",getpid());return 0;
}[BCH@hcss-ecs-6176 11_7_1]$ ./test
pid:4066,excel command begin
总用量 24
drwxrwxr-x  2 BCH BCH 4096 11月 10 18:05 .
drwx------ 26 BCH BCH 4096 11月 10 18:04 ..
-rwxrwxr-x  1 BCH BCH 8464 11月 10 18:05 test
-rw-rw-r--  1 BCH BCH  208 11月 10 18:04 test.c

替换的底层原理

证明程序替换并没有创建新进程

[BCH@hcss-ecs-6176 11_7]$ cat myprocess.c
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{pid_t id=fork();if(id==0){//childprintf("pid:%d,excel command begin\n",getpid());sleep(3);execl("/usr/bin/ls","ls","-a","-l",NULL);printf("pid:%d,excel command end\n",getpid());}else{//fatherpid_t rid=waitpid(-1,NULL,0);if(rid>0){printf("wait success,rid:%d\n",rid);}}return 0;
}[BCH@hcss-ecs-6176 11_7]$ ./myprocess
pid:11527,excel command begin
总用量 56
drwxrwxr-x  2 BCH BCH  4096 11月 10 18:08 .
drwx------ 26 BCH BCH  4096 11月 10 18:04 ..
-rw-rw-r--  1 BCH BCH  1928 11月 10 16:30 1
-rw-rw-r--  1 BCH BCH    74 11月  9 22:13 Makefile
-rwxrwxr-x  1 BCH BCH  8624 11月 10 17:59 myprocess
-rw-rw-r--  1 BCH BCH  2672 11月 10 17:59 myprocess.c
-rw-r--r--  1 BCH BCH 12288 11月 10 18:08 .myprocess.c.swo
-rw-r--r--  1 BCH BCH 12288 11月  8 23:05 .myprocess.c.swp
wait success,rid:11527pid和rid的值一样,可以说明,execl的时候没有创建新的进程

当父进程创建子进程,子进程发生了替换的时候,这里发生的写时拷贝,写时拷贝的同时不仅要拷贝数据还要拷贝代码,这样子进程发生程序替换的时候,不会影响父进程

子进程怎么知道,要从新的程序的最开始执行?

他怎么知道最开始的地方在哪里呢?

Eip寄存器:虽然cpu中只有一个eip寄存器,但是可以存储多组数据,也就是每一个进程都都会有一组数据

当替换进来的程序,eip会找到entry,可执行程序的入口地址

注意:当通过execl程序替换成功了,则后续代码没有机会再执行了!因为被替换掉了!

如果替换失败,会有一个返回值-1,替换成功,不会返回

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{pid_t id=fork();if(id==0){//childprintf("pid:%d,excel command begin\n",getpid());sleep(3);int n=execl("/usr/bin/lsss","lsss","-a","-l",NULL);//execl程序替换错误时,n接收返回值printf("pid:%d,excel: n=%d  command end\n",getpid(),n);}else{//fatherpid_t rid=waitpid(-1,NULL,0);if(rid>0){printf("wait success,rid:%d\n",rid);}}return 0;
}[BCH@hcss-ecs-6176 11_7]$ ./myprocess
pid:29364,excel command begin
pid:29364,excel: n=-1  command end
wait success,rid:29364

execlp

execlp 是一个系统调用,与 execl 类似,用于在当前进程中执行一个新的程序,但它可以搜索 PATH 环境变量指定的路径来寻找可执行文件。它的原型如下:

#include <unistd.h>
int execlp(const char *file, const char *arg0, const char *arg1, ..., const char *argn, (char *) NULL);
file 参数是要执行的新程序的文件名或路径。如果 file 中不包含斜杠 /,则 execlp 函数会在 PATH 环境变量指定的路径中搜索与 file 匹配的可执行文件。
arg0, arg1, ..., argn 是新程序的命令行参数,最后一个参数必须是空指针 (char *) NULL,表示参数列表的结束。


与 execl 类似,execlp 函数也会取代当前进程的内存映像,加载并执行指定路径的新程序,新程序的参数由 arg0 到 argn 指定。执行 execlp 后,当前进程的代码、数据、堆栈等内容都被替换为新程序的内容,新程序开始执行。

如果 execlp 函数执行成功,它不会返回;如果发生错误,返回值为 -1,并且设置全局变量 errno 表示具体的错误类型。

execlp 常用于在当前进程中执行其他程序,且无需指定程序的绝对路径,只需指定程序的名称即可,因为它会在 PATH 环境变量指定的路径中搜索可执行文件。

事例

[BCH@hcss-ecs-6176 11_7]$ cat myprocess.c
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{pid_t id=fork();if(id==0){//childprintf("pid:%d,excel command begin\n",getpid());sleep(3);//execl("/usr/bin/ls","ls","-a","-l",NULL);execlp("ls","ls","-a","-l",NULL);//第一个ls是文件名,第二个ls是命令行调用ls指令printf("pid:%d,excel command end\n",getpid());}else{//fatherpid_t rid=waitpid(-1,NULL,0);if(rid>0){printf("wait success,rid:%d\n",rid);}}return 0;
}[BCH@hcss-ecs-6176 11_7]$ ./myprocess
pid:13889,excel command begin
总用量 56
drwxrwxr-x  2 BCH BCH  4096 11月 11 00:05 .
drwx------ 26 BCH BCH  4096 11月 11 00:05 ..
-rw-rw-r--  1 BCH BCH  1928 11月 10 16:30 1
-rw-rw-r--  1 BCH BCH    74 11月  9 22:13 Makefile
-rwxrwxr-x  1 BCH BCH  8624 11月 11 00:05 myprocess
-rw-rw-r--  1 BCH BCH  2710 11月 11 00:05 myprocess.c
-rw-r--r--  1 BCH BCH 12288 11月 10 23:44 .myprocess.c.swo
-rw-r--r--  1 BCH BCH 12288 11月  8 23:05 .myprocess.c.swp
wait success,rid:13889

execv

execv 是一个系统调用,用于在当前进程中执行一个新的程序。它的原型如下:

#include <unistd.h>
int execv(const char *path, char *const argv[]);
path 参数是要执行的新程序的路径。
argv 是一个以 NULL 结尾的字符串数组,其中第一个元素是要执行的新程序的名称,后续元素是新程序的命令行参数。

execv 函数会取代当前进程的内存映像,加载并执行指定路径的新程序,新程序的参数由 argv 指定。执行 execv 后,当前进程的代码、数据、堆栈等内容都被替换为新程序的内容,新程序开始执行。

如果 execv 函数执行成功,它不会返回;如果发生错误,返回值为 -1,并且设置全局变量 errno 表示具体的错误类型。

execv 与 execl 和 execlp 的不同之处在于它接受一个字符串数组作为参数,而不是逐个列出参数。这使得在运行时动态构建参数列表更为方便。

事例

[BCH@hcss-ecs-6176 11_7]$ cat myprocess.c
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{pid_t id=fork();if(id==0){//child//char* const argv[]={"ls","-a","-l",NULL};printf("pid:%d,excel command begin\n",getpid());sleep(3);//execl("/usr/bin/ls","ls","-a","-l",NULL);//execlp("ls","ls","-a","-l",NULL);execv("/usr/bin/ls",argv);printf("pid:%d,excel command end\n",getpid());}else{//fatherpid_t rid=waitpid(-1,NULL,0);if(rid>0){printf("wait success,rid:%d\n",rid);}}return 0;
}[BCH@hcss-ecs-6176 11_7]$ ./myprocess
pid:1798,excel command begin
总用量 56
drwxrwxr-x  2 BCH BCH  4096 11月 11 00:15 .
drwx------ 26 BCH BCH  4096 11月 11 00:15 ..
-rw-rw-r--  1 BCH BCH  1928 11月 10 16:30 1
-rw-rw-r--  1 BCH BCH    74 11月  9 22:13 Makefile
-rwxrwxr-x  1 BCH BCH  8624 11月 11 00:15 myprocess
-rw-rw-r--  1 BCH BCH  2824 11月 11 00:15 myprocess.c
-rw-r--r--  1 BCH BCH 12288 11月 10 23:44 .myprocess.c.swo
-rw-r--r--  1 BCH BCH 12288 11月  8 23:05 .myprocess.c.swp
wait success,rid:1798

execvp

execvp 是一个系统调用,与 execv 类似,用于在当前进程中执行一个新的程序,但它可以搜索 PATH 环境变量指定的路径来寻找可执行文件。它的原型如下:

#include <unistd.h>
int execvp(const char *file, char *const argv[]);
file 参数是要执行的新程序的文件名或路径。如果 file 中不包含斜杠 /,则 execvp 函数会在 PATH 环境变量指定的路径中搜索与 file 匹配的可执行文件。
argv 是一个以 NULL 结尾的字符串数组,其中第一个元素是要执行的新程序的名称,后续元素是新程序的命令行参数。


与 execv 类似,execvp 函数也会取代当前进程的内存映像,加载并执行指定路径的新程序,新程序的参数由 argv 指定。执行 execvp 后,当前进程的代码、数据、堆栈等内容都被替换为新程序的内容,新程序开始执行。

如果 execvp 函数执行成功,它不会返回;如果发生错误,返回值为 -1,并且设置全局变量 errno 表示具体的错误类型。

execvp 常用于在当前进程中执行其他程序,且无需指定程序的绝对路径,只需指定程序的名称即可,因为它会在 PATH 环境变量指定的路径中搜索可执行文件。

替换c++程序

我们用自己写的c语言程序,去程序替换成我们自己编写的C++程序

[BCH@hcss-ecs-6176 11_7]$ cat test.cc
#include<iostream>
using namespace std;
int main()
{cout<<"hello c++"<<endl;cout<<"hello c++"<<endl;cout<<"hello c++"<<endl;cout<<"hello c++"<<endl;return 0;
}[BCH@hcss-ecs-6176 11_7]$ cat myprocess.c
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{pid_t id=fork();if(id==0){//childprintf("pid:%d,excel command begin\n",getpid());sleep(3);excel("./mytest" , "mytest",NULL);printf("pid:%d,excel command end\n",getpid());}else{//fatherpid_t rid=waitpid(-1,NULL,0);if(rid>0){printf("wait success,rid:%d\n",rid);}}return 0;
}[BCH@hcss-ecs-6176 11_7]$ ./myprocess
pid:32180,excel command begin
hello c++
hello c++
hello c++
hello c++
wait success,rid:32180

替换shell语言程序

test.sh是shell下面的脚本语言

[BCH@hcss-ecs-6176 11_7]$ cat test.sh
#!/usr/bin/bash //开头必须以#!,后面跟上系统指令或者自己写的程序路径echo "hello world"
touch file1 file2 file3
echo "hello done"命令行运行的时候
1)bash test.sh2)也可以chmod +x test.sh
./test.sh

我们用自己写的c语言程序,去程序替换成我们自己编写的shell脚本程序

[BCH@hcss-ecs-6176 11_7]$ cat test.sh
#!/usr/bin/bash //开头必须以#!,后面跟上系统指令或者自己写的程序路径echo "hello world"
touch file1 file2 file3
echo "hello done"[BCH@hcss-ecs-6176 11_7]$ cat myprocess.c
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{pid_t id=fork();if(id==0){//childprintf("pid:%d,excel command begin\n",getpid());sleep(3);excel("/usr/bin/bash" , "bash", "test.sh",NULL);printf("pid:%d,excel command end\n",getpid());}else{//fatherpid_t rid=waitpid(-1,NULL,0);if(rid>0){printf("wait success,rid:%d\n",rid);}}return 0;
}[BCH@hcss-ecs-6176 11_7]$ ./myprocess
pid:31838,excel command begin
hello world//脚本语言运行成功
hello done
wait success,rid:31838

exec族函数为什么可以替换不同语言的程序?

exce*(*是通配符)

不管用什么语言编写的程序(C/C++、bash、java、python等),只要在os中都可以进行进程替换

exec 系列函数可以替换不同语言的程序,是因为它们是操作系统级别的系统调用,与编程语言无关。这些函数在操作系统层面实现了程序的加载和执行,不依赖于特定的编程语言。

当调用 exec 系列函数时,操作系统会负责加载指定的可执行文件,并在当前进程的上下文中执行该文件。这意味着,无论是用 C、Python、Java 还是其他编程语言编写的程序,只要它们是可执行文件,并符合操作系统的执行要求,就可以被 exec 函数加载和执行。

因此,exec 系列函数是跨语言的,可以用于替换任何可执行文件,而不仅仅局限于特定语言的程序。这也使得在一个编程环境中,通过调用 exec 函数,可以方便地与其他编程语言的程序进行交互和整合。

当一个程序运行的整个过程

一个程序被运行(形成进程),首先第一步,是要创建内核数据结构(pcb、页表等),然后第二步,将程序的代码和数据加载到内存中,页表再一一映射

有时,有些程序没有被运行时,只需要创建内核数据结构(pcb、页表等),页表没有映射物理内存

  🌸🌸🌸如果大家还有不懂或者建议都可以发在评论区,我们共同探讨,共同学习,共同进步。谢谢大家! 🌸🌸🌸 


http://www.ppmy.cn/server/40190.html

相关文章

Python Web框架Django项目开发实战:多用户内容发布系统

注意:本文的下载教程,与以下文章的思路有相同点,也有不同点,最终目标只是让读者从多维度去熟练掌握本知识点。 下载教程:Python项目开发Django实战-多用户内容发布系统-编程案例解析实例详解课程教程.pdf 一、引言 在Web应用开发中,内容发布系统是一个常见的需求。这类系…

python中flask使用简要记录

文档层级概要 一、flask简要说明 二、uwsgi配置说明 三、启动 四、结果验证 之前有做过接口&#xff0c;后来写了许多算法和数据处理&#xff0c;对于接口大多时候是通过fastapi或调别人的接口。自己写的接口倒是没有多少。在这里使用uwsgi和flask及nginx进行配置&#xf…

力扣127.单词接龙讲解

距离上一次刷题已经过去了.........嗯............我数一一下............整整十天&#xff0c;今天再来解一道算法题 由于这段时间准备简历&#xff0c;没咋写博客。。今天回来了&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&…

如何在matlab时间序列中X轴标注月-日

一般我们使用的时间序列都是以年为单位&#xff0c;比如下图&#xff1a; 而如果要绘制月尺度的时间变化图&#xff0c;则需要调整X轴的标注。下面代码展示了如何绘制小时尺度的降水数据。 [sname2,lon2,lat2] kml2xy(GZ_.kml); nc_bound2 [lon2,lat2]; area_ind2inpolygon(e…

网络安全之DHCP详解

DHCP&#xff1a;Dynamic Host Configration Protocol 动态主机配置协议 某一协议的数据是基于UDP封装的&#xff0c;当它想确保自己的可靠性时&#xff0c;这个协议要么选确认重传机制&#xff0c;要么选周期性传输。 DHCP是确认重传&#xff0c;【UDP|DHCP】,当DHCP分配完地…

SpringBoot拦截器中使用RedisTemplate

这几天想着把登陆拦截器的验证规则修改一下&#xff0c;验证介质由session中获取改为从redis中获取&#xff0c;结果发现redisTemplate一直为空&#xff0c; Configuration public class WebInterceptorConfig implements WebMvcConfigurer {Overridepublic void addIntercept…

docker Harbor私有仓库部署管理

搭建本地私有仓库&#xff0c;但是本地私有仓库的管理和使用比较麻烦&#xff0c;这个原生的私有仓库并不好用&#xff0c;所以我们采用harbor私有仓库&#xff0c;也叫私服&#xff0c;更加人性化。 一、什么是Harbor Harbor是VWware 公司开源的企业级Docker Registry项…

✨✨使用vue3打造一个el-form表单及高德地图的关联组件实例✨

✨1. 实现功能 &#x1f31f;表单内显示省市县以及详细地址 点击省市县输入框时&#xff0c;打开对应地图弹窗&#xff0c;进行位置选择选择位置回显入对应输入框表单内的省市县以及地址输入框同外嵌表单走相同的校验方式触发校验后点击reset实现清除校验与清空数据 &#x1f…