Linux系统编程:采用管道的方式实现进程间通信
目录
一. 进程间通信概述
二. 管道的概念
三. 通过管道实现进程间通信
3.1 实现原理
3.2 匿名管道创建系统接口pipe
3.3 管道通信的模拟实现
3.4 管道通信的访问控制规则
3.5 管道通信的特点
四. 通过匿名管道实现进程池
4.1 进程池的概念
4.2 进程池的模拟实现
五. 命名管道
5.1 命名管道的功能
5.2 命名管道的创建和使用
六. 总结
一. 进程间通信概述
进程间通信的目的:实现进程之间的数据传输、共享资源、事件通知、多进程协同等操作。‘
进程间通信的技术手段:进程间要实现通信,就必须要让不同的进程看到同一块资源(内存空间),而由于进程之间具有独立性,因此这块资源不能隶属于任何一个进程,应当由操作系统内核提供。
进程间通信的方法:管道、SystemV、POSIX
管道:匿名管道、命名管道。
System V:共享内存、消息队列、信号量 。-- 用于本地计算机进行单机进程间通信
POSIX:消息队列、共享内存、信号量、互斥量、读写锁、条件变量。 -- 在网络中,用于多机之间的进程间通信。
二. 管道的概念
管道,是用于传输资源(数据)的一种媒介,可以实现进程之间的单向通信(也只能单向通信)。
由于进程之间具有相互独立性,因此,管道只能由操作系统内核提供,不能源自任意进程,管道的本质是一种内存级文件,即:内容不会被刷新到磁盘上的文件。
三. 通过管道实现进程间通信
3.1 实现原理
管道,尤其是匿名管道,一般用于具有亲缘关系的进程之间的通信,其底层实现原理如下:
- 父进程以读和写的方式创建匿名管道,由于管道的本质是文件,因此父进程会有两个文件描述符fd分别指向管道(一个读一个写)。
- fork创建子进程,子进程的文件描述符及指向与父进程相同。
- 关闭不需要的文件描述符,一般父进程用于写数据,子进程用于读数据,因此父进程关闭用来读的fd,子进程关闭用于写的fd。
经过上面的步骤,父进程的写fd和子进程的读fd就指向了相同的内存级文件(管道),通过父进程向管道中写的数据,就能被子进程读出来。
3.2 匿名管道创建系统接口pipe
原型:int pipe(int pipefd[2])
参数:pipefd为输出型参数,pipefd[0]为读端文件描述符,pipefd[1]为写端文件描述符。
返回值:如果成功创建管道返回0,失败返回-1并设置全局错误码。
头文件:#include <sys/fcntl.h>、#include <unistd.h>
一般pipe由父进程来调用,调用pipe后父进程要fork创建子进程,在父进程中一般要关闭pipefd[0],在子进程中一般要关闭pipefd[1]。
3.3 管道通信的模拟实现
代码3.1通过管道,实现父进程向子进程发生消息,父进程每隔1s写一次消息,子进程不间断读取并输出消息,由于写慢读快,子进程需要阻塞等待父进程写消息后才能读。
代码3.1:模拟实现管道通信
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/fcntl.h>#define SIZE 1024int main()
{// 1. 父进程创建管道int pipefd[2] = {0}; // 管道读写对应的文件描述符int n = pipe(pipefd); // 创建管道if(n == -1) // 管道创建失败{perror("pipe");exit(1);}// 2. fork子进程pid_t id = fork(); // 创建子进程if(id < 0) //子进程创建失败{perror("fork");exit(2);}else if(id == 0) //子进程代码{// 3. 子进程 -- 用于读取管道中的数据// 3.1 关闭不需要的文件描述符,子进程关写pipefd[1]close(pipefd[1]);// 3.2 读取数据并打印到标准输出流// 如果写慢读快,那么读要等写// 如果写快读慢,那么等待管道被写满后,就不能再继续写char read_buffer[1024] = {0}; // 读数据文件缓冲区while(true){ssize_t sz = read(pipefd[0], read_buffer, SIZE - 1); //数据读取// 如果写端退出,那么读端read读到0,读端最终会退出// 如果读端退出,OS会强制终止写端进程if(sz > 0) // 确实读到了数据{read_buffer[sz] = '\0';std::cout << "Father# " << read_buffer << std::endl;}else if(sz == 0) // 写端关闭{std::cout << "Father quit, write end, read end!" << std::endl;break;}else // sz < 0{perror("read");break;}}close(pipefd[0]);exit(0);}// 3. 父进程代码 -- 用于写数据// 3.1 关闭不需要的文件描述符,父进程关读pipdfd[0];close(pipefd[0]);// 3.2 向管道写数据const char* msg = "I am father process, I am sending message";char send_buffer[SIZE] = {0}; // 写数据缓冲区int count = 0;while(true){snprintf(send_buffer, SIZE, "%s,%d", msg, ++count);write(pipefd[1], send_buffer, strlen(send_buffer));sleep(1);}close(pipefd[1]);return 0;
}
3.4 管道通信的访问控制规则
- 读快写慢:读端需要阻塞等待写端写入数据后才能读。
- 写快读慢:管道被写满后就不能继续写入,需要等待数据被读出。
- 写端关闭:读端read读到0,退出。
- 读端关闭:OS强制终止写端进程。
3.5 管道通信的特点
- 管道(匿名管道),常用于具有亲缘关系的进程的进程间通信。
- 管道通信存在访问控制。
- 管道通信是一种面向字节流式的通信。-- 面向流式的通信:可以多次写入的内容一次读取,也可以一次写入的内容分多次读取。
- 管道的本质是文件(内存级文件),文件的生命周期随进程的结束而结束,因此进程结束时,管道关闭。
- 管道通信为单向通信,是半双工通信的一种特殊形式。
半双工通信:通信双方在某一时刻,只能单独进行写或读。(并不是说某一端只能进行写或读,而是不能写和读同时进行)
全双工通信:通信双方可以写和读同时进行。
四. 通过匿名管道实现进程池
4.1 进程池的概念
父进程创建N个子进程,按照一定的规则向子进程派发任务,父进程只负责向子进程派发任务,具体的任务由子进程来完成。
如果父进程将均衡的向每个子进程派发任务,这种算法称为单机版负载均衡。
4.2 进程池的模拟实现
采用rand随机生成来决定选用哪个子进程来执行任务。
task.hpp文件:父进程派发的任务
#ifndef __TASK_DEFINE_#define __TASK_DEFINE_#include <iostream>
#include <vector>
#include <functional>typedef std::function<void()> func; // 类型重定义std::vector<func> trace_back; // 回调函数
std::vector<std::string> desc; // 任务编号及对应说明void execuleUrl()
{std::cout << "execuleUrl" << std::endl;
}void save()
{std::cout << "save data" << std::endl;
}void visitSQL()
{std::cout << "visit SQL" << std::endl;
}void online()
{std::cout << "take online" << std::endl;
}void load()
{trace_back.emplace_back(execuleUrl);desc.emplace_back("execuleUrl");trace_back.emplace_back(save);desc.emplace_back("save data");trace_back.emplace_back(visitSQL);desc.emplace_back("visit SQL");trace_back.emplace_back(online);desc.emplace_back("take online");
}void show()
{int count = 0;for(const auto& msg : desc){std::cout << count << ": " << msg << std::endl;++count;}
}int handlerSize()
{return trace_back.size();
}#endif
PipePool.cc文件:进程池的实现源文件
#include <iostream>
#include <vector>
#include <ctime>
#include <cstdlib>
#include <cassert>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>#include "task.hpp"#define PROCESS_NUM 5void distributeTask(int who, int fd, uint32_t command)
{// 派发任务ssize_t n = write(fd, &command, sizeof(uint32_t));assert(n == sizeof(uint32_t));
}uint32_t waitCommand(int fd, bool &quit)
{uint32_t command = 0;ssize_t n = read(fd, &command, sizeof(uint32_t));if (n == 0){quit = true;return -1;}assert(n == sizeof(uint32_t));return command;
}int main()
{load(); // 载入任务// 创建子进程int pipefd[2] = {0};std::vector<std::pair<pid_t, int>> slot; // 记录子进程id以及写端文件描述符for (int i = 0; i < PROCESS_NUM; ++i){// 创建管道int ret = pipe(pipefd);if (ret == -1) // 管道创建失败{perror("pipe");exit(1);}// 创建子进程pid_t id = fork();if (id < 0) // 如果子进程创建失败{perror("fork");exit(2);}else if (id == 0) // 子进程代码{// 子进程代码// 关闭不需要的文件描述符close(pipefd[1]); // 子进程关写while (true){// 阻塞等待指令bool quit = false;uint32_t command = waitCommand(pipefd[0], quit);if (quit){std::cout << "write close, read also close!" << std::endl;break;}if (command >= 0 && command < handlerSize()){trace_back[command]();}else{std::cerr << "choice wrong" << std::endl;}}close(pipefd[0]);exit(0);}// 父进程代码close(pipefd[0]); // 关闭读slot.emplace_back(id, pipefd[1]); // 将子进程id和对应写端文件描述符插入顺序表}srand((unsigned int)time(NULL));// 父进程,开始派发任务int select = 0;while (true){std::cout << "##############################" << std::endl;std::cout << "## 1. show 2. choice ##" << std::endl;std::cout << "##############################" << std::endl;std::cout << "Please Select: > ";std::cin >> select;if (select == 1){show();}else if (select == 2){int choice = 0;std::cout << "Please chose task: > ";std::cin >> choice;int proc = rand() % PROCESS_NUM; // 选择子进程完成任务distributeTask(slot[proc].first, slot[proc].second, choice); // 派发任务usleep(100000);}else{std::cerr << "choice error" << std::endl;break;}}// 父进程关闭写端,子进程退出for (const auto &iter : slot){close(iter.second);}// 父进程阻塞等待子进程退出for (const auto &iter : slot){waitpid(iter.first, NULL, 0);}return 0;
}
五. 命名管道
5.1 命名管道的功能
一般意义上的管道(匿名管道)只能实现父子进程之间的通信,而命名管道可以实现不相关进程之间的进程间通信。
命名管道是一种特殊类型的文件,具有以下特性:
- 可以被打开,但不会将内存中的数据刷新到磁盘。
- 具有属于自己的名称。
- 在系统中有唯一的路径。
两个不相关的进程,可以通过访问同一管道文件,来实现进程间通信。
5.2 命名管道的创建和使用
- 通过指令创建管道文件:mkfifo [文件名]
- 删除管道文件:unlink、rm均可
同样,C语言库函数中也有mkfifo,其功能也是创建管道文件,mkfifo库函数的信息如下:
- 函数原型:int mkfifo(const char* pathname, mode_t mode);
- 函数参数:pathname为创建的管道文件名和路径,mode为起始权限。
- 返回值:创建成功返回0,失败返回-1并设置全局错误码。
也存在unlink库函数,用于删除管道文件,原型为:int unlink(const char* pathname)
假设有两个进程A和B,进程A以写的方式打开管道文件,进程B以读的方式打开管道文件,如果进程B先运行,需要等到进程A以写的方式打开管道文件后,进程B才可以以读打开的方式打开管道文件,否则,进程B要一直等待管道文件被以读的方式打开。
代码5.1通过命名管道,实现服务端进程(serve.exe)和客户端进程(client.exe)之间的进程间通信,serve.cc创建管道文件,并以只读方式打开管道文件,client.cc以只写的方式打开管道文件,执行代码时先运行serve.exe,等待client.exe运行以只写打开管道后,serve.exe才能执行只读打开管道文件的代码,在client中输入的信息,会显示到serve中。
代码5.1:命名管道实现不相关进程间的通信
// log.hpp头文件 -- 日志打印相关声明和实现
// 日志操作
#include <iostream>
#include <string>
#include <ctime>#ifndef __LOG_DEFINE_
#define __LOG_DEFINE_#define DEBUG 0
#define NOTICE 1
#define WARNING 2
#define ERROR 3 std::string msg[] = {"Debug","Notice","Waring","Error"
};std::ostream& log(const std::string& message, int level)
{std::cout << (unsigned int)time(NULL) << " | " << msg[level] << " | " << message << std::endl;
}#endif// common.hpp头文件 -- 声明宏,包含库文件
#ifndef __COMMON_DEF_
#define __COMMON_DEF_#include <iostream>
#include <cstdio>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/fcntl.h>
#include <sys/stat.h>#include "log.hpp"#define MODE 0666
#define SIZE 1024
#define PROCESS_NUM 3std::string ipcPath = "fifo.ipc";#endif// serve.cc -- 服务端源文件代码
#include "commom.hpp"// 服务端函数
int main()
{// 1. 以只写的方式打开管道文件int fd = open(ipcPath.c_str(), O_WRONLY);if(fd < 0){perror("client open");exit(1);}// 2. 开始执行进程间通信std::string send_buffer; // 信息发送缓冲区while(true){std::cout << "请输入要发送的信息: > ";std::getline(std::cin, send_buffer); // 逐行读取信息write(fd, send_buffer.c_str(), send_buffer.size()); //写数据}// 3. 关闭文件close(fd);return 0;
}// client.cc -- 客户端源文件代码
#include "commom.hpp"// 信息读取函数
void GetMessage(int fd)
{char read_buffer[SIZE] = {0}; //读数据缓冲区while(true){ssize_t n = read(fd, read_buffer, SIZE - 1); //从管道文件读数据if(n > 0){read_buffer[n] = '\0';std::cout << "[ " << getpid() << " ] client say# " << read_buffer << std::endl;}else if(n == 0){std::cout << "client quit, read end, serve quit too! " << std::endl;break;}else // n < 0 -- 读取出错{perror("read");break;}}
}int main()
{// 1. 创建命名管道文件if(mkfifo(ipcPath.c_str(), MODE) < 0){perror("mkfifo");exit(1);}log("管道创建成功", DEBUG);// 2. 服务端以只读方式打开文件int fd = open(ipcPath.c_str(), O_RDONLY);if(fd < 0) //检验打开是否成功{perror("serve fopen");exit(2);}log("文件打开成功", DEBUG);// 3. 创建子进程,进行进程间通信(读数据)for(int i = 0; i < PROCESS_NUM; ++i){pid_t id = fork();if(id == 0) //子进程代码{GetMessage(fd); //信息读取函数exit(0);}}// 4. 阻塞等待子进程退出for(int i = 0; i < PROCESS_NUM; ++i){waitpid(-1, NULL, 0);}log("子进程全部退出", NOTICE);// 5. 关闭文件,删除命名管道文件close(fd);unlink(ipcPath.c_str());log("文件关闭成功", NOTICE);// std::cout << "文件关闭成功" << std::endl;return 0;
}
六. 总结
- 实现进程间通信的方式有三种,分别为管道、System V、POSIX,其中System V用于本地单机进程间通信,POSIX用于网络进程间通信。
- 管道的本质是内存级文件,由OS内核提供,管道一般用于具有亲缘关系的进程间通信,管道通信为单向通信,是面向字节流式的通信,存在访问控制,管道的生命周期随进程的终止而终止。
- 通过命名管道,可以实现不相关进程间的通信。
相关文章:
Linux系统编程:采用管道的方式实现进程间通信
目录 一. 进程间通信概述 二. 管道的概念 三. 通过管道实现进程间通信 3.1 实现原理 3.2 匿名管道创建系统接口pipe 3.3 管道通信的模拟实现 3.4 管道通信的访问控制规则 3.5 管道通信的特点 四. 通过匿名管道实现进程池 4.1 进程池的概念 4.2 进程池的模拟实现 五…...
网络安全面试题
什么是SQL注入攻击 SQL 注入攻击是一种常见的 Web 应用程序安全漏洞,攻击者通过在 Web 应用程序的输入框、搜索框、登陆框等地方注入恶意的 SQL 语句,从而获取未授权的访问权限或者窃取敏感数据。攻击者利用注入的 SQL 语句执行恶意操作,例如…...
如何成为游戏主程
前言 前段时间有人在知乎上提问,如何成为主程,技术毋庸置疑是最重要的,但很多事情我认为主要是要有思路和品位。 1、技术 1、技术是程序员吃饭的手艺,打磨自己的手艺肯定无可厚非 2、保持对技术的热爱,不断学习&…...
SSM整合(XML方式)
文章目录 SSM整合之后xml方式1 系统环境1.1 软件环境1.2 项目环境1.3 配置web.xml1.4 配置jdbc.properties文件1.5 配置SpringMVC核心文件1.6 配置Spring的核心文件1.7 配置MyBatis的核心文件1.8 配置数据库1.9 配置文件位置 2 编写后端代码2.1 编写实体类2.2 编写Dao接口2.3 编…...
学习Vue:列表渲染(v-for)
在 Vue.js 中,实现动态列表的显示是非常常见的需求。为了达到这个目的,Vue 提供了 v-for 指令,它允许您迭代一个数组或对象,将其元素渲染为列表。然而,在使用 v-for 时,key 属性的设置也非常重要࿰…...
使用巴特沃兹滤波器的1D零相位频率滤波研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
ubuntu18.04安装cuda
卸载之前安装的驱动 sudo apt-get purge nvidia*安装驱动 方法1: 查看显卡适配的NVIDIA的驱动 ubuntu-drivers devices安装recommend推荐的版本 sudo apt-get install nvidia-driver-455方法2: 或者直接使用下面命令安装 sudo ubuntu-drivers au…...
【MFC】09.MFC视图-笔记
MFC视图窗口:CView类 显示数据/画面 我们之前的绘图消息,都是在框架类上画出来的 视图窗口就覆盖在框架窗口上 视图窗口本质上也是窗口,只是和框架窗口风格不同 CView类也继承于CWnd类 CView也能处理消息,因为它继承于CWnd类…...
【字节跳动青训营】后端笔记整理-2 | Go实践记录:猜谜游戏,在线词典,Socks5代理服务器
**本人是第六届字节跳动青训营(后端组)的成员。本文由博主本人整理自该营的日常学习实践,首发于稀土掘金:🔗Go实践记录:猜谜游戏,在线词典,Socks5代理服务器 | 青训营 我的go开发环境…...
GPT的第一个创作
嗨,大家好,我是赖兴泳!今天,我要和大家聊一聊前端开发,就像我用音符创造音乐一样,前端开发也是创造美丽的用户界面的过程。 前端开发是构建网站和应用程序用户界面的关键部分。就像音乐家需要精心编排音符…...
Spring Boot 获取前端参数
Spring Boot 获取前端参数 在开发 Web 应用程序时,前端参数是非常重要的。Spring Boot 提供了多种方法来获取前端参数,本文将介绍其中的一些常用方法。 1. 使用 RequestParam 注解 RequestParam 注解是 Spring MVC 提供的一种常用方式,用于…...
java应用运行在docker,并且其他组件也在docker
docker启动redis容器 # create redis docker run -d --name redis-container -p 6379:6379 redis:latest创建java 应用 dockerfile FROM openjdk:17##Pre-create related directories RUN mkdir -p /data/etax/ms-app WORKDIR /data/etax/ms-appEXPOSE 10133 COPY ./target…...
Java真实面试题,offer已到手
关于学习 在黑马程序员刚刚开始的时候学习尽头非常足,到后面逐渐失去了一些兴趣,以至于后面上课会出现走神等问题,但是毕业时后悔晚矣。等到开始学习项目一的时候,思路总会比别人慢一些,不看讲义写不出来代码。 建议…...
在序列化、反序列化下如何保持单例(Singleton)模式
1、序列化、反序列化 在 Java 中,当一个对象被序列化后再被反序列化,通常情况下会创建一个新的对象实例。这是因为序列化将对象的状态保存到字节流中,而反序列化则是将字节流重新转化为对象。在这个过程中,通常会使用类的构造函数…...
【数据结构】二叉树篇|超清晰图解和详解:二叉树的最近公共祖先
博主简介:努力学习的22级计算机科学与技术本科生一枚🌸博主主页: 是瑶瑶子啦每日一言🌼: 你不能要求一片海洋,没有风暴,那不是海洋,是泥塘——毕淑敏 目录 一、题目二、题解三、代码 一、题目 …...
android ndk clang交叉编译ffmpeg动态库踩坑
1.ffmpeg默认使用gcc编译,在android上无法使用,否则各种报错,所以要用ndk的clang编译 2.下载ffmpeg源码 修改configure文件,增加命令 cross_prefix_clang 修改以下命令 cc_default"${cross_prefix}${cc_default}" cxx…...
简单记录牛客top101算法题(初级题C语言实现)BM24 二叉树的中序遍历 BM28 二叉树的最大深度 BM29 二叉树中和为某一值的路径
1. BM24 二叉树的中序/后续遍历 要求:给定一个二叉树的根节点root,返回它的中序遍历结果。 输入:{1,2,#,#,3} 返回值:[2,3,1]1.1 自己的整体思路(与二叉树的前序遍…...
前后端分离------后端创建笔记(05)用户列表查询接口(上)
本文章转载于【SpringBootVue】全网最简单但实用的前后端分离项目实战笔记 - 前端_大菜007的博客-CSDN博客 仅用于学习和讨论,如有侵权请联系 源码:https://gitee.com/green_vegetables/x-admin-project.git 素材:https://pan.baidu.com/s/…...
性能测试|App性能测试需要关注的指标
一、Android客户端性能测试常见指标: 1、内存 2、CPU 3、流量 4、电量 5、启动速度 6、滑动速度、界面切换速度 7、与服务器交互的网络速度 二、预期标准指定原则 1、分析竞争对手的产品,所有指标要强于竞品 2、产品经理给出的预期性能指标数据…...
Termux SFTP 进行远程文件传输
文章目录 1. 安装openSSH2. 安装cpolar3. 远程SFTP连接配置4. 远程SFTP访问4. 配置固定远程连接地址 SFTP(SSH File Transfer Protocol)是一种基于SSH(Secure Shell)安全协议的文件传输协议。与FTP协议相比,SFTP使用了…...
XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...
【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...
3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...
Linux 中如何提取压缩文件 ?
Linux 是一种流行的开源操作系统,它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间,使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的,要在 …...
【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看
文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...
在树莓派上添加音频输入设备的几种方法
在树莓派上添加音频输入设备可以通过以下步骤完成,具体方法取决于设备类型(如USB麦克风、3.5mm接口麦克风或HDMI音频输入)。以下是详细指南: 1. 连接音频输入设备 USB麦克风/声卡:直接插入树莓派的USB接口。3.5mm麦克…...
第八部分:阶段项目 6:构建 React 前端应用
现在,是时候将你学到的 React 基础知识付诸实践,构建一个简单的前端应用来模拟与后端 API 的交互了。在这个阶段,你可以先使用模拟数据,或者如果你的后端 API(阶段项目 5)已经搭建好,可以直接连…...
