Linux之管道,system V的共享内存,消息队列和信号量

Linux之管道,systemV共享内存和信号量

  • 一.进程间通信
    • 1.1进程间通信的目的
    • 1.2进程间通信的方式
  • 二.管道
    • 2.1管道的概念
    • 2.2匿名管道
    • 2.3命名管道
  • 三.system V
    • 3.1共享内存
    • 3.2消息队列
    • 3.3信号量

一.进程间通信

在我们之前有关Linux指令的学习时我们使用过“|”这个命令,当时我们说这是管道文件,那么什么是管道?管道的作用是什么?
管道是进程间通信的一种方式,在我们之前的学习中我们反复强调进程是具有独立性的所以不同的进程很难相互影响但是在项目的开发中我们不可能只使用一个进程或者个别进程来进行开发,在进程的数量多了起来之后数据也就多了起来那么我们就需要在进程之间传输数据或共享数据。而且我们有时候还需要利用一个进程来通知另外一个进程发生了什么事件或者控制另外一个进程进行某种操作。这些都是我们进程间通信的目的!

1.1进程间通信的目的

  • 传输数据:一个进程需要将它的数据发送给另一个进程
  • 共享资源:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

1.2进程间通信的方式

  1. 管道
  • 匿名管道
  • 命名管道
  1. system V
  • system V 共享内存
  • system V 消息队列
  • system V 信号量

二.管道

2.1管道的概念

管道是最古老的一种通信方式但是时至今日它仍然是我们需要学习的知识,古老但并不落后。
对于想要让不同的进程进行通信那么我们可以先思考一个问题:进程间通信的本质是什么?
本来我们强调进程要具有独立性所以我们无法让一个进程中含有另外一个进程那么一个进程要如何读取到另外一个进程写入的信息呢?
读取?写入?如果我们让一个进程向一个文件中写入数据然后让另外一个进程读取该文件的数据,这是不是就是完成了进程间的通信呢?那么这种方法的本质是什么?
所以进程间通信的本质是让不同的进程看见了同一份资源

所以管道就诞生了而那个作为中转的文件就是管道而可以叫做管道文件。
那么如何理解让不同的进程看见同一份资源呢?我们先以父子进程为例并配图来展示。
在这里插入图片描述
同时我们要注意管道是单向的所以只能让一个文件写入一个文件读取
并且有两种管道之分:匿名管道和命名管道。这两种管道有什么区别我也可以提前告诉各位,匿名管道只能让有“亲缘关系”的进程即父子,兄弟,爷孙等等进行通信而命名管道可以让任意两个进程进行通信。

2.2匿名管道

在Linux中想要创建匿名管道需要用到系统调用pipe
在这里插入图片描述
在这里插入图片描述

这个系统调用很简单,成功就返回零,失败就返回-1同时设置错误码。而参数int pipefd[2]在我们刚刚看见时可能会有点奇怪,我们知道这是一个整型数组但是为什么会有个2呢?
我们只需要回想有关数组传参的知识就可以知道对于数组来说传参传的都是数组的首地址所以这个2是没有用的更多的是起到一种提示的作用。那么在提示什么呢?我们来深入了解一下匿名管道的原理就可以知道了,一样我们结合图来讲述。
在这里插入图片描述
在了解了其中的原理之后我们再回过头来看这个接口就能知道这个参数其实就是文件描述符的数组而那个2也就是提示我们只有两个文件描述符。那么问题又出现了这两个文件描述符中哪个是写端哪个是读端呢?我们从手册中就能知道。
在这里插入图片描述
所以文件描述符数组中第一个文件描述符是读端,第二个文件描述符是写端。在了解了哪个是读写端后我们就可以尝试使用匿名管道来传输数据了,并且通过对匿名管道的不同使用场景我们可以总结出来四种情况和五种特性假设为子写父读的情况!

#include <iostream>
#include <unistd.h>
#include <cassert>
#include <stdio.h>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>using namespace std;int main()
{// 打开管道int pipefd[2] = {};int n = pipe(pipefd);assert(n == 0);// 使用断言来判断pipe的返回值,// 因为pipe大概率是创建成功的所以使用断言比使用if更加的方便(void *)n;// 为了避免编译器因为定义了变量但是没有使用从而产生报错所以随意使用一下变量ncout << "fd[1]:" << pipefd[0] << " " << "fd[2]:" << pipefd[1] << endl;// 创建子进程pid_t id = fork();if (id < 0){perror("fork");return 1;}else if (id == 0){// 子进程// 子进程为写入端所以要关闭读取端即pipefd[0]close(pipefd[0]);// 向管道中写入// 正常情况// int cnt = 5;// while (cnt--)// {//     ssize_t n = write(pipefd[1], buffer, strlen(buffer));//     if (n < 0)//     {//         perror("write");//         return 1;//     }//     sleep(1);// }// 情况4while (true){char buffer[1024] = {};snprintf(buffer, sizeof(buffer), "i am child , pid:%d , ppid:%d\n", getpid(), getppid());ssize_t n = write(pipefd[1], buffer, strlen(buffer));if (n < 0){perror("write");return 1;}sleep(1);}cout << "write point quit" << endl;close(pipefd[1]);exit(0);}else{// 父进程// 父进程为读取端所以要关闭写入端即pipefd[1]close(pipefd[1]);// 从管道中读取数据char buffer1[1024] = {};// 正常情况// while (true)// {//     ssize_t n = read(0, buffer1, sizeof(buffer1));//     if (n == 0)//     {//         break;//     }//     else if (n > 0)//     {//         buffer1[n] = 0;//         cout << "child say:" << buffer1;//     }//     else//     {//         perror("read");//         return 1;//     }//     sleep(1);// }// 情况3// while(true)// {//     ssize_t n = read(0, buffer1, sizeof(buffer1)-1);//     if(n > 0)//     {//         buffer1[n] = 0;//         cout << "child say:" << buffer1;//         sleep(2);//     }//     else if(n == 0)//     {//         cout << "write point is close , i read the file's end" << endl;//         break;//     }//     else//     {//         perror("read");//         return 1;//     }// }// 情况4while (true){ssize_t n = read(pipefd[0], buffer1, sizeof(buffer1));if (n > 0){buffer1[n] = 0;cout << "child say:" << buffer1;sleep(1);}else if (n == 0){cout << "write point is close , i read the file's end" << endl;break;}else{perror("read");return 1;}break;}close(pipefd[0]);cout << "read point quit" << endl;// 等待子进程// 正常情况//  pid_t rid = waitpid(id,NULL,0);//  if(rid == id)//  {//      cout << "wait success" << endl;//  }// 情况4sleep(3);int status = 0;pid_t rid = waitpid(id, &status, 0);if (rid == id){cout << "wait success " << "child quit sig:" << (status&0x7F) << endl;}}return 0;
}

在这里插入图片描述
而从这些代码中我们可以总结出来匿名管道的四种情况和五种特性:
四种情况

  1. 正常情况下,如果管道没有数据了那么读端必须等待直到管道中有数据即写端写入数据
  2. 正常情况下,如果管道中数据满了那么写端必须等待直到管道中有空间即读端读取数据
  3. 写端关闭,读端一直读取,读端会读到read返回值为0,表示读到文件结尾
    在这里插入图片描述
  4. 读端关闭,写端一直写入,操作系统会通过向写端进程发送sig 13信号来终止写端进程
    在这里插入图片描述

五种特性

  1. 匿名管道只允许有亲缘关系的进程之间进行通信,常见的为父子
  2. 匿名管道默认要给予读写段提供同步机制 ——了解现象即可
  3. 管道是面对字节流的 ——了解现象即可
  4. 管道的生命周期是随进程的
  5. 管道是单项通信的,也是半双工通信的一种

2.3命名管道

匿名管道是只能让有亲缘关系的进程通信而命名管道则可以让任意的两个进程互相通信,而命名管道的原理和匿名管道也是相似的。

那么想要创建一个命名管道有两种方法:一种是指令级的一种是代码级的。

  1. 指令级
    在这里插入图片描述
  2. 代码级
    在这里插入图片描述
//connect.hpp
//为了符合两个进程都能看见同一个资源的本质
//所以我们创建一个.hpp文件来存储那些公用的数据和接口
#pragma once#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cassert>
#include <fcntl.h>
#include <cstring>#define FILENAME "./FIFO"
#define SIZE 1024
//Makefile
##利用伪目标达成一次性生成多个可执行文件
.PHONY:all
all:launch receptionlaunch:launch.ccg++ -o $@ $^ -std=c++11reception:reception.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f FIFO launch reception
//launch.cc
#include <iostream>
#include "connect.hpp"using namespace std;int main()
{// 创建命名管道mkfifo(FILENAME, 0666);// 写端要以写入模式打开命名管道int fd = open(FILENAME, O_WRONLY);if (fd == -1){perror("open");return 1;}// 向命名管道中写入数据int cnt = 10;while (cnt--){char buffer[SIZE] = {};cout << "i am wirting message..." << endl;snprintf(buffer, sizeof(buffer), "message is pid:%d ppid:%d\n", getpid(), getppid());write(fd, buffer, strlen(buffer));sleep(1);}return 0;
}
//reception.cc
#include <iostream>
#include "connect.hpp"using namespace std;int main()
{// 读端要以读取的模式打开命名管道int fd = open(FILENAME, O_RDONLY);if (fd == -1){perror("open");return 1;}// 从命名管道中读取数据int buffer[SIZE] = {};while (true){int n = read(fd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = 0;cout << "i am receiving meesge..." << endl;cout << "message is:" << buffer << endl;}else if(n == 0){cout << "write point quit , me too" << endl;break;}else{perror("read");return 1;}sleep(1);}return 0;
}

在这里插入图片描述
在这里插入图片描述

当我们利用命名管道进行通信时我们会发现如果读写端任意一个先使用open打开了命名管道后这个进程会卡住不会向下继续运行代码直到另外一个进程也使用open打开命名管道后,这也符合了我们五种特性中的一种即管道默认要给予读写段提供同步机制

对于命名管道而言,它也有四种情况和五种特性但是大部分都和匿名管道相同只有一个特性不同命名管道可以让任意两个进程进行通信

匿名管道是通过亲缘关系继承制的看见同一份资源但是命名管道可以让没有亲缘关系的两个进程看见同一份资源这是怎么做到的呢?
在我们创建命名管道时大家有没有注意到其中的参数是什么
在这里插入图片描述
第二个参数是创建管道的权限这个无需多谈因为管道也是个文件所以一定需要设置权限,重点在第一个参数路径加文件名,两个进程正是通过路径加文件名来看到同一个资源的
当我们没有了继承制后我们想要让两个不同的进程看见同一份资源那么一定要让这两个进程找到这一份资源而在我们学习了文件系统和软硬连接后我们知道在一个分区下文件名是唯一的因为inode是唯一而文件名会和inode形成映射关系所以文件名也是唯一的,同时我们也知道查找一个文件的方法是通过路径那么利用路径加文件名是不是就能做到准确的找到这个文件并且还保持着唯一性。所以我们利用路径加文件名的方式让两个进程找到同一份资源!!!

三.system V

通过对管道的学习我们大致了解了进程间通信的概念和本质,所以我们现在来介绍另外一种进程间通信的方法即system V。
System V它最初由AT&T开发,曾经也被称为AT&T System V,是Unix操作系统众多版本中的一支。而在system V中存在着三种进程间通信的方式。

3.1共享内存

我们创建管道文件并且利用管道完成进程间通信,本质是向管道文件的文件缓冲区写入读取。但是使用管道我们还需要调用系统接口向缓冲区中写入,而使用接口的过程中其实是比较浪费时间的那么我们是否可以之间向内存中写入读取数据呢?
这就是system V共享内存,共享内存是物理内存中的一块内存而在虚拟地址空间中则是映射到共享区的和动静态库一样,而且共享内存是可以被开辟多块的。那么问题也就出来了对于管道而言我们是通过继承制和路径加文件名来看见同一份资源的,而共享内存要如何看见同一份资源即看见同一块共享内存呢?我们结合图来讲述。
在这里插入图片描述
所以我们知道了共享内存是通过创建时设立一个标识符来达到让另外一个进程找到同一个共享内存的,并且这个标识符是存储在共享内存的结构体中的。想要证明这些我们就需要使用接口了

共享内存的接口很多,其中包括了创建,挂载,卸载,控制。

  1. 创建:创建共享内存
    在这里插入图片描述
//connect.hpp
#pragma once#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>#define FILENAME "/home/ly/lesson7"
#define ID 0x12345678
#define SIZE 4096
//Makefile
.PHONY:all
all:server clientserver:server.ccg++ -o $@ $^ -std=c++11client:client.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f client server	
#include <iostream>
#include "connect.hpp"
using namespace std;int main()
{//获取keykey_t key = ftok(FILENAME,getpid());if(key == -1){cout << "errno:" << errno << ", errstring" << strerror(errno) << endl;}//创建共享内存int n = shmget(key,SIZE,IPC_CREAT|0666);if(n == -1){cout << "errno:" << errno << ", errstring" << strerror(errno) << endl;}return 0;
}

如果我们想要查看创建的共享内存则可以使用ipcs -m命令
在这里插入图片描述

其中key是标识符,shmid就是共享内存的id,owner是创建者,perms则是权限,bytes则是共享内存的大小,nattch则是共享内存挂载到了多少个虚拟地址空间中。

2.挂载:将共享内存链接到进程的虚拟内存空间中
在这里插入图片描述

//connect.hpp
#pragma once#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <iostream>#define FILENAME "/home/ly/lesson7"
#define ID 0x12345678
#define SIZE 4096using namespace std;//获取key
key_t GetKey()
{key_t key = ftok(FILENAME,ID);if(key == -1){cout << "errno:" << errno << ", errstring:" << strerror(errno) << endl;exit(1);}return key;
}//将key转为会16进制打出
string ToHex(key_t key)
{char buffer[1024];snprintf(buffer,sizeof(buffer),"0x%x",key);return buffer;
}//创建或者获得共享内存
int CreatShm(key_t key)
{int id = shmget(key,SIZE,IPC_CREAT|0666);if(id == -1){cout << "errno:" << errno << ", errstring:" << strerror(errno) << endl;exit(2);}return id;
}
//server.cc
#include "connect.hpp"
#include <iostream>using namespace std;int main()
{// 获取keykey_t key = GetKey();cout << "key:" << ToHex(key) << endl;// 创建共享内存int id = CreatShm(key);cout << "id:" << id << endl;// 将共享内存挂载到进程的虚拟地址空间中char *s = (char *)shmat(id, nullptr, 0);if (*s == -1){cout << "errno:" << errno << ", errstring:" << strerror(errno) << endl;}else{cout << "server attch shm done" << endl;}return 0;
}
//client.cc
#include "connect.hpp"
#include <iostream>using namespace std;int main()
{// 获取keykey_t key = GetKey();cout << "key:" << ToHex(key) << endl;// 获得共享内存int id = CreatShm(key);cout << "id:" << id << endl;// 挂载char *s = (char *)shmat(id, NULL, 0);if (*s == -1){cout << "errno:" << errno << ", errstring:" << strerror(errno) << endl;}else{cout << "server attch shm done" << endl;}return 0;
}
  1. 卸载:将共享内存和虚拟地址空间之间的链接去除
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述
可以发现在我们卸载了之后共享内存并没有被删除说明共享内存的生命不是随进程的,而是随内核的。所以如果我们想要删除一个共享内存要怎么做呢?
4.控制:删除共享内存或者查找更改共享内存的属性
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

除了使用shmctl接口外我们还可以通过指令来删除共享内存即ipcrm -m +shmid
在这里插入图片描述

我们通过shmctl手册中还可以看见有关共享内存的结构体以及里面存储的属性那么是是否就证明了我们之前说的对共享内存的管理工作是存在并且正确的呢?
在这里插入图片描述

在了解了那几个接口之后我们就要尝试向共享内存中写入和读取数据了从而来观察出共享内存的通信和管道的通信有什么区别。
在这里插入图片描述

在这里插入图片描述

至于共享内存和管道这两个通信方式有什么区别,只能说共享内存是所有通信方式中最快的,而原因就是利用共享内存来通信减少了数据的迁移也可以说是数据的拷贝。
我们可以大致思考一些管道和共享内存中分别需要数据迁移多少次

  • 管道:我们首先要将数据从键盘迁移到读端自己定义的缓冲区中然后使用write接口将缓冲区的数据迁移到管道文件的缓冲区中,之后我们想要读取数据还需要将数据从管道文件的缓冲区中迁移到写端自己的缓冲区中最后再将其迁移到显示屏文件中。中间甚至还省略了一些语言层面的迁移,所以使用管道来通信的话至少需要4次数据迁移。
  • 共享内存:在我们创建并且分别挂载到两个进程后,我们只需要将数据从键盘中迁移到共享内存中然后再将数据从共享内存迁移到显示屏文件中,所以使用共享内存通信我们只需要2次数据迁移。

从迁移的数量上我们就可以看出来使用共享内存通信的速度是远远大于使用管道的,其主要原因就是共享内存我们只需要向内存中写入读取即可省略了那些繁琐的接口。

3.2消息队列

system V中的消息队列则是提供了一个进程向另外一个进程发送数据块的能力从而实现进程间通信。
在这里插入图片描述
对于消息队列同样也有系统接口,而消息队列的系统接口和共享内存的接口是相似的。

  1. 创建
    在这里插入图片描述
    这与共享内存的则基本相同,参数分别为标识符和权限。返回值则为msgid
  2. 接受消息
    在这里插入图片描述
    在这里插入图片描述

第一个参数是消息队列的id,第二个参数是一个结构体存储了消息的类型和消息的内容,第三个参数则是发送消息的大小,第四个参数则和共享内存挂载时相同是以什么模式发送消息。
返回值则为常见的成功返回0,失败返回-1

  1. 接收消息
    在这里插入图片描述
    第一个参数是消息队列的id,第二个参数则和发送消息时相同也为一个结构体,第三个参数则是想要接收多少大小的消息,第四个参数为模式选择。
    返回值则是成功就返回接收消息的大小即msgsz,失败返回-1。

  2. 控制
    在这里插入图片描述
    控制接口则与共享内存完全相同。
    同时在消息队列中我们也可以找到它的结构体,而且我们发现消息队列的结构体第一个变量也是这个ipc_perm这个结构体。
    在这里插入图片描述

3.3信号量

在理解信号量的概念之前我们需要了解一些简单的概念
为了完成进程间的通信我们需要做到让不同的进程看见同一份资源而这份资源也被叫做公共资源,并且在开发中可能会有很多的进程同时访问这份资源这也就叫做并发开发,但是在我们之前学习共享内存的时候我们发现共享内存是被暴露给所有的使用者的,那么对于共享内存这份公共资源来说是具有数据安全的问题的,所以对于公共资源我们是需要保护起来的而保护的手段主要分为互斥和同步。
而对于公共资源的保护是可以由用户来做或者由操作系统来做的,比如匿名命名管道和消息队列都是由操作系统做了保护就像管道的同步机制一样,而例如共享内存的保护就是由我们用户来做的就像我们之前利用管道完成了共享内存的同步机制。

对于互斥和同步我们现在暂时不需要深入了解只需要知道简单的概念和现象就可以,我们会在多线程的部分再次学习到。
互斥:任何一个时刻只允许一个进程访问公共资源。
同步:多个进程访问一个公共资源时是按照一定的顺序来访问的。
我们对于这些被保护起来的公共资源叫做临界资源,而对于那些访问临界资源的代码叫做临界区。
例如我们使用管道保护起来的共享内存就是一个临界资源,而在代码中我们向共享内存中写入数据读取数据的代码就是临界区。
而且我们可以很简单的发现我们想要维护临界资源其实只需要维护临界区就可以了,代码一旦被维护了那么资源就自然而然的也被维护了。

同时我们再来介绍一个概念:原子性
原子性是数据库事务的四大特性之一,它确保事务中的所有操作要么全部成功,要么全部失败回滚。这意味着事务的操作如果成功就必须完全应用到数据库,如果操作失败则不能对数据库有任何影响。
就像我们小时候吹牛说我以后只会考一百分,那么这句话带来的后果只有两个:考到一百分,没考到一百分。其中的其他情况我们都不需要考虑。

在了解了这些概念后我们就可以来谈谈信号量了
要如何理解信号量呢我们可以用一个例子来引入:看电影。
在我们去看电影的时候我我们的座位是在我们买票的时候就已经决定了还是在我们坐到座位的时候就已经决定了呢?
毫无疑问是在我们买票的时候就已经决定了,无论我们去不去看这场电影电影院都必须给我们把这个座位留着。
在这里插入图片描述
而信号量就是这个计数器,所以它的作用也就是保护临界资源。
信号量:表示对资源数量的计数器,每一个进程想要访问公共资源的某一份资源,就必须在访问之前申请一份信号量资源而本质就是对计数器进行–操作,只要–成功就代表该进程已经预定了资源,而如果一个进程–失败了就必须被挂起阻塞。

想要完成这种对信号量的访问我们只需要在申请资源的代码即临界区之前增加对计数器的–操作以及在代码的结尾增加对计数器的++操作即可。
在这里插入图片描述

那么如果信号量的资源量只有1呢?同时只能有一个进程访问这个资源,这是不是就完成了一种互斥的功能,这就是互斥锁而这种信号量就被叫做二元信号量。

在介绍了信号量的大概概念后我们可以提出两个问题:

  1. 在了解了信号量的作用后我们发现如果想要信号量有作用就必须让每个进程都看见同一个信号量资源,这又要如何做到呢?
    对于问题我们可以采用和共享内存以及消息队列同样的操作即使用key,但是这个key就必须是操作系统提供的了。
  2. 信号量是为了保护公共资源所以进程在访问资源之前都会先访问信号量,那么信号量是不是就变成了一个公共资源呢?那么信号量是不是也要被保护呢?它又要怎么被保护呢?
    信号量本身就是保护公共资源但是现在发现信号量本身也需要被保护这不就陷入了一种循环中,所以对于信号量的保护我们需要换一种方式,这件需要使用到我们刚刚提到的原子性,只要让进程对信号量的访问成功就完全应用,失败就完全不影响。这就完成了对信号量的保护。所以我们对访问信号量申请资源的操作叫做p操作,释放资源的操作叫做v操作,总结就是只提供pv操作来完成保护。
  3. 如何让进程在访问信号量申请资源失败时完成挂起阻塞操作
    在信号量中不仅有计数器来完成–和++操作还会存在一个阻塞队列,只要是申请资源失败的进程就会被移入到阻塞队列中。
    在这里插入图片描述

现在我们要来熟悉一下信号量的接口
1.创建
在这里插入图片描述

  1. 申请资源
    在这里插入图片描述

3.控制
在这里插入图片描述

在这里插入图片描述
对于信号量我们同样可以找到它的结构体所以信号量=信号量的内容+信号量的属性。同时我们可以发现信号量的结构体也有一个ipc_serm。所以我们发现从共享内存到消息队列再到信号量都有ipc_perm这个结构体。

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

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

相关文章

Figma入门-基本操作制作登录页

Figma入门-基本操作制作登录页 前言 在之前的工作中&#xff0c;大家的原型图都是使用 Axure 制作的&#xff0c;印象中 Figma 一直是个专业设计软件。 最近&#xff0c;很多产品朋友告诉我&#xff0c;很多原型图都开始用Figma制作了&#xff0c;并且很多组件都是内置的&am…

Django实现智能问答助手-数据库方式读取问题和答案

扩展 增加问答数据库&#xff0c;通过 Django Admin 添加问题和答案。实现更复杂的问答逻辑&#xff0c;比如使用自然语言处理&#xff08;NLP&#xff09;库。使用前端框架&#xff08;如 Bootstrap&#xff09;增强用户界面 1.注册模型到 Django Admin&#xff08;admin.py…

SQL注入--文件读写注入--理论

什么是文件读写注入&#xff1f; MySQL中有 读取文件的函数&#xff1a;load_file() 写入文件的函数&#xff1a;Into outfile&#xff08;能写入多行&#xff0c;按格式输出&#xff09;和 into dumpfile&#xff08;只能写入一行且没有输出格式&#xff09; 利用这些函数在S…

《最小生成树算法详解:Kruskal的优雅实现》

前置知识和本篇介绍 前置知识&#xff1a; 数据结构-优先级队列&#xff0c; 数据结构-并查集。 Kruskal算法不需要建图&#xff0c; 因此不会建图的模板也没事。 本篇介绍一最小生成树的概念和Kruskal算法。 有关prim算法&#xff08;另一种最小生成树的算法&#xff09;&am…

云计算-华为HCIA-学习笔记

笔者今年7月底考取了华为云计算方向的HCIE认证&#xff0c;回顾从IA到IE的学习和项目实战&#xff0c;想整合和分享自己的学习历程&#xff0c;欢迎志同道合的朋友们一起讨论&#xff01; 第二章&#xff1a;服务器基础 服务器是什么&#xff1f; 服务器本质上就是个性能超强的…

uniapp接入高德地图

下面代码兼容安卓APP和H5 高德地图官网&#xff1a;我的应用 | 高德控制台 &#xff0c;绑定服务选择《Web端(JS API)》 /utils/map.js 需要设置你自己的key和安全密钥 export function myAMap() {return new Promise(function(resolve, reject) {if (typeof window.onLoadM…

C++:探索AVL树旋转的奥秘

文章目录 前言 AVL树为什么要旋转&#xff1f;一、插入一个值的大概过程1. 插入一个值的大致过程2. 平衡因子更新原则3. 旋转处理的目的 二、左单旋1. 左单旋旋转方式总处理图2. 左单旋具体会遇到的情况3. 左单旋代码总结 三、右单旋1. 右单旋旋转方式总处理图2. 右单旋具体会遇…

文小言1:

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

uni-app 界面TabBar中间大图标设置的两种方法

一、前言 最近写基于uni-app 写app项目的时候&#xff0c;底部导航栏 中间有一个固定的大图标&#xff0c;并且没有激活状态。这里记录下实现方案。效果如下&#xff08;党组织这个图标&#xff09;&#xff1a; 方法一&#xff1a;midButton的使用 官方文档&#xff1a;ta…

CentOS7(Linux)详细安装教程(图文详解)

一、软件准备 本文CentOS7安装在VMware Workstation虚拟机软件,故安装前请自行安装该软件。VMware Workstation官网链接:VMware Workstation官网地址CentOS7下载地址:centos7镜像 如下是最常使用的版本(任选版本)centos-7.9.2009-isos-x86_64安装包下载_开源镜像站-阿里…

【实战】基于urllib和BeautifulSoup爬取jsp网站的数据

文章目录 前言目标网站分析目标网页爬取数据解析导出数据其他问题处理分页检索及多关键字搜索去重cookie问题工具封装经验总结前言 网络数据爬取大致分为两类: 静态爬取:该种方式针对那种架构比较老的网站,使用模版方式,通过浏览器F12只能找到静态页面,找不到返回json数…

玩转数字与运算:用C语言实现24点游戏的扑克牌魅力

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

【MySQL】sql注入相关内容

【MySQL】sql注入相关内容 1. 为什么使用sql注入的时候&#xff0c;url传值的时候要使用–而不是– 使用–进行注释的时候需要在后面加一个空格才可以被认为是注释&#xff0c;url传值的过程中会将空格自动忽略&#xff0c;使用则可以在传输中保留为空格符号。&#xff08;同…

shell脚本(完结)

声明&#xff1a;学习视频来自b站up主 泷羽sec&#xff0c;如涉及侵权马上删除文章 感谢泷羽sec 团队的教学 视频地址&#xff1a;shell编程&#xff08;完结&#xff09;_哔哩哔哩_bilibili 本文主要讲解不同shell脚本中的相互调用以及输入输出重定向操作。 一、不同脚本之间…

【bug】使用transformers训练二分类任务时,训练损失异常大

使用transformers训练二分类任务时&#xff0c;训练损失异常大 问题分析 问题 training_loss异常大&#xff0c;在二分类损失中&#xff0c;收敛在1~2附近&#xff0c;而eval_loss却正常&#xff08;小于0.5&#xff09; 分析 参考&#xff1a; Bug in gradient accumulation…

深入解析 EasyExcel 组件原理与应用

✨深入解析 EasyExcel 组件原理与应用✨ 官方&#xff1a;EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel 官网 在日常的 Java 开发工作中&#xff0c;处理 Excel 文件的导入导出是极为常见的需求。 今天&#xff0c;咱们就一起来深入了解一款非常实用的操作 Exce…

Gradio学习笔记记录

安装指令&#xff1a;pip install gradio方法介绍 Interface》用于构建一些简单的页面&#xff0c;可以直接用这个指令搞定 形式》接收三个参数分别为处理函数、输入、输出三部分&#xff0c;呈现一般左/上为输入&#xff0c;右或下为输出 fn&#xff1a;将用户界面 &#xff0…

养老院管理系统+小程序项目需求分析文档

智慧综合养老服务平台是以业务为牵引、场景为驱动&#xff0c;围绕“老人”业务域&#xff0c;持续沉淀和打磨形成适应不同养老业务发展需要的业务能力&#xff0c;推动业务模式升级&#xff0c;为养老服务提供数字化解决方案&#xff0c;并依托实体站点与养老机构实现线上线下…

React的基本知识:事件监听器、Props和State的区分、改变state的方法、使用回调函数改变state、使用三元运算符改变state

这篇教学文章涵盖了大量的React基本知识。 包括&#xff1a; 事件监听器Props和State的区分改变state的方法使用回调函数改变state使用三元运算符改变state处理state中的数组处理state中的object条件渲染 &&条件渲染 三元运算符React中的forms 1. Event Listeners 在…

repmgr安装及常用运维指令

简介 repmgr 由 EDB 与其他个人和组织的贡献一起开发&#xff0c;安装部署相对较为简单 安装 repmgr官网上传对应的安装到服务器上 安装前/etc/hosts IP映射、始终同步、免密通信本文忽略 repmgr的安装相对较为简单,目前repmgr-5仅仅支持到postgresql-15 postgresql必要参数…