IPC之一:使用匿名管道进行父子进程间通信的例子
IPC 是 Linux 编程中一个重要的概念,IPC 有多种方式,本文主要介绍匿名管道(又称管道、半双工管道),尽管很多人在编程中使用过管道,但一些特殊的用法还是鲜有文章涉及,本文给出了多个具体的实例,每个实例均附有完整的源代码;本文所有实例在 Ubuntu 20.04 上编译测试通过,gcc版本号为:9.4.0;本文适合 Linux 编程的初学者阅读
1 概述
- IPC(Inter-Process Communication) - 进程间通信,提供了各种进程间通信的方法;
- 在 Linux C 编程中,IPC 通常有如下几种方式
- 半双工管道(Unix Pipe),简称管道,又称为匿名管道
- FIFOs - 又称为命名管道
- 消息队列(Message Queues)
- 信号量集(Semaphore Sets)
- 共享内存(Shared Memory Segments)
- 网络 socket(AF_INET Address Family)
- 全双工管道(AF_UNIX Address family)
- 本文主要介绍匿名管道(Unix Pipe)的应用场景及使用方法,并给出多个附有完整源代码的实例;
- 管道又被称为 匿名管道,是相对于命名管道而言的;
- 匿名管道的通信模式是半双工的,所谓半双工指的是在管道中数据流是单方向的,当 A 进程和 B 进程之间使用管道进行通信时,数据要么从 A 发向 B,要么从 B 发向 A,在一个管道上,不能既有 A 向 B 的数据流,又有 B 向 A 的数据流;
- 管道还有一个特性就是只能在有亲缘关系的进程间传递消息,换句话说,只有当两个进程有相同的祖先时,才有可能使用管道进行通信。
2. 管道的基本概念
-
简单地说,管道是一种将一个进程的输出连接到另一个进程的输入的方法;
-
管道是最古老的 IPC 工具,从最早的 UNIX 操作系统开始就存在了;它们提供了进程间单向通信的方法(因此称为半双工);
-
实际上,管道的这个特性广泛应用在 Linux 的命令行上,比如下面的命令:
ls | sort | lp -
这条命令实际上就建立了一个管道,将
ls的输出作为sort的输入,将sort的输出作为lp的输入;数据在匿名管道中运行,看上去,数据在管道中从左向右单方向流动; -
管道是在
Linux内核中实现的,很多程序员在shell脚本编程中都会频繁使用管道,但很少有人会去想管道在Linux内核中是如何实现的;当一个进程创建管道时,内核会创建两个文件描述符(
fd[0]和fd[1])供管道使用;一个描述符(fd[1])用于将数据写入管道,另一个描述符(fd[0])用于从管道中读取数据;此时,管道的实际用处不大,因为创建管道的进程只能使用管道与自身进行通信,毫无意义; -
下图展示了一个进程创建管道后,进程与内核的关系:

-
从上图中,可以看出以下几点:
- 文件描述符是如何连接在一起的;进程通过文件描述符(
fd[1])向管道写入数据,也能够从文件描述符(fd[0])从管道中读取该数据; - 通过管道传输数据时,数据是通过内核流动的;在
Linux下,管道在内核内部使用inode表示,innode驻留在内核中,并不属于一个物理文件系统。
- 文件描述符是如何连接在一起的;进程通过文件描述符(
-
这样建立的管道毫无用处,一个进程要自言自语,没有必要建立一个管道;但是,如果创建管道的进程再
fork出一个子进程,由于子进程会从父进程继承管道的描述符,这样父子进程之间有可以通过这个管道进行通信了; -
下图描述了父进程、子进程和内核的关系

-
从上图中,我们可以看到,父进程和子进程都可以访问管道的两个文件描述符,但是很显然,如果父进程和子进程同时向
fd[1]写入数据,一定会造成混乱,而且如果父、子进程均向fd[1]写入数据,当从fd[0]读出数据时,并无法区分读到的数据是那个进程写入的;所以必须要做出抉择,这个建立的管道的数据是向那个方向流动,从父进程流向子进程?还是从子进程流向父进程?两个进程必须达成一致,否则会出现混乱; -
为了讨论方便,我们假定子进程要做一些事务,然后把结果通过管道发送给父进程,如下面图示:

-
至此,管道已经建立完毕,下面就是如何使用管道;前面提到过,管道的文件描述符使用
inode,所以可以使用低级文件 I/O 的系统调用来直接访问管道; -
向管道中写入数据,使用
write()系统调用;从管道中读出数据,使用read()系统调用; -
特别提醒:系统调用
lseek()不能在管道中使用。
3 如何用C语言创建管道
- 使用
pipe()系统调用可以创建一个管道,这个调用需要一个由两个整数组成的数组作为参数,调用成功后,该数组将包含管道的两个文件描述符; - 系统调用:pipe()
原型:#include <unistd.h>int pipe(int fd[2]); 返回:调用成功返回 0调用失败返回 -1error = EMFILE (no free descriptors)EMFILE (system file table is full)EFAULT (fd array is not valid)备注: fd[0] 用于从管道中读取数据, fd[1] 用于向管道中写入数据 - 调用成功后,不仅两个管道描述符被建立,而且处于打开状态,可以直接进行读、写操作;
- 再次重申,所有通过管道传输的数据都要通过内核;下面是使用
pipe()建立管道的代码:#include <stdio.h> #include <unistd.h> #include <sys/types.h>main() {int fd[2];pipe(fd);.. } - 前面说过,这样建立的管道毫无用处,进程自言自语并不需要使用管道;要使管道有意义,在建立管道后要
fork()一个子进程;#include <stdio.h> #include <unistd.h> #include <sys/types.h>main() {int fd[2];pid_t childpid;pipe(fd);if ((childpid = fork()) == -1) {perror("fork");exit(1);}.. } - 如果父进程要从子进程接收数据,父进程应关闭向管道写入的描述符
fd[1],而子进程应该关闭从管道读出的描述符fd[0];如果父进程要向子进程发送数据,则父进程应关闭从管道读出的描述符fd[0],而子进程应该关闭向管道写入的描述符fd[1]; - 由于管道描述符在父进程和子进程之间是共享的,所以我们要确保关闭掉我们不需要的管道末端,从技术上讲,如果不需要的管道末端没有关闭,则永远不会返回
EOF; - 下面代码假定父进程要从子进程接收数据:
#include <stdio.h> #include <unistd.h> #include <sys/types.h>main() {int fd[2];pid_t childpid;pipe(fd);if ((childpid = fork()) == -1) {perror("fork");exit(1);}if (childpid == 0) {/* Child process closes up input side of pipe */close(fd[0]);} else {/* Parent process closes up output side of pipe */close(fd[1]);}.. } - 如前所述,建立了管道以后,就可以像对待普通文件描述符一样对待管道描述符;
- 源程序:pipe.c(点击文件名下载源程序)演示了子进程向父进程发送信息:Hello, world!
4 在管道上使用 dup()
-
大多数已有的
Linux命令或者自己编写的程序,其默认的输入设备往往是STDIN,而输出设备是STDOUT,当我们希望在程序中用某个Linux命令处理数据时,往往不太好获得命令的输出,或者不好把数据传送给这个程序,这时候管道可以发挥作用; -
比如
Linux命令sort,在没有其它参数时,其默认的输入设备就是STDIN,当我们在程序中希望使用sort处理一组数据时,我们可以设法把STDIN连接到管道的输出端,这样,我们向管道中的一端写入数据时,管道的另一端已经启动的sort就可以从STDIN读到数据并进行处理; -
系统调用
dup()和dup2()可以帮助我们实现这个想法;先看一下这两个系统调用的说明; -
系统调用: dup();
原型:#include <unistd.h>int dup(int oldfd); 说明:dup() 系统调用创建文件描述符 oldfd 的副本,使用编号最小的未使用的文件描述符作为新描述符。 返回:调用成功则返回新描述符调用失败则返回 -1errno = EBADF (oldfd is not a valid descriptor) EBADF (newfd is out of range)EMFILE (too many descriptors for the process) 备注:oldfd 不会被关闭,新描述符和 oldfd 都可以使用。 -
系统调用:dup2();
原型:#include <unistd.h>int dup2(int oldfd, int newfd); 说明:dup2() 系统调用与 dup() 相似,创建文件描述符 oldfd 的副本,但它不使用编号最小的未使用文件描述符,而是使用 newfd 中指定的文件描述符;如果文件描述符 newfd 先前已打开,该调用会首先将其关闭然后再使用。 返回:调用成功则返回新描述符调用失败则返回 -1errno = EBADF (oldfd is not a valid descriptor)EBADF (newfd is out of range)EMFILE (too many descriptors for the process)备注:使用 dup2(),oldfd 会被关闭 -
在子进程中,使用
dup2()将管道的输出(fd[0])复制到STDIN上,并关闭STDIN,然后用exec()启动sort时,当sort从STDIN读入数据时,实际上是从管道中读出数据,当我们从父进程向管道中写入数据时,这个数据将被sort读取并处理; -
为了搞清楚这种用法,请自行学习 Linux 命令 sort,可以用在线手册
man sort了解该命令的详细信息; -
下图或许可以更直观地描述这种使用方法:

-
源程序:pipe-dup-stdin.c(点击文件名下载源程序)演示了在管道中使用
dup2()将 fd[0] 复制到STDIN的方法:子进程中把 fd[0] 复制到
STDIN,然后启动sort,父进程向管道中写入若干个单词,每个单词以\n结尾,sort从STDIN读入数据,实际上是从管道中读入数据,所以sort程序会对这些单词进行排序,并把结果写入文件sort.log中,程序运行完毕后,使用cat sort.log可以看到经过排序的单词; -
同样道理,也可以在子进程中把管道的输入端(
fd[1])复制到STDOUT上,这样,当子进程中启动的程序向STDOUT输出时,实际上是在向管道上写入数据; -
源程序:pipe-dup-stdout.c(点击文件名下载源程序)演示了在管道中使用
dup2()将 fd[1] 复制到STDOUT的方法:子进程中把
fd[1]复制到STDOUT,然后启动uname,uname -r会输出一个字符串到STDOUT,实际上是写入到了管道中,父进程从管道中收到了这个字符串并显示出来;子进程中把管道的输入端复制到
STDOUT后,在子进程中启动任何程序,在主进程中通过读取管道都可以轻易地获得这个程序的输出,比如我们要知道当前系统的是不是 64 位系统,那我们在子进程中启动命令uname -m,如果主进程在管道上读出的内容是x86_64,则系统无疑是64位的。
5 使用管道的简单方法
-
上面介绍的在程序中使用管道获取一个外部程序的输出(或者向一个外部程序输入数据)的方法看上去不仅繁琐,而且绕的弯也比较多,其实使用管道还有更为简单的方法;
-
使用标准库函数
popen()可以很容易地使用管道;库函数:popen();原型:#include <stdio.h>FILE *popen (char *command, char *type); 说明:popen() 函数通过创建一个管道,调用 fork 产生一个子进程,执行 shell 运行命令来开启一个进程。 返回:调用成功则返回一个标准 I/O 流调用 fork() 或 pipe() 失败则返回 NULL -
该标准库函数通过内部调用
pipe()创建匿名管道,然后fork()一个子进程,执行shell,并在shell中执行 “command” 参数;数据流的方向由第二个参数type确定,type可以是 “r” 或 “w”,表示读或写,不可能两者兼而有之!在Linux下,管道将以type参数的第一个字符指定的模式打开,如果您将type设置为 “rw”,该函数会以 “r” (读)模式打开管道。 -
与直接使用
pipe()系统调用相比,这个库函数为我们做了很多繁琐的工作,但却让我们失去了对整个过程的精细控制; -
该函数直接使用了 Bourne shell(bash), 所以在
command参数中可以使用shell元字符以及元字符扩展(包括通配符); -
使用
popen()创建的管道必须使用pclose()关闭;popen()/pclose()与标准文件流I/O函数fopen()/fclose()非常相似。库函数:pclose();原型:#include <stdio.h>int pclose(FILE *stream); 说明:pclose()函数等待相关进程终止,并返回由 wait4() 返回的命令退出状态。 返回:返回 wait4() 调用的退出状态码如果 stream 不合法,或者 wait() 执行失败,则返回 -1备注:等待管道进程退出,然后关闭文件 I/O 流 -
pclose()函数对由popen()派生的进程执行wait4(),当wait4()返回时,它会销毁管道和文件流; -
源程序:pipe-popen.c(点击文件名下载源程序)完成与前面的例子 pipe-dup-stdin.c 一样的功能,但看上去要简单的多;
-
由于
popen()使用shell来执行命令,因此shell扩展字符和元字符都可以使用,此外,使用 popen() 打开管道时,可以使用一些高级技术来执行命令,例如重定向,甚至输出管道;以下调用示例分别使用了扩展字符、重定向和输出管道:popen("ls ~scottb", "r"); popen("sort > /tmp/foo", "w"); popen("sort | uniq | more", "w"); -
源程序:pipe-popen2.c(点击文件名下载源程序)打开了两个管道,一个用于
ls命令,另一个用于sort命令; -
下面这个例子试图编写一个通用的管道程序,源程序文件:pipe-popen3.c(点击文件名下载源程序)
- 使用方法为:
./pipe-popen3 [command] [filename] - 该程序会首先打开文件
filename - 然后使用
popen()以写方式打开command管道 - 从
filename中读出内容并写入管道 - 可以尝试用以下方式测试这个例子
./pipe-popen3 sort pipe-popen3.c ./pipe-popen3 cat pipe-popen3.c ./pipe-popen3 more pipe-popen3.c ./pipe-popen3 cat pipe-popen3.c | grep main
- 使用方法为:
6 管道的原子操作
- 所谓“原子操作”,是指一个或一系列不可中断的操作,就是说一个原子操作一旦开始执行就不能被中断,直至执行完毕;
- POSIX 标准规定了管道上原子操作的最大缓冲区大小是 512 字节,定义在头文件:
bits/posix1_lim.h中:#define _POSIX_PIPE_BUF 512 - 根据这一定义,如果一次写入/读出管道的操作大于 512 字节,操作将是非“原子操作”,也就是写入/读出的数据可能会被分割;
- 在 Linux 下,定义的管道上的原子操作的最大缓冲区大小为:4096 字节,定义在头文件:
linux/limits.h中:#define PIPE_BUF 4096 /* # bytes in atomic write to a pipe */ - 显然,在我们目前的环境下,在管道上进行不大于 4096 字节的读/写操作是原子操作;
- 在多进程环境下,原子操作对管道的读/写操作非常重要,当一个进程写入管道的数据大于阈值时,其写入过程中间会中断,操作系统会产生进程调度,如果这时其它进程也向这个管道写入数据,那么写入管道的数据会产生混乱。
7 匿名管道的其它说明
- 尽管管道是半双工的,但是,打开两个管道,并在子进程中合理地重新分配描述符,可以构建出一个类似全双工的管道;
pipe()调用必须在fork()调用之前进行,否则描述符将不会被子进程继承;- 使用匿名管道进行通信的进程都必须有一个共同的祖先,而且这个祖先必须是管道的创建者,由于管道位于内核中,不在管道创建者祖先中的进程都无法对其进行寻址,这与命名管道(FIFO)是不同;
- 由于使用管道的一些限制,在进程间进行通讯时,管道实际上并不是一个常用的方法,但是,如果需要使用已有的 Linux 命令处理数据,或者从 Linux 命令获得结果数据,管道不失为一个好的选择;
- 匿名管道的生命周期与创建它的进程的生命周期一致,当进程结束时,其创建的匿名管道也将被销毁。
相关文章:
IPC之一:使用匿名管道进行父子进程间通信的例子
IPC 是 Linux 编程中一个重要的概念,IPC 有多种方式,本文主要介绍匿名管道(又称管道、半双工管道),尽管很多人在编程中使用过管道,但一些特殊的用法还是鲜有文章涉及,本文给出了多个具体的实例,每个实例均附…...
前端将页面转化为图片---进行下载导出、打印等功能
1.需要实现一个将div页面的东西导出,使用到了html2canvas 官网: 配置型 | HTML2CANVAS 中文文档 (allenchinese.github.io) 2.下载html2canvas npm install --save html2canvas 3.导入使用到的页面 import html2canvas from html2canvas 4.创建图片基础应用 …...
docker安装code-service在线开发vscode工具及node版本过低问题
docker安装code-service 拉去镜像 docker pull codercom/code-server创建项目存放映射路径 mkdir /data/code-service/project运行 这里不唯一,但注意密码 docker run -itd --name code-service -u root -p 1024:8080 -v /data/code-service/project:/home/cod…...
C++ 多态深入解析
文章目录 前言一、什么是多态二、如何实现多态三、代码讲解四、静态联编,动态联编总结 前言 在C编程中,多态性(Polymorphism)是一种重要的概念,它允许基于对象的实际类型来调用不同的函数。多态性提供了灵活性和可扩展…...
C#使用EmguCV播放视频
目录 一、前言 1、简介 2、测试工程代码下载链接 3、EmguCV 库文件下载链接 二、工程环境配置 1、EmguCV控件添加引用 (1)窗口控件添加 (2)相关Dll文件添加添加引用 (3)工程运行基础文件夹添加 &a…...
LeetCode150道面试经典题-买卖股票的最佳时机(简单)
1、题目 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从这笔交易中获取的…...
【积水成渊】CSS磨砂玻璃效果和渐变主题色文字
大家好,我是csdn的博主:lqj_本人 lqj_本人_python人工智能视觉(opencv)从入门到实战,前端,微信小程序-CSDN博客 最新的uniapp毕业设计专栏也放在下方了: https://blog.csdn.net/lbcyllqj/category_12346639.html?spm1…...
JVM、JRE、JDK三者之间的关系
JVM、JRE和JDK是与Java开发和运行相关的三个重要概念。 再了解三者之前让我们先来了解下java源文件的执行顺序: 使用编辑器或IDE(集成开发环境)编写Java源文件.即demo.java程序必须编译为字节码文件,javac(Java编译器)编译源文件为demo.class文件.类文…...
input 标签的 type 属性有哪些值?分别表示什么意思?
聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ type值以及作用⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端…...
(十五)大数据实战——hive的安装部署
前言 Hive是由Facebook开源,基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张表,并提供类SQL查询功能。本节内容我们主要介绍一下hive的安装与部署的相关内容。 正文 上传hive安装包到hadoop101服务器/opt/software目录 解…...
MySQL安装和卸载
1.MySQL概述 MySQL概述 MySQL是一个[关系型数据库管理系统],由瑞典MySQL AB 公司开发,2008年被sun公司收购, 2009sun又被oracle收购,所以属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用…...
ELK、ELFK日志分析系统
菜单一、ELK简介1.1 ELK组件说明1.1.1 ElasticSearch1.1.2 Kiabana1.1.3 Logstash 1.2 可以添加的其它组件1.2.1 Filebeat1.2.2 缓存/消息队列(redis、kafka、RabbitMQ等)1.2.3 Fluentd 1.3 为什么要用ELK1.4 完整日志系统的基本特征1.5 ELK 的工作原理 …...
JVM基础篇-StringTable
StringTable 特性 常量池中的字符串仅是符号,第一次用到时才变为对象 利用串池的机制,来避免重复创建字符串对象 字符串变量拼接的原理是 StringBuilder (1.8) 字符串常量拼接的原理是编译期优化 可以使用 intern 方法&#…...
探秘手机隐藏的望远镜功能:开启后,观察任何你想看的地方
当今的智能手机不仅仅是通信工具,它们蕴藏着各种隐藏的功能,其中之一就是让你拥有望远镜般的观察能力。是的,你没有听错!今天我们将探秘手机中隐藏的望远镜功能,这项神奇的功能可以让你打开后,轻松观察任何…...
正运动亮相2023半导体设备材料与核心部件展示会,助力半导体产业高速高精应用
■展会名称: 第11届(2023)半导体设备材料与核心部件展示会 ■展会日期 2023年8月9日-11日 ■展馆地点 无锡太湖国际博览中心A6馆 ■展位号 A6-A361 正运动技术,作为国内领先的运动控制企业,将于2023年8月9日参加…...
如何在MongoDB中添加新用户
如何在MongoDB中添加新用户? MongoDB是一款流行的NoSQL数据库,它的可扩展性强,可进行分布式部署,且具有高可用性。其许多优势使得越来越多的企业和组织选择MongoDB作为其数据库系统。本文将介绍如何在MongoDB中添加新用户。 第一步…...
幻读怎么复现
大家好,我是想想。 很久没有给大家分享技术了,主要在计划一些事情,几乎没什么时间爽文了。 今天从实操上实现了MySQL事务隔离复现问题,就记录分享给大家吧。 正文 我们知道,著名的四大事务特性ACID特性 Atomicity…...
无脑入门pytorch系列(二)—— torch.mean
本系列教程适用于没有任何pytorch的同学(简单的python语法还是要的),从代码的表层出发挖掘代码的深层含义,理解具体的意思和内涵。pytorch的很多函数看着非常简单,但是其中包含了很多内容,不了解其中的意思…...
ansible-kubeadm在线安装高可用K8S集群v1.19-v1.20版本
ansible可以安装的KS8版本如下: 请按照此博客中的内容操作后,才可以通过下面的命令查询到版本。 [rootk8s-master01 ~]# yum list kubectl --showduplicates | sort -r kubectl.x86_64 1.20.0-0 kubern…...
Cesium entity 渐隐渐显、闪烁
点entity function f2(){var x1;var flogtrue;//闪烁//var x0;var flogfalse;//渐显viewer.entities.add({name:"圆点point闪烁",position:Cesium.Cartesian3.fromDegrees(116.200.03,39.530.03,0),point : {show : true, // defaultcolor :new Cesium.CallbackProp…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...
【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...
【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...
