Linux系统编程 day06 进程间通信

news2024/11/18 17:34:47

进程间通信

  • 1. 进程间通信的概念
  • 2. 匿名管道pipe
  • 3. 命名管道FIFO
  • 4. 内存映射区

1. 进程间通信的概念

在Linux的环境下,进程地址空间是相互独立的,每个进程有着各自不同的用户地址空间。一个进程不能访问另一个进程中的内容,要进行数据交换必须要通过内核,在内核中开辟块缓冲区,一个进程将数据从用户空间拷贝到内核缓冲区中,另一个进程从内核缓冲区中将数据读走。内核提供的这种机制称为进程间通信(IPC)。

在进程间完成数据传输需要借助操作系统提供的特殊方法,比如文件、管道、FIFO、内存映射区、消息队列、信号、套接字等。如今常用的进程间通信主要有管道(最简单)、信号(开销最小)、内存映射区(无血缘关系)、本地套接字(最稳定)。

2. 匿名管道pipe

管道是一种最基本的进程间通信机制,也称为匿名管道,应用于有血缘关系的进程间进行通信。管道的本质是一块内核缓冲区,内部使用的环形队列实现,由两个文件描述符进行引用,其中一个表示读端,另一个表示写端。管道的数据从管道的写端流入管道,从读端流出。当两个进程都死亡的时候,管道也会自动消失。管道不管读端还是写端默认都是阻塞的。管道的默认缓冲区大小为4K,可以使用ulimit -a命令获取大小。

在这里插入图片描述

管道的数据一旦被读走,便不在管道中存在,不可以反复读取。管道的数据只能在一个方向上流动,如果需要实现双向流动则需要使用两个管道。匿名管道只能在有血缘关系的进程中使用。我们用pipe函数来创建管道。

在这里插入图片描述

pipe函数的原型如下:

       #include <unistd.h>
			
       int pipe(int pipefd[2]); // 创建管道

其中pipefd为管道写端和读端的文件描述符,其中pipefd[0]为管道读端的文件描述符,pipefd[1]为管道写端的文件描述符。当函数调用成功创建了管道返回0,失败则返回-1并设置errno。

在使用匿名管道进行通信的时候,一般是先用pipe函数创建管道,再使用fork函数创建子进程。这样父子进程就具有了相同的文件描述符,就会指向同一个管道。

在管道的通信中,读写数据也是使用readwrite。管道的示例代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>

int main()
{
	// 创建管道
	//int pipe(int pipefd[2]);
	int fd[2];
	int ret = pipe(fd);
	if(ret < 0)
	{
		perror("pipe error");
		return -1;
	}

	// 创建子进程
	pid_t pid = fork();
	if(pid < 0)
	{
		perror("fork error");
		return -1;
	}
	else if(pid == 0)
	{
		// 子进程关闭写端
		close(fd[1]);
		char buf[1024];
		memset(buf, 0x00, sizeof(buf));
		sleep(5);
		read(fd[0], buf, sizeof buf);
		printf("child: read over, pid = [%d], fpid = [%d], buf = [%s]\n", getpid(), getppid(), buf);
	}
	else
	{
		// 父进程关闭读端
		close(fd[0]);
		write(fd[1], "helloworld", strlen("helloworld"));
		printf("father: write over, pid = [%d], fpid = [%d]\n", getpid(), getppid());
		pid_t wpid = wait(NULL);
		printf("child [%d] is dead!\n", wpid);
	}

	return 0;
}

在shell中我们查询某一个进程的时候我们会使用ps -ef | grep --color=auto bash,这里的|就是管道。我们使用父进程进程去执行ps -ef命令,由于我们需要交给grep去作为输入,所以在父进程中需要将输出重定向到管道的写端。子进程执行grep的时候会从输入进行读取内容,因此我们需要将输入重定向到管道读端。重定向的时候需要使用dup2函数,需要执行命令则需要使用execlexeclp函数,示例程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>


int main()
{
	// 创建管道
	int pipefd[2];
	int ret = pipe(pipefd);
	if(ret < 0)
	{
		// 创建失败
		perror("pipe error");
		return -1;
	}
	// 创建子进程
	pid_t pid = fork();
	if(pid < 0)
	{
		// 创建失败
		perror("fork error");
		return -1;
	}
	else if(pid == 0)
	{
		// 子进程关闭写端
		close(pipefd[1]);
		// 将标准输入重定向到管道读端
		dup2(pipefd[0], STDIN_FILENO);
		execlp("grep", "grep", "--color=auto", "bash", NULL);
		perror("execlp error");
	}
	else 
	{
		// 父进程关闭读端
		close(pipefd[0]);
		// 将标准输出重定向到管道写端
		dup2(pipefd[1], STDOUT_FILENO);
		execlp("ps", "ps", "-ef", NULL);
		perror("execlp error");
	}
	return 0;
}

当管道有数据的时候,read可以正常读,并返回读出来的字节数;当管道没有数据的时候,若写端全部关闭,则read函数解出阻塞状态,返回0,相当于读文件读到了尾部。若写端没有关闭,则read阻塞。

若读端全部关闭,进行写操作的时候则管道会破裂,进程终止,内核会给当前进程发送SIGPIPE信号。若读端没有完全关闭,缓冲区写满了则write会阻塞,缓冲区没有满则可以继续write

管道默认两端都是阻塞的,若要设置为非阻塞,则可以使用前面提过的fcntl函数。首先使用F_GETFL获取flags,然后在添加O_NONBLOCK使用F_SETFL设置即可。当读端设置为非阻塞状态的时候,会有以下四种情况:

  • 写端没有关闭,管道中没有数据可读,则read返回-1。
  • 写端没有关闭,管道中有数据可读,则read返回实际读到的字节数。
  • 写端已经关闭,管道中有数据可读,则read返回实际读到的字节数。
  • 写端已经关闭,管道中没有数据可读,则read返回0。

设置的流程为:

int flags = fcntl(fd[0], F_GETFL, 0);
flags = flags | O_NONBLOCK;
fcntl(fd[0], F_SETFL, flags);

除了前面使用的ulimit -a可以查看到管道缓冲区的大小之外,也可以使用fpathconf函数,该函数的原型为:

       #include <unistd.h>

       long fpathconf(int fd, int name); 

这个函数会根据name的参数设置返回文件描述符fd对应文件的配置内容。比如获取管道缓冲区则需要把name设置为_PC_PIPE_BUF。如下面查看管道的缓冲区大小。

printf("pipe size = [%ld]\n", fpathconf(fd[0], _PC_PIPE_BUF));
printf("pipe size = [%ld]\n", fpathconf(fd[1], _PC_PIPE_BUF));

更多的name参数设置可以使用man fpathconf查询帮助文档。

3. 命名管道FIFO

使用pipe管道只能实现有血缘关系的两个进程间的通信,那么对于两个没有血缘关系的进程又应该怎么实现呢?则需要这一节的命名管道FIFO

FIFO是Linux上基于文件类型的一种通信方式,文件类型为p,但是FIFO在磁盘上并没有数据块,文件大小为0,仅仅用于标识内核中的一条通道。进程可以通过read或者write函数去对这一条通道进行操作,也就是内核缓冲区,这样就实现了进程间的通信。

要使用命名管道,就得先创建命名管道文件,创建管道文件使用mkfifo函数,该函数的原型为:

       #include <sys/types.h>
       #include <sys/stat.h>

       int mkfifo(const char *pathname, mode_t mode);

其中参数第一个pathname表示管道的路径名,mode表示权限,使用一个三位八进制数表示。创建成功函数返回0,创建失败则函数返回-1并设置errno

FIFO严格遵守先进先出的规则,也就是对于管道的读总是从管道的开始处返回数据,而对于管道的写则是添加到末尾。因此命名管道不支持使用lseek等对文件定位的操作。

使用FIFO完成进程间通信的示意图如下:

在这里插入图片描述

既然是两个进程,也就是说两个进程中的程序都需要打开管道,也就是需要找到文件。若写进程没有打开就去使用读进程,则可能因为管道的文件不存在而报错。因此我们在使用管道或者创建管道之前要先判断文件是否存在。这个可以使用access函数实现,该函数的原型为:

       #include <unistd.h>

       int access(const char *pathname, int mode);

该函数的第一个参数pathname是文件的路径,第二个参数mode表示要测试的模式,有四个参数可以传,分别为:

  • F_OK: 文件存在
  • R_OK:有读权限
  • W_OK:有写权限
  • X_OK:有执行权限

当有对应的权限或者文件存在的时候,该函数的返回值为0,若没有对应权限或者文件不存在则该函数返回-1

接下来我们实现两个无血缘关系之间的进程间通信。代码如下:

fifo_write.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

// FIFO 写进程
int main()
{
	// 创建FIFO文件
	// int access(const char *pathname, int mode);
	int ret = access("./myfifo", F_OK);
	if(ret < 0)
	{
		// int mkfifo(const char *pathname, mode_t mode);
		ret = mkfifo("./myfifo", 0777);
		if(ret < 0)
		{
			perror("mkfifo error");
			return -1;
		}
	}

	// 打开FIFO
	int fd = open("./myfifo", O_RDWR);
	if(fd < 0)
	{
		perror("open error");
		return -1;
	}

	// 传输数据
	char buf[1024] = "hello world";
	write(fd, buf, strlen(buf));
	sleep(1);

	// 关闭FIFO
	close(fd);

	return 0;
}

fifo_read.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

// FIFO 读进程端
int main()
{
	// 判断创建FIFO
	int ret = access("./myfifo", F_OK);
	if(ret < 0)
	{
		int ret = mkfifo("./myfifo", 0777);
		if(ret < 0)
		{
			perror("mkfifo error");
			return -1;
		}
	}

	// 打开FIFO
	int fd = open("./myfifo", O_RDWR);
	if(fd < 0)
	{
		perror("open error");
		return -1;
	}

	// 接收数据
	char buf[1024];
	memset(buf, 0x00, sizeof buf);
	read(fd, buf, sizeof buf);
	printf("%s\n", buf);

	// 关闭FIFO
	close(fd);

	return 0;
}

在上述代码中,只要运行,不管是读进程还是写进程先运行,只要写进程没有往管道中写数据,读进程都会阻塞在read处。直到写进程调用write函数往管道中写数据之后读进程才会读出来管道中的数据输出。

4. 内存映射区

内存映射区是将一个磁盘文件与内存空间的一个缓冲区向映射。当我们在缓冲区中读取数据的时候,就相当于在文件中读取相应的字节。若我们向缓冲区中写数据,则会把数据写入对应的文件之中。这样就可以在不使用read函数和write函数啊的情况下使用指针完成IO操作。

在这里插入图片描述

映射这个过程可以使用mmap函数实现。解除映射可以使用munmap实现。函数的原型以及参数如下:

       #include <sys/mman.h>
			
		// 函数作用: 建立存储映射区
		// 返回值: 成功:返回创建的映射区首地址
		//		  失败: 返回MAP_FAILED宏, 实际上就是(void *)-1
		// 参数: addr: 指定映射的起始地址,通常设为NULL,由系统指定
		//      length: 映射到内存的文件长度
		//       prot: 映射区的保护方式,最常用的有
		//                PROT_READ   				读
		//                PROT_WRITE  				写
		//                PROT_READ | PORT_WRITE    读写
		//       flags: 映射区的特性,可以设置以下
		//                MAP_SHARED   写入映射区的数据会写回文件,且允许
		//							   其它映射改文件的进程共享。
		//                MAP_PRIVATE  对映射区的写入操作会产生一个映射区的复
		//  						 	制,对此区域所做的修改不会写回原文件。
		//                MAP_ANONYMOUS  匿名映射区,需要结合MAP_SHARED使用。
		// 	      fd: 代表要映射的文件,由open函数返回的文件描述符
		//        offset: 以文件开始出的偏移量,必须是4K的整数倍,通常为0,表示
		//                从文件头开始映射
       void *mmap(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset);

		// 函数作用: 释放由mmap函数建立的存储映射区
		// 返回值: 成功返回0,失败返回-1并设置errno
		// 参数: addr: 调用mmap函数成功返回的映射区首地址
		//  	 length: 映射区的大小
       int munmap(void *addr, size_t length);

需要注意到的是,mmap函数不能开辟长度为0的存储映射区。在使用mmap函数创建映射区的过程中,隐含着一次对映射文件的读操作,将文件读取到映射区。当我们将flags设置为MAP_SHARED的时候,要求映射区的权限应该小于或者等于文件打开的权限,这是出于对映射区的保护。而对于MAP_PRIVATE,则是没有这个必要,因为mmap中的权限是对内存的限制。

在映射区建立之后,映射区的释放是与文件的关闭无关的,所以在映射区建立之后就可以关闭文件。在创建映射区的时候,使用mmap函数常常会出现总线错误,通常是因为共享文件存储空间大小引起的。所以创建映射区的时候出错的概率比较高,因此一定要检查函数的返回值,确保映射区建立成功了再进行后续的操作。使用munmap函数的时候,传入的地址一定要是mmap函数的返回值,一定不要改变指针的指向。其中函数的参数中offset也是需要注意的,不能随便指定,必须要是4K的整数倍才行。

下面给出一个关于关于有血缘关系的进程间的通信示例。

//使用mmap完成有血缘关系进程间的通信
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
	//使用mmap函数建立共享映射区
	//void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
	int fd = open("test.log", O_RDWR);
	if(fd < 0)
	{
		perror("open error");
		return -1;
	}

	int len = lseek(fd, 0, SEEK_END);

	void *addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if(addr == MAP_FAILED)
	{
		perror("mmap error");
		return -1;
	}

	close(fd);

	//创建子进程
	pid_t pid = fork();
	if(pid < 0)
	{
		perror("fork error");
		return -1;
	}
	else if(pid == 0)
	{
		//子进程
		sleep(2);
		char *p = (char *)addr;
		printf("[%s]\n", p);
	}
	else
	{
		//父进程
		memcpy(addr, "hello world", strlen("hello world"));
		wait(NULL);
	}

	return 0;
}

共享存储映射区也可以用于没有血缘关系的两个进程。

写进程

// 使用mmap完成没有血缘关系的进程间的通信
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>


// 写进程
int main()
{
	// 使用mmap函数建立共享映射区
	int fd = open("test.log", O_RDWR);
	if(fd < 0)
	{
		perror("open error");
		return -1;
	}

	int len = lseek(fd, 0, SEEK_END);

	// 建立共享映射区
	void *addr = mmap(NULL, len, PROT_WRITE, MAP_SHARED, fd, 0);
	if(addr == MAP_FAILED)
	{
		perror("mmap error");
		return -1;
	}

	close(fd);

	memcpy(addr, "Good morning", strlen("Good morning"));

	return 0;
}

读进程

// 使用mmap完成没有血缘关系的两个进程间的通信
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>

// 读进程端
int main()
{
	// 使用mmap函数建立共享映射区
	int fd = open("test.log", O_RDONLY);
	if(fd < 0)
	{
		perror("open error");
		return -1;
	}

	int len = lseek(fd, 0, SEEK_END);

	// 建立共享映射区
	void *addr = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
	if(addr == MAP_FAILED)
	{
		perror("mmap error");
		return -1;
	}

	char *p = (char *)addr;
	printf("[%s]\n", p);

	return 0;
}

上面的共享存储映射区都是有名字的,我们也可以创建匿名的共享存储映射区。匿名映射区是不需要使用文件去创建的,因此没有血缘关系的两个进程不能使用匿名映射区通信。在使用匿名共享映射区的时候,文件的描述符一般传为-1。关于匿名共享映射区的示例代码如下:

// 建立匿名共享映射区
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <fcntl.h>

int main()
{
	void *addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
	if(addr == MAP_FAILED)
	{
		perror("mmap error");
		return -1;
	}

	pid_t pid = fork();
	if(pid < 0)
	{
		perror("fork error");
		return -1;
	}
	else if(pid == 0)
	{
		sleep(2);
		char *p = (char *)addr;
		printf("[%s]\n", p);
	}
	else
	{
		memset(addr, 0x00, 4096);
		memcpy(addr, "hello world", strlen("hello world"));
		wait(NULL);
	}

	return 0;
}

后续博客关于函数原型以及作用均以注释的形式写在代码中以便直观

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[MySQL--基础]函数、约束

hello! 这里是欧_aita的频道。 今日语录:不管你觉得自己能做什么&#xff0c;或者你觉得你不能做什么&#xff0c;你都是对的。 祝福语&#xff1a;愿你的程序像太阳一样明亮&#xff0c;给世界带来温暖和光明。 大家可以在评论区畅所欲言&#xff0c;可以指出我的错误&#xf…

雷达公式实现(matlab)

雷达公式实现 代码来源&#xff1a;《雷达系统分析与设计(MATLAB版)(第三版)》 function [snr] radar_eq(pt,freq,g,sigma,b,nf,loss,range) % This program implements Eq.(1.63) %% Inputs:% pt——峰值功率&#xff0c;W% freq——雷达中心频率&#xff0c;Hz% g——天线…

Javaweb之Vue组件库Element之Form表单的详细解析

4.3.4 Form表单 4.3.4.1 组件演示 Form 表单&#xff1a;由输入框、选择器、单选框、多选框等控件组成&#xff0c;用以收集、校验、提交数据。 表单在我们前端的开发中使用的还是比较多的&#xff0c;接下来我们学习这个组件&#xff0c;与之前的流程一样&#xff0c;我们首…

AI 重构工业制造的故事 我们从大模型开始讲起

在数字化浪潮的推动下&#xff0c;工业制造领域正经历着一场前所未有的变革。人工智能&#xff08;AI&#xff09;作为这场变革的关键推动者之一&#xff0c;正以惊人的速度颠覆传统制造业。而大模型作为AI时代最先进的科技工具之一&#xff0c;或将成为引领这场变革的利器&…

零基础学Python第三天||写一个简单的程序

通过对四则运算的学习&#xff0c;已经初步接触了Python中内容&#xff0c;如果看官是零基础的学习者&#xff0c;可能有点迷惑了。难道敲几个命令&#xff0c;然后看到结果&#xff0c;就算编程了&#xff1f;这也不是那些能够自动运行的程序呀&#xff1f; 的确。到目前为止…

C语言错误处理之“非局部跳转<setjmp.h>头文件”

目录 前言 setjmp宏 longjmp函数 使用方法&#xff1a; 实例&#xff1a;测试setjmp与longjmp的使用 前言 通常情况下&#xff0c;函数会返回到它被调用的位置&#xff0c;我们无法使用goto语句改变它的返回的方向&#xff0c;因为goto语句只能跳转到同一函数内的某个标号…

数字档案馆档案咨询工作内容有哪些

档案咨询通常是指关于如何管理、存储、保护档案资料的问题。随着电子档案的日渐兴起&#xff0c;数字档案馆室的建设也是如火如荼&#xff0c;随之数字档案馆档案咨询的工作也逐渐增多。以下就是专久智能关于数字档案馆档案咨询工作内容有哪些的简要介绍&#xff1a; 1. 提供档…

软著项目推荐 深度学习动物识别 - 卷积神经网络 机器视觉 图像识别

文章目录 0 前言1 背景2 算法原理2.1 动物识别方法概况2.2 常用的网络模型2.2.1 B-CNN2.2.2 SSD 3 SSD动物目标检测流程4 实现效果5 部分相关代码5.1 数据预处理5.2 构建卷积神经网络5.3 tensorflow计算图可视化5.4 网络模型训练5.5 对猫狗图像进行2分类 6 最后 0 前言 &#…

【3D程序软件】SideFX与上海道宁一直为设计师提供程序化 3D动画和视觉效果工具,旨在创造高质量的电影效果

Houdini是一个 从头开始构建的程序系统 使艺术家能够自由工作 创建多次迭代 并与同事快速共享工作流程 Houdini FX为 视觉特效艺术家创作故事片 广告或视频游戏 凭借其基于程序节点的工作流程 Houdini FX可让 您更快地创建更多内容 从而缩短时间并 在所有创意任务中…

微信可以添加多少好友?

不知道有没有小伙伴好奇&#xff0c;微信到底可以添加多少好友&#xff1f;正好这个话题也上热搜了&#xff0c;我们就来了解一下。 有网友表示&#xff0c;自己的微信好友数量有10004个&#xff0c;已经不能再添加新的微信好友了。 一个微信号&#xff0c;可以添加的好友上限…

数字阅读用户规模持续增长 5.3亿人享受数字化阅读便利

近日,鲁迅长孙周令飞在接受采访时表示,自己“现在90%的时间刷视频,10%的时间看书”,引发网友热议。不少网友表示,鲁迅的孙子都花90%的时间刷视频,难怪现在没人看书了,其实这并不奇怪,也并不表明没人看书,而是读屏与读书并重的时代,纸质阅读与数字阅读共同构成了日常的阅读模式。…

51单片机项目(17)——基于51单片机的双机通信系统

1.功能设计 本次实现的功能如下&#xff1a; 发送端通过串口向接收端循环发送字符0~F&#xff0c;并且实时显示在数码管上 接收端通过串口接收数据&#xff0c;将接收到的数据实时显示在数码管上。 &#xff08;需要完整工程的移步至最后 包括发送端代码 接收端代码 仿真图…

脚本绑邦引流脚本拓客软件短视频获客直播间截流抖音快手小红书自动引流关注点赞私信评论截流涨粉

一、引流脚本是什么&#xff1f; 引流脚本是一种自动化的工具&#xff0c;可以帮助你在各​种短视频、​社交媒体平台上进行批量关注、点赞、私信、评论等操作&#xff0c;从而吸引更多的流量和粉丝。通过引流脚本&#xff0c;你可以自动化地执行各种操作&#xff0c;解放双手…

概要设计检查单、需求规格说明检查单

1、概要设计检查表 2、需求规格说明书检查表 概要&#xff08;结构&#xff09;设计检查表 工程名称 业主单位 承建单位 检查依据 1、设计方案、投标文件&#xff1b;2、合同&#xff1b;3、信息系统相关技术标准及安全规范&#xff1b; 检查类目 检查内容 检查…

智能优化算法应用:基于回溯搜索算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于回溯搜索算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于回溯搜索算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.回溯搜索算法4.实验参数设定5.算法结果6.参考…

Helm3部署kubeview资源可视化工具

一、KubeView 介绍 KubeView 是一个 Kubernetes 集群可视化工具和可视化资源管理器。它提供了跨命名空间的 Kubernetes 对象的完整概述&#xff0c;以及它们如何通过直观的 UI 和资源对象相互连接。它允许用户在集群内部运行命令&#xff0c;并查看集群内部的资源使用情况、容器…

后端整合Swagger+Knife4j接口文档

后端整合SwaggerKnife4j接口文档 接口文档介绍 什么是接口文档&#xff1a;写接口信息的文档&#xff0c;条接口包括&#xff1a; 请求参数响应参数 错误码 接口地址接口名称请求类型请求格式备注 为什么需要接口文档 who用&#xff1f;后端提供&#xff0c;前后端都需要使用…

不同酿酒风格生产的葡萄酒有什么不同?

霞多丽适合大多数风格的葡萄酒制作&#xff0c;从干燥的静止葡萄酒&#xff0c;到起泡酒&#xff0c;再到甜美的晚收&#xff0c;甚至是植物酿酒。最广泛影响霞多丽葡萄酒最终结果的两个酿酒决定是是否使用乳酸发酵和用于葡萄酒的橡木影响程度。 通过乳酸发酵&#xff08;或MLF…

开关电源低温启动测试条件是什么?如何测试开关电源?

开关电源作为常用的一种电源供应器被广泛应用在各大领域&#xff0c;同时也被用在各种不同的环境温度下工作。因此在开关电源测试中&#xff0c;温度测试是评估其性能、可靠性、稳定性的重要指标。低温启动测试就是检测低温存储环境对开关电源性能的影响&#xff0c;判断开关电…