【Linux】进程间通信——管道
目录
写在前面的话
什么是进程间通信
为什么要进行进程间通信
进程间通信的本质理解
进程间通信的方式
管道
System V IPC
POSIX IPC
管道
什么是管道
匿名管道
什么是匿名管道
匿名管道通信的原理
pipe()的使用
匿名管道通信的特点
拓展代码
命名管道
什么是命名管道
命名管道通信的原理
mkfifo的使用
代码模拟命名管道通信过程
写在前面的话
本章是首次提出进程间通信的概念,所以会先介绍进程间通信的相关概念,以及整体的框架结构。
而本文重点是先介绍进程间通信的基本概念,然后重点介绍进程间通信的第一种方式:管道。
什么是进程间通信
进程间通信(Inter-Process Communication,IPC)是指操作系统或计算机系统中,不同进程之间进行数据交换和通信的机制或技术。由于进程是操作系统中独立运行的程序实例,而进程间通信允许这些独立的进程之间相互协作、共享资源和进行数据交换。
为什么要进行进程间通信
根据我们前面讲的,进程间是相互独立的,进程具有独立性啊,那通信不就不独立了吗?
答案是正确的,进程通信的确会破坏进程的完全独立性,因为进程通信的目的是为了实现进程之间的数据共享、同步和协作。通过进程通信,各个进程可以相互交互和共享资源,这意味着它们不再完全独立,而是具有一定的相互依赖性和关联性。
尽管进程通信破坏了进程的完全独立性,但这种破坏是有意义且必要的。在实际的计算机系统和操作系统中,进程往往需要协同工作、共享资源和交换数据才能完成复杂的任务。进程通信提供了一种机制,使得不同进程之间可以进行必要的协作和交流,并提供了相应的同步和保护机制来确保数据的正确性和一致性。
所以这是一种权衡和折中的方案,但大部分情况下进程是相互独立的。
综上,进程间通信主要是为了完成下面这些作用:
- 数据传输:一个进程需要将它的数据发送给另一个进程。
- 资源共享:多个进程之间共享同样的资源(包括本地共享和远程资源共享)。
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
进程间通信的本质理解
1.我们知道进程具有独立性,是通过虚拟地址空间 + 页表映射的方式来保持独立性的,所以通信起来成本会比较高。
2.既然通信,那么前提是一定要让不同的进程看到同一块“内存”(特定的结构组织),这块"内存"不能隶属于任何一个进程,而更应该强调共享。
进程间通信不是目的,而是手段!
进程间通信的方式
大体上可以分为3种通信方式:
-
管道
- 匿名管道pipe
- 命名管道
-
System V IPC
- System V消息队列
- System V共享内存
- System V信号量
System V只能用于单机通信(本地通信).
-
POSIX IPC
- 消息队列
- 共享内存
- 信号量
- 互斥量
- 条件变量
- 读写锁
POSIX IPC可以在单机通信的基础上,进行网络通信(远程资源共享)。
以上所提到的方式,我会在后面的章节逐一讲解,这个是进程间通信的方式.
今天我们将首要讲解进程间通信方式(一)——管道.
管道
什么是管道
管道(Pipe)是一种进程间通信机制,用于在相关进程之间传输数据。它是一种特殊的文件描述符,它可以连接一个进程的输出(写入端)到另一个进程的输入(读取端),从而使得这两个进程可以通过管道进行数据传输。
也就是说管道是单向传输的!现实生活中,我们所看听到的天然气管道、石油管道基本上都是单向传输的.

匿名管道
什么是匿名管道
匿名管道(Anonymous Pipe)是进程间通信的一种机制,用于在具有亲缘关系(例如父子进程)或共享同一终端的兄弟进程之间传输数据。
匿名管道是一种单向的数据流通道,它可以用于在进程之间传递数据。通常,一个进程作为管道的写入端(称为管道写入端),将数据写入管道;另一个进程作为管道的读取端(称为管道读取端),从管道中读取数据。
匿名管道的创建是通过系统调用 pipe() 来完成的。pipe的使用后面会讲。
匿名管道通信的原理
管道通信的背后是进程之间通过管道进行通信。
我们知道一个进程要运行,首先要加载到内存,然后创建一个task_struct结构体,里面会有一个files_struct结构体,然后这个结构体里又有一个fd_array[]数组,每个元素指向对应的文件struct_file,里面包含了文件内容等.
此时我们fork之后的子进程会重新创建一份task_struct,内容继承父进程的,此时fd_array[]里的内容也被子进程继承,即父进程打开的文件 子进程也继承了下来。它们指向的文件是 相同的.
假设父进程3号文件描述符是读取文件的,4号文件描述符也用来写入文件的,子进程继承以后,fd=3也是用来读取文件的,fd=4也是用来写入文件的
此时我们想让父进程进行写入fd=4,子进程进行读取fd=3,所以父进程就要关闭读端fd=3,子进程关闭写端fd=4。
这样我们就做到了让不同的进程看到了同一份资源(通过fork子进程),而且通过文件描述符的方式完成了进程间的单向通信。
综上,管道内部本质大体是如下流程:
1.父进程分别以读写方式打开一个文件
2.fork()创建子进程
3.双方各自关闭不需要的文件描述符
整体图如下:

pipe()的使用
既然我们知道了思路,那我们可以用代码来使用一下管道。
首先,父进程如何使用读和写方式分别打开文件呢?
这里使用到了pipe函数,函数用法及原型如下:

参数pipefd为输出型参数,我们提前在外部定义好数组,然后传入,结果就会保存在这个数组中,分别为pipefd[0],代表的是读端,pipefd[1],代表的是写端.
第二步我们利用fork创建子进程。
最后,子进程用来读取文件的内容,并关闭写端pipefd[1],父进程用来写入内容,同时关闭读端。
匿名管道通信的特点
一个小demo如下:
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;int main()
{// 1.创建管道int pipefd[2] = {0}; // pipefd[0] :读端, pipefd[1] :写端int n = pipe(pipefd);assert(n != -1);#ifdef DEBUG#endif// cout << pipefd[0] << " " << pipefd[1] << endl;// 2.创建子进程pid_t id = fork();assert(id != -1);if (id == 0){// 子进程// 3.构建单向通信的信道,父进程写入,子进程读取// 3.1关闭子进程不需要的fdclose(pipefd[1]);char buffer[1024];while (true){ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = 0;cout << "child get a message [" << getpid() << "] Father# " << buffer << endl;}}exit(0);}// 父进程// 构建单向通信的信道// 3.1 关闭父进程不需要的fdclose(pipefd[0]);string message = "我是父进程,我正在给你发消息";int count = 0;char send_buffer[1024];while (true){// 3.2 构建一个变化的字符串snprintf(send_buffer, sizeof(send_buffer), "%s[%d] : %d", message.c_str(), getpid(), count++);// 3.3 写入write(pipefd[1], send_buffer, strlen(send_buffer));// 3.4 sleepsleep(1);}pid_t ret = waitpid(id, nullptr, 0);assert(ret > 0);close(pipefd[1]);return 0;
}
然后此时我们编译运行 :

可以看到,父进程写入的内容,子进程全部读到了。
这里是静态图,看不出效果,这些信息其实是时隔1秒打印一条的.
我们看只有子进程在读取,可子进程我们并没有加任何的sleep啊,理论上应该子进程一直打印才对啊。
显示器是一个文件,而管道也是一个文件,父子进程同时向显示器 写入时,没有说一个进程会等另一个进程,而是各自打印各自的消息,互相干扰,这是缺乏访问控制。而管道文件提供了访问控制,使得子进程读取完得等待父进程写入才能读取。
这样,如果我们让子进程读取先sleep 10秒,期间父进程每隔1秒写入,等10秒过后,子进程开始读取,但会把 父进程写入10次的文件的内容全部一下读出来。这说明写入次数和读取次数没有直接关系。即管道是面向字节流的,具体怎么读需要定制协议,后面会说,
这里就针对与管道的特点做一些总结:
- 管道是用来进行具有血缘关系的进程进行进程间通信 --- 常用于父子间通信
- 管道具有通过让进程间协同,提供了访问控制
- 管道提供的是面向字节流式的通信服务 --- 面向字节流 --- 通过定制协议实现
- 管道是基于文件的,文件的生命周期是随进程的,即管道的生命周期也随进程的!
- 管道是单向通信的,就是半双工通信的一种特殊方式.
上面最后一条提到了半双工概念,这里来解释一下:
半双工:通信的双方只能在同一时间点单向的传输数据,即两个参与者不能同时发送和接收数据。在半双工通信中,通信双方必须交替使用共享的通信信道。例如,当一个人在对讲机上说话时,另一个人必须停止接收,然后才能回应。典型的半双工通信方式包括对讲机和卫星电台。
全双工:全双工通信允许在同一时间点双向地传输数据。这意味着通信的两个参与者能够同时发送和接收数据,而不需要交替使用通信信道。在全双工通信中,通信双方可以同时进行发送和接收操作,彼此之间的数据传输互不干扰。例如,电话通话是一个典型的全双工通信场景,双方可以同时说话和倾听对方的声音。
顺带总结一下管道的几种情况:
a.写快,读慢,写满就不能再写了
b.写慢,读快,管道没有数据时,读必须等待
这两种是由访问控制提供的.
c.写关,读继续读,会标识读到了文件结尾
d.写继续写,读关,OS会终止写进程
拓展代码
利用匿名管道的方式,创建多个子进程,然后父进程分别派发随机的任务,
总代码流程是:父进程首先load()加载方法,然后for循环创建多个进程,每次创建完成后,该进程都要与父进程(pipefd[1])建立关联,以方便父进程管理这些子进程。
其中每个子进程调用 waitCommand函数,会阻塞在read,等待着父进程的写入,然后父进程开始分发任务,当是对应的子进程时,子进程会执行对应的任务,然后继续while循环等待。
共两个文件,第一个文件ProcessPool.cc文件
#include <iostream> #include <vector> #include <ctime> #include <unistd.h> #include <stdlib.h> #include <assert.h> #include <sys/types.h> #include <sys/wait.h> #include "Task.hpp" using namespace std;#define PROCESS_NUM 5 int waitCommand(int waitFd, bool quit) // 如果对方不发任务就阻塞 {uint32_t command = 0;ssize_t s = read(waitFd, &command, sizeof(command));if (s == 0){quit = true;return -1;}assert(s == sizeof(command));return command; } void sendAndWakeup(pid_t who, int fd, uint32_t command) {write(fd, &command, sizeof(command));cout << "main process: call process " << who << " execute " << desc[command] << " through " << fd << endl; }int main() {load();// pid : pipefdvector<pair<pid_t, int>> slots;// create multiple child processfor (int i = 0; i < PROCESS_NUM; i++){// 创建管道int pipefd[2] = {0};int n = pipe(pipefd);assert(n == 0);pid_t id = fork();assert(id != -1);// 让子进程读取if (id == 0){// 关闭写端close(pipefd[1]);// child processwhile (true){// 等命令bool quit = false;int command = waitCommand(pipefd[0], quit); // 如果对方不发任务就阻塞if (quit)break;// 执行对应的命令if (command >= 0 && command < handlerSize()){callbacks[command]();}else{cout << "非法 command" << endl;}}exit(1);}// father processclose(pipefd[0]);slots.push_back(pair<pid_t, int>(id, pipefd[1]));}// 父进程派发任务srand((unsigned long)time(nullptr) ^ getpid() ^ 2311156L);while (true){ //选择一个任务 int command = rand() % handlerSize();//选择一个进程,采用随机数的方式,选择进程来完成任务,随机数的方式负载均衡int choice = rand() % slots.size();// 把任务给指定的进程sendAndWakeup(slots[choice].first, slots[choice].second, command);sleep(1);int select;//以下是手动派发任务// int command = 0;// cout << "######################################" << endl;// cout << "1.show functions 2.send command" << endl;// cout << "######################################" << endl;// cout << "Please Select > ";// cin >> select;// if (select == 1)// showHandler();// else if (select == 2)// {// cout << "Enter your Command > ";// // 选择任务// cin >> command;// // 选择进程// int choice = rand() % slots.size();// // 把任务给指定的进程// sendAndWakeup(slots[choice].first, slots[choice].second, command);// }// else// {// }}// 关闭fd,结束所有进程for (auto &slot : slots){close(slot.second);}// 回收所有子进程for (auto &slot : slots){waitpid(slot.first, nullptr, 0);}return 0; }
第二个文件为Task.hpp文件,主要是包含了任务的加载及任务的执行方法。
#pragma once #include<iostream> #include<vector> #include<string> #include<unordered_map> #include<unistd.h> #include<functional> using namespace std;typedef function<void()> func;vector<func> callbacks; unordered_map<int,string> desc;void readMySQL() {cout << "sub process[" << getpid() << "] 执行数据库被访问的任务\n" << endl; } void executeURL() {cout << "sub process[" << getpid() << "] 执行url解析任务\n" << endl; } void cal() {cout << "sub process[" << getpid() << "] 执行加密任务\n" << endl; } void save() {cout << "sub process[" << getpid() << "] 执行数据持久化\n" << endl; }void load() {desc.insert({callbacks.size(),"readMySQWL:读取数据库"});callbacks.push_back(readMySQL);desc.insert({callbacks.size(),"executeURL:解析URL"});callbacks.push_back(executeURL);desc.insert({callbacks.size(),"cal:进行加密计算"});callbacks.push_back(cal);desc.insert({callbacks.size(),"save:进行数据的文件保存"});callbacks.push_back(save); } void showHandler() {for(auto& iter: desc){cout << iter.first << "\t" << iter.second << endl;} } int handlerSize() {return callbacks.size(); }
这样每次父进程都会随机给子进程派发随机的任务:

命名管道
与匿名管道不同,命名管道不需要亲缘关系的进程之间,也不需要共享同一终端。任意进程可以通过打开命名管道的读取端和写入端来与其进行通信。
什么是命名管道
命名管道(Named Pipe)是一种独立进程之间通信的机制,用于在无关的进程之间进行数据传输。
命名管道通过在文件系统中创建一个特殊的文件来实现通信。这个特殊的文件被称为FIFO(First-in, First-out)或命名管道。
命名管道通信的原理
和匿名管道一样,想让双方通信,必须先让双方看到同一份资源!它和匿名管道本质是一样的,只是看到资源的方式不同。
匿名管道是通过父子进程继承来看到同一份资源的,也叫做管道文件,这个文件是纯内存级的,所以没有名字,叫做匿名管道。
而命名管道是在磁盘上有一个特殊的文件,这个文件可以被打开,但是打开后不会将内存中的数据刷新到磁盘。在磁盘上就有了路径,而路径是唯一的,所以双方就可以通过文件的路径 来看到同一份资源,即管道文件。
这是命名管道的流程:
创建命名管道:通过调用系统调用
mkfifo()在文件系统中创建一个特殊的文件,这个文件就是命名管道。创建命名管道时,需要指定管道的名称和所需的权限。打开命名管道:进程通过调用系统调用
open()来打开命名管道,得到一个文件描述符。进程可以通过打开具有相同名称的文件来打开命名管道的读取端和写入端。进程通信:一旦命名管道被打开,进程就可以使用文件描述符进行通信。每个进程可以选择读取端或写入端与命名管道进行交互。
数据传输:进程在读取端通过调用
read()系统调用从命名管道中读取数据,而在写入端通过调用write()系统调用将数据写入命名管道中。读取端和写入端可以通过文件描述符进行数据的发送和接收。关闭命名管道:进程完成通信后,可以通过调用
close()关闭命名管道的文件描述符来释放资源。当所有对命名管道的引用都被关闭时,管道的文件系统条目将被删除。
mkfifo的使用
上面提到了需要使用mkfifo来创建这个特殊的文件,来让独立的进程之间通过它进行通信,下面来看一下它的用法。
mkfifo [选项] 文件名
非常简单的使用方法,选项我们一般不用,所以直接mkfifo + 文件名即可
我们在当前路径下创建一个name_pipe的文件.

注意权限的最前面是p,代表是管道文件。
我们此时echo一句消息到这个管道文件中:

我们发现这里阻塞住了,这是因为一方向管道文件里写入了,但是另外一方还没有读,所以此时我们新建一个窗口,然后读取name_pipe里的内容:

这样信息便成功的被读取出来了,这就是mkfifo的简单使用。
代码模拟命名管道通信过程
其实过程和匿名管道类似,只是看到同一资源的手段不一样。
上面讲的mkfifo是指令创建,但是如果我想用代码该如何实现呢?这里有一个mkfifo函数:

第一个参数是创建的管道文件的路径,第二个是权限。

当创建成功时,mkfifo返回0,否则返回-1.
整体的流程是是这样的:
我们首先可以分成 服务端 和 客户端,服务端负责
1.创建管道文件并打开
2.进行与客户端正常的通信
3.最后关闭并删除管道文件
而客户端
1.首先要打开管道文件
2.然后进行与服务端正常的通信流程即可
这里为了方便,我们加入了日志,可以看到每一步的动作。
所以一共四个文件,分别为comm.hpp,client.cc,server.cc,Log.hpp.
comm.hpp
#pragma once #include<iostream> #include<cstdio> #include<cstring> #include<unistd.h> #include<vector> #include<string> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h>using namespace std;#define MODE 0666 #define SIZE 128 string ipcPath = "./fifo.ipc";client.cc
#include "comm.hpp" int main() {//1.获取管道文件int fd = open(ipcPath.c_str(),O_WRONLY);if(fd < 0){perror("open");exit(1); }//2.通信过程string buffer;while(true){cout << "please Enter Message Line :> ";getline(cin,buffer);write(fd,buffer.c_str(),buffer.size());}//3.关闭文件return 0; }server.cc
#include"comm.hpp" #include"Log.hpp" int main() {//1.创建管道文件if(mkfifo(ipcPath.c_str(),MODE) < 0){perror("mkfifo");exit(1);}Log("创建管道文件成功",Debug) << "step 1" << endl;//2.正常的文件操作int fd = open(ipcPath.c_str(),O_RDONLY);if(fd < 0){perror("open");exit(2);}Log("打开管道文件成功",Debug) << "step 2" << endl;//3.编写正常的通信代码char buffer[SIZE]; while(true){memset(buffer,'\0',sizeof(buffer));ssize_t s = read(fd,buffer,sizeof(buffer)-1);if(s > 0){cout << "client say> " << buffer << endl;}else if(s == 0){//end of filecerr << "read emd of file, client quit, server quit too!" << endl;break;}else{//read errorperror("read");}}//4.关闭文件close(fd);Log("关闭管道文件成功",Debug) << "step 3" << endl;unlink(ipcPath.c_str());//通信完毕就删除文件Log("删除管道文件成功",Debug) << "step 4" << endl;return 0; }Log.hpp
#pragma once #include <iostream> #include <ctime> #include<string> using namespace std;#define Debug 0 #define Notice 1 #define Warning 2 #define Error 3string msg[] = {"Debug ","Notice","Warning","Error" };ostream& Log(string message,int level) {cout << " | " << (unsigned)time(NULL) << " | " << msg[level] << " | " << message;return cout; }
然后我们再编译运行,可以再创建一个Makefile文件,直接编译好所有的文件,内容如下:
.PHONY:all all:client serverclient:client.ccg++ -o $@ $^ -std=c++11 server:server.ccg++ -o $@ $^ -std=c++11.PHONY:clean clean:rm -rf client server
此时我们直接make即可。然后会得到两个可执行文件client和server.
我们打开两个窗口,首先运行server.

第一步创建管道完成,然后我们在另一个窗口运行客户端.

运行起来后,显示打开文件也成功了,这个时候,我们在客户端输入,服务端都能读取到:
然后我们ctrl + c 退出客户端,此时服务端也会break跳出循环,然后结束.

这样,利用命名管道通信的代码流程也就完成了.
相关文章:
【Linux】进程间通信——管道
目录 写在前面的话 什么是进程间通信 为什么要进行进程间通信 进程间通信的本质理解 进程间通信的方式 管道 System V IPC POSIX IPC 管道 什么是管道 匿名管道 什么是匿名管道 匿名管道通信的原理 pipe()的使用 匿名管道通信的特点 拓展代码 命名管道 什么是命…...
Element-plus中tooltip 提示框修改宽度——解决方案
tooltip 提示框修改宽度方法: 在element中,想要设置表格的内容,超出部分隐藏,鼠标悬浮提示 可以在el-table 上添加show-overflow-tooltip属性 同时可以通过tooltip-options配置提示信息 如下图代码 <el-tableshow-overflo…...
java实现当前系统时间格式化
import java.text.SimpleDateFormat; import java.util.Date;public class DateTest {public static void main(String[] args) {Date date new Date();System.out.println("当前系统时间:" date);SimpleDateFormat simpleDateFormat new SimpleDateFo…...
篇十一:享元模式:共享细粒度对象
篇十一:“享元模式:共享细粒度对象” 设计模式是软件开发中的重要工具,享元模式(Flyweight Pattern)是结构型设计模式的一种。享元模式旨在通过共享细粒度的对象,减少内存消耗和提高性能。在设计模式学习中…...
Dev控件 Gridcontrol,gridview 实现多选功能
在网上看了好多实现dev控件GridControl多选功能的方法,都很麻烦,其实GridControl有一个自带的实现多选功能的控件,很简单。 实现效果如下 无需代码代码,使用GridControl中自带的多选功能,在界面直接设置即可 1.找到要…...
内网穿透:如何通过公网访问本地Web服务器?
文章目录 前言1. 首先安装PHPStudy2.下载一个开源的网页文件3. 选择“创建网站”并将网页内容指向下载好的开源网页文件4. 打开本地网页5. 打开本地cpolar客户端6. 保存隧道设置 生成数据隧道 前言 随着科技进步和时代发展,计算机及互联网已经深深融入我们的生活和…...
在qemu中挂载镜像文件
将镜像文件作为交换分区 创建镜像文件: dd if/dev/zero ofswap.img bs512 count131072 qemu-system-arm -nographic -M vexpress-a9 -m 64M -kernel arch/arm/boot/zImage -append "rdinit/linuxrc consolettyAMA0 loglevel8" -dtb arch/arm/boot/dts/…...
报错注入(主键重复)攻击原理
基本原理 利用数据表中主键不能重复的特点,通过构造重复的主键,使得数据库报错,并将报错结果返回到前端。 SQL说明函数 以pet数据表为例进行说明 rond(): 返回[0,1)区间内的任意浮点数。 count(): 返回每个组的列行数。 如࿰…...
Golang基础教程
Golang基础教程 golang简介安装golanggolang开发工具go常用命令golang开发 vscode快捷键如何编写golang代码golang标识符、关键字、命名规则golang变量go语言常量go语言数据类型go语言布尔类型go语言数字类型golang字符串golang格式化输出golang运算符go语言中的流程控制golan…...
ppt压缩文件怎么压缩最小?文件压缩技巧分享
在日常的工作和学习中,难免会遇到PPT太大,需要将其压缩变小的情况,但很多朋友还不知道怎么压缩PPT文件,下面就给大家分享几个简单的方法,分分钟缩小过大的PPT文件。 一、PowerPoint PowerPoint就是微软公司的演示文稿…...
实例033 制作闪烁的窗体
实例说明 Windows系统中,当程序在后台运行时,如果某个窗口的提示信息需要用户浏览,该窗口就会不停的闪烁,这样就会吸引用户的注意。同样,如果在自己的程序中使某个窗口不停的闪烁就会吸引用户的注意。本例设计了一个闪…...
【JavaEE进阶】Spring创建与使用
文章目录 一. 创建 Spring 项目1.1 创建一个Maven项目1.2 添加Spring依赖1.4. 创建一个启动类 二. 将 Bean 对象存放至 Spring 容器中三. 从 Spring 容器中读取到 Bean1. 得到Spring对象2. 通过Spring 对象getBean方法获取到 Bean对象【DI操作】 一. 创建 Spring 项目 接下来使…...
PHP8的循环控制语句-PHP8知识详解
我们在上一节讲的是条件控制语句,本节课程我们讲解循环控制语句。循环控制语句中,主要有for循环、while循环、do...while循环和foreach循环。 在编写代码时,经常需要反复运行同一代码块。我们可以使用循环来执行这样的任务,而不是…...
第八次作业
一,.什么是数据认证,有什么作用,有哪些实现的技术手段? 数据认证是指保证数据的真实性、完整性和可信度,以确保数据不被篡改或伪造。其作用包括但不限于: 保护关键数据不被恶意篡改或损坏 提供数据来源的可靠性和安全…...
LeetCode //C - 290. Word Pattern
290. Word Pattern Given a pattern and a string s, find if s follows the same pattern. Here follow means a full match, such that there is a bijection between a letter in pattern and a non-empty word in s. Example 1: Input: pattern “abba”, s “dog c…...
[保研/考研机试] 括号匹配问题 C++实现
题目描述: 在某个字符串(长度不超过100)中有左括号、右括号和大小写字母;规定(与常见的算数式子一样)任何一个左括号都从内到外与在它右边且距离最近的右括号匹配。写一个程序,找到无法匹配的左括号和右括号,输出原来的字符串&am…...
springBoot集成caffeine,自定义缓存配置 CacheManager
目录 springboot集成caffeine Maven依赖 配置信息:properties文件 config配置 使用案例 Caffeine定制化配置多个cachemanager springboot集成redis并且定制化配置cachemanager springboot集成caffeine Caffeine是一种基于服务器内存的缓存库。它将数据存储在…...
【瑞吉外卖】Git部分学习
Git简介 Git是一个分布式版本控制工具,通常用来对软件开发过程中的源代码文件进行管理。通过Git仓库来存储和管理这些文件,Git仓库分为两种: 本地仓库:开发人员自己电脑上的Git仓库 远程仓库:远程服务器上的Git仓库…...
如何阐述自己做了一个什么样的东西
线上qps2000,主要的性能瓶颈在于出现在数据库I/O上。另外,如果是一个正常部署的容器,qps能达到几百就不错了。资讯服务现在做了静态的底层页,所以热点新闻多数会命中底层页,即便没有命中底层页,也会走多层的…...
TC3XX - MCAL知识点(二十二):QSPI 同步与异步 Mcal配置及代码实战
目录 1、MCAL配置 1.1、配置目标 1.2、同步QSPI配置 1.2.1、SpiGeneral 1.2.2、SpiMaxChannel 1.2.3、SpiMaxJob...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...
基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...
学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...
使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度
文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...
FFmpeg:Windows系统小白安装及其使用
一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】,注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录(即exe所在文件夹)加入系统变量…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
