当前位置: 首页 > news >正文

进程间通信(上)

进程间通信(上)

  • 背景
  • 进程间通信目的
  • 进程间通信发展
  • 进程间通信分类
  • 管道
    • 什么是管道
    • 匿名管道
      • 实例代码
        • 简单的匿名管道实现
        • 一个父进程控制单个子进程完成指定任务
        • 父进程控制一批子进程完成任务(进程池)
      • 用fork来共享管道
      • 站在文件描述符角度-深度理解管道
      • 站在内核角度-管道本质
      • 理解管道操作 -- |
    • 管道读写规则
    • 管道的特点
    • 命名管道
      • 原理
      • 创建一个命名管道
      • 命名管道具体示例
        • 命令行创建
        • 代码示例

背景

1、进程是具有独立性的,所以进程间想要交互数据,成本会非常高

2、为什么要进行进程间通信?有的时候需要多进程协同处理一件事情。

进程间通信目的

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

进程间通信发展

  • 管道
  • System V进程间通信
  • POSIX进程间通信

进程间通信分类

管道

  • 匿名管道
  • pipe 命名管道

System V IPC(用的不多,更多的是进行本地通信)

  • System V 消息队列
  • System V 共享内存
  • System V 信号量

POSIX IPC(用的较多,也可以用来进行网络通信)

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

注意:System V IPC和POSIX IPC是两套标准(IPC是通信的简称)。

管道

什么是管道

  • 管道是Unix中最古老的进程间通信的形式。

  • 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”

image-20221031150427050

匿名管道

image-20221031170631490

里面封装了两次open,第一次是以读方式打开,返回值写在fd[0]中,也就是打开文件的fd,第二次是以写方式打开,返回值写在fd[1]中,也就是打开文件的fd。同时通过上面的联合体标定它是一个管道文件。

#include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码-1

image-20221031155504450

实例代码

简单的匿名管道实现

#include<iostream>
#include<cstdio>
#include<fcntl.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string>
#include<cstring>
#include<sys/wait.h>
using namespace std;
int main()
{//1.创建管道int pipefd[2] = {0};if(pipe(pipefd) != 0){cerr << "pipe error" << endl;return 1;}//2.创建子进程pid_t id = fork();if(id < 0){cerr << "fork error" << endl;return 2;}else if(id == 0)//子进程{//子进程来读取,关闭写端close(pipefd[1]);
#define MAX_NUM 1024char buffer[MAX_NUM];while(true){memset(buffer, 0, sizeof(buffer));ssize_t ret = read(pipefd[0], buffer, sizeof(buffer) - 1);if(ret > 0){//读取成功,可以进行写入buffer[ret] = '\0';cout << "子进程受到消息了,消息内容:" << buffer << endl;}else if(ret == 0) {sleep(1);//此处是为了稍微等一下父进程cout << "父进程写完了,我也退出了!" << endl;break;}else {//Do nothing}}close(pipefd[0]);exit(0);}else//父进程{//父进程来写入,关闭读端close(pipefd[0]);const string msg = "你好子进程,我是父进程!这次发送的信息编号是: ";int cnt = 0;while(cnt < 5){char sendBuffer[1024];sprintf(sendBuffer, "%s:%d", msg.c_str(), cnt);write(pipefd[1], sendBuffer,strlen(sendBuffer));sleep(1);//为了看现象明显设计的cnt++;}close(pipefd[1]);cout << "父进程写完了" << endl;}pid_t res = waitpid(id, nullptr, 0);if(res > 0){cout << "等待子进程成功!" << endl;}return 0;
}

问:父进程关闭写端了,子进程是如何知道父进程关闭写端的?

答:通过引用计数知道的,file结构体中,有类似引用的变量记录了有几个指针指向该文件。当引用计数为1了,说明此时就只有一个进程指向该文件了。此时子进程读完就不再有进程指向该文件了。

问:父进程每隔一秒写一次,为什么子进程也是一秒读一次呢?

答:当父进程在写入数据的时候,子进程在等(阻塞等待:将当前进程放在等待队列中(管道资源的等待队列中))!所以,父进程写入之后,子进程才能read(会返回)到数据,子进程打印读取数据要以父进程的节奏为主。所以父进程和子进程在读写的时候,是有一定的顺序性的(pipe内部自带访问控制(同步和互斥机制))。(父子进程在各自printf的时候(向显示器写入文件),并没有顺序,谁快谁先写,缺乏访问控制)。

管道内部,没有数据,reader就必须阻塞等待(等管道有数据);管道内部如果被写满了,writer就必须阻塞等待(等数据被读走)。

一个父进程控制单个子进程完成指定任务

代码:

#include<iostream>
#include<cstdio>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<ctime>
#include<string>
#include<vector>
#include<unordered_map>
#include<cassert>
#include<cstdlib>using namespace std;typedef void(*functor)();
vector<functor> functors;//方法集合
//for debug
unordered_map<uint32_t, string> info;
void f1()
{cout << "这是一个处理日志的任务, 执行的进程id:" << getpid() << "执行时间是" << time(nullptr) << endl;;
}
void f2()
{cout << "这是一个备份数据的任务, 执行的进程id:" << getpid() << "执行时间是" << time(nullptr) << endl;;
}
void f3()
{cout << "这是一个处理网络请求的任务, 执行的进程id:" << getpid() << "执行时间是" << time(nullptr) << endl;;
}
void loadFunctor()
{info.insert({functors.size(), "处理日志的任务"});functors.push_back(f1);info.insert({functors.size(), "处理备份数据的任务"});functors.push_back(f2);info.insert({functors.size(), "处理网络请求的任务"});functors.push_back(f3);
}int main()
{//0.加载任务列表loadFunctor();//1.创建管道int pipefd[2] = {0};if(pipe(pipefd) != 0){cerr << "pipe error" << endl;return 1;}//2.创建子进程pid_t id = fork();if(id < 0){cerr << "fork error" << endl;return 2;}else if(id == 0){//3.关闭不需要的文件close(pipefd[1]);//child:read//4.业务处理while(true){uint32_t operatorType = 0;//如果有数据就读取,如果没有数据就阻塞等待,等待任务的到来ssize_t s = read(pipefd[0], &operatorType, sizeof(uint32_t));if(s == 0){cout << "读取数据结束!退出!" << endl;break;}assert(s == sizeof(uint32_t));//assert是断言,是编译有效debug模式//release模式,断言就没有了//如果断言没有了,那么s变量就是只被定义,没有被使用,在release模式中,就会有warning存在(void)s;if(operatorType < functors.size()){functors[operatorType]();}else {cerr << "bug?operatorType = " << operatorType << endl;}}close(pipefd[0]);exit(0);}else if(id > 0){srand((long long)time(nullptr));//3.关闭不需要的文件close(pipefd[0]);//parant:write//4.业务生成int num = functors.size();int cnt = 10;while(cnt--){//5.形成任务码uint32_t commandCode = rand() % num;cout << "父进程指派任务完成,任务是:" << info[commandCode] << ", 任务的编号是:"<< cnt << endl;//向指定的进程下达执行任务的操作write(pipefd[1], &commandCode, sizeof(uint32_t));sleep(1);}close(pipefd[1]);pid_t res = waitpid(id, nullptr, 0);if(res){cout << "wait success" << endl;}}return 0;
}

父进程控制一批子进程完成任务(进程池)

代码:

#include<iostream>
#include<cstdio>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<ctime>
#include<string>
#include<vector>
#include<unordered_map>
#include<cassert>
#include<cstdlib>using namespace std;typedef void(*functor)();
vector<functor> functors;//方法集合
//for debug
unordered_map<uint32_t, string> info;
void f1()
{cout << "这是一个处理日志的任务, 执行的进程id:" << getpid() << "执行时间是" << time(nullptr) << endl << endl;;
}
void f2()
{cout << "这是一个备份数据的任务, 执行的进程id:" << getpid() << "执行时间是" << time(nullptr) << endl <<  endl;;
}
void f3()
{cout << "这是一个处理网络请求的任务, 执行的进程id:" << getpid() << "执行时间是" << time(nullptr) << endl << endl;;
}
void loadFunctor()
{info.insert({functors.size(), "处理日志的任务"});functors.push_back(f1);info.insert({functors.size(), "处理备份数据的任务"});functors.push_back(f2);info.insert({functors.size(), "处理网络请求的任务"});functors.push_back(f3);
}
//int32_t:进程pid  int32_t:该进程对应的管道写端fd
typedef pair<int32_t, int32_t> elem;
int processNum = 5;//创建子进程的个数
void work(int blockFd)
{ cout << "进程:" << getpid() << "开始工作" << endl;//子进程核心工作的代码while(true){//a.阻塞等待 b.获取任务信息uint32_t operatorCode = 0;ssize_t s = read(blockFd, &operatorCode, sizeof(uint32_t));if(s == 0){break;}assert(s == sizeof(uint32_t));(void)s;//c.处理任务if(operatorCode < functors.size()){functors[operatorCode]();}}cout << "进程:" << getpid() << "结束工作" << endl;
}
//[子进程的pid, 子进程的管道fd]
void BalanceSendTask(vector<elem>& processFds)
{srand((long long)time(nullptr));int cnt = 10;//cnt是要分配任务的数目while(cnt != 0){sleep(1);//选择一个进程int pick = rand() % processFds.size();//选择一个任务int task = rand() % functors.size();//把任务给一个指定的进程write(processFds[pick].second, &task, sizeof(int));//打印对应的提示信息cout << "父进程指派任务"<< info[task] << "给进程:"  << processFds[pick].first << "编号:" << pick << endl;cnt--;}
}
int main()
{loadFunctor();vector<elem> assignMap;//创建processNum个进程for(int i = 0; i < processNum; i++){//定义保存管道fd的对象int pipefd[2] = {0};//创建管道pipe(pipefd);//创建子进程pid_t id = fork();if(id == 0){//子进程读取close(pipefd[1]);//子进程执行work(pipefd[0]);close(pipefd[0]);exit(0);}//父进程做的事情   close(pipefd[0]);elem e(id, pipefd[1]);assignMap.push_back(e);}cout << "creat all process success!" << endl;//父进程派发任务BalanceSendTask(assignMap);//回收资源for(int i = 0; i < processNum; i++){close(assignMap[i].second);}for(int i = 0; i < processNum; i++){if(waitpid(assignMap[i].first, nullptr, 0) > 0){cout << "wait for" << assignMap[i].first << "success!"  << "number:" << i << endl;}}return 0;
}

用fork来共享管道

image-20221031161452915

站在文件描述符角度-深度理解管道

image-20221031164213553

问:为什么父进程要分别打开读和写?

答:为了让子进程继承,让子进程不必再打开了。

问:为什么父子要关闭对应的读和写?

答:因为管道必须是单向通信的,一端是读端,另已端必须是写端。

问:谁决定父子关闭读端还是写端?

答:由需求决定。

站在内核角度-管道本质

image-20221101192704676

理解管道操作 – |

注意:|操作的本质就是匿名管道

image-20221101193113741

sleep 1000 | sleep 100

这两个进程(sleep 1000和sleep 100)的关系是什么呢?两个进程的ppid是一样的,即有同样的父进程。

以下面的命令进行举例:

cat pipe.cc | wc -l 

父进程fork两个子进程即cat pipe.ccwc -l,父进程在创建进程的同时,创建了一条匿名管道,两个进程通过该匿名管道来进行通信,cat pipe.cc和wc -l两个进程分别关闭读写端,父进程关闭这个进程的读写端,cat进程进行输出重定向,wc进程进行输入重定向。

管道读写规则

  • 当没有数据可读时:
    • O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止
    • O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
  • 当管道满的时候
    • O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
    • O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
  • 如果所有管道写端对应的文件描述符被关闭,则read返回0
  • 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程 退出
  • 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
  • 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

管道的特点

  1. 管道只能用来进行具有血缘关系的进程之间进行进程间通信,常用于父子间通信或者兄弟间通信。(只属于匿名管道)
  2. 管道只能单向通信(内核实现决定的),半双工的一种特殊情况
  3. 管道自带同步机制(内核会对管道操作进行同步与互斥)(pipe满,writer等,pipe空,reader等)
  4. 管道是面向字节流的,先写的字符,一定是先被读取的,没有格式边界,需要用户来定义区分内容的边界
  5. 管道的生命周期跟随进程 – 管道是文件 – 进程退出了,曾经打开是文件引用计数到达0就会自动退出

命名管道

  • 管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
  • 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
  • 命名管道是一种特殊类型的文件

原理

image-20221101210357942

和匿名管道的区别:

匿名管道:子进程继承父进程。

命名管道:通过打开同一个fifo文件,进行信息的交互(路径具有唯一性)

注意:我们使用的命名管道,更多的时候是作为一种标定的作用,内存中的管道文件中的数据不会刷新到磁盘中,即使在进行通信的时候,命名管道的大小也始终是0个字节

创建一个命名管道

  • 命名管道可以从命令行上创建,命令行方法是使用下面这个命令:

    $ mkfifo filename
    
  • 命名管道也可以从程序里创建,相关函数有:

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

命名管道具体示例

命令行创建

简单使用:

image-20221101202732807

image-20221101202801137

代码示例

创建管道文件:

image-20221101202319701

删除管道文件:

image-20221102101704696

代码示例:

clientFifo.cpp文件:

#include"comm.h"
using namespace std;
int main()
{int pipeFd = open(IPC_PATH, O_WRONLY);if(pipeFd < 0){cerr << "open error" << endl;return 1;}
#define NUM 1024char line[NUM];while(true){printf("请输入你的消息#");fflush(stdout);memset(line, 0, sizeof(line));if(fgets(line, sizeof(line), stdin) != nullptr){line[strlen(line) - 1] = '\0';write(pipeFd, line, strlen(line));//12345\n\0}else {break;}}cout << "退出客户端" << endl;close(pipeFd);return 0;
}

serverFifo.cpp文件:

//写入
#include"comm.h"
using namespace std;
int main()
{if(mkfifo(IPC_PATH, 0666) != 0)//创建管道文件{cerr << "mkfifo error" << endl;return 1;}int pipeFd = open(IPC_PATH, O_RDONLY);if(pipeFd < 0){cerr << "open error" << endl;return 2;}//正常的通信过程
#define NUM 1024char buffer[NUM];while(true){ssize_t s = read(pipeFd, buffer, sizeof(buffer) - 1);if(s > 0){buffer[s] = '\0';cout << "客户端->服务器#" << buffer << endl;}else if(s == 0){cout << "客户退出了,我也退出了!" << endl;break;}else{//do nothingcerr << "read" << strerror(errno) << endl;}}close(pipeFd);cout << "服务端退出了" << endl;unlink(IPC_PATH);//sreturn 0;
}

comm.h文件:

#pragma once
#include<cstdio>
#include<iostream>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<cstring>
#include<cerrno>
#define IPC_PATH "./.fifo"

makefile文件:

.PHONY:all
all:clientFifo serverFifoclientFifo:clientFifo.cppg++  -o $@ $^ -std=c++11
serverFifo:serverFifo.cppg++  -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -rf clientFifo serverFifo .fifo

相关文章:

进程间通信(上)

进程间通信&#xff08;上&#xff09;背景进程间通信目的进程间通信发展进程间通信分类管道什么是管道匿名管道实例代码简单的匿名管道实现一个父进程控制单个子进程完成指定任务父进程控制一批子进程完成任务&#xff08;进程池&#xff09;用fork来共享管道站在文件描述符角…...

【Unity3D】Unity 3D 连接 MySQL 数据库

1.Navicat准备 test 数据库&#xff0c;并在test数据库下创建 user 数据表&#xff0c;预先插入测试数据。 2.启动 Unity Hub 新建一个项目&#xff0c;然后在Unity编辑器的 Project视图 中&#xff0c;右击新建一个 Plugins 文件夹将连接 MySQL的驱动包 导入&#xff08;附加驱…...

vue通用后台管理系统

用到的js库 遇到的问题 vuex和 localStorage区别 vuex在内存中&#xff0c;localStorage存在本地localStorage只能存储字符串类型数据&#xff0c;存储对象需要JSON.stringify() 和 parse()…读取内存比读取硬盘速度要快刷新页面vuex数据丢失&#xff0c;localStorage不会vuex…...

IDEA设置只格式化本次迭代变更的代码

趁着上海梅雨季节&#xff0c;周末狠狠更新一下。平常工作在CR的时候&#xff0c;经常发现会有新同事出现大量代码变更行..一看原因竟是在格式化代码时把历史代码也格式化掉了这样不仅坑了自己&#xff08;覆盖率问题等&#xff09;&#xff0c;也可能会影响原始代码责任到人&a…...

算法训练——剑指offer(Hash集合问题)

摘要 数据结构中有一个用于存储重要的数据结构&#xff0c;它们就是HashMap,HasSet&#xff0c;它典型特征就是存储key:value键值对。在查询制定的key的时候查询效率最高O(1)。Hashmap&#xff0c;HasSet的底层结构是如图所示。它们的区别就是是否存在重复的元素。 二、HashMa…...

Element UI框架学习篇(七)

Element UI框架学习篇(七) 1 新增员工 1.1 前台部分 1.1.1 在vue实例的data里面准备好需要的对象以及属性 addStatus:false,//判断是否弹出新增用户弹窗dailog,为true就显示depts:[],//部门信息mgrs:[],//上级领导信息jobs:[],//工作岗位信息//新增用户所需要的对象newEmp:…...

【项目实战】32G的电脑启动IDEA一个后端服务要2min!谁忍的了?

一、背景 本人电脑性能一般&#xff0c;但是拥有着一台高性能的VDI&#xff08;虚拟桌面基础架构&#xff09;&#xff0c;以下是具体的配置 二、问题描述 但是&#xff0c;即便是拥有这么高的性能&#xff0c;每次运行基于Dubbo微服务架构下的微服务都贼久&#xff0c;以下…...

2022年山东省中职组“网络安全”赛项比赛任务书正式赛题

2022年山东省中职组“网络安全”赛项 比赛任务书 一、竞赛时间 总计&#xff1a;360分钟 竞赛阶段竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 A模块 A-1 登录安全加固 180分钟 200分 A-2 Nginx安全策略 A-3 日志监控 A-4 中间件服务加固 A-5 本地安全策略…...

RibbitMQ 入门到应用 ( 二 ) 安装

3.安装基本操作 3.1.下载安装 3.1.1.官网 下载地址 https://rabbitmq.com/download.html 与Erlang语言对应版本 https://rabbitmq.com/which-erlang.html 3.1.2.安装 Erlang 在确定了RabbitMQ版本号后&#xff0c;先下载安装Erlang环境 Erlang下载链接 https://packa…...

提取DataFrame中每一行的DataFrame.itertuples()方法

【小白从小学Python、C、Java】【计算机等级考试500强双证书】【Python-数据分析】提取DataFrame中的每一行DataFrame.itertuples()选择题关于以下python代码说法错误的一项是?import pandas as pddf pd.DataFrame({A:[a1,a2],B:[b1,b2]},index[i1,i2])print("【显示】d…...

基于卷积神经网络的立体视频编码质量增强方法_余伟杰

基于卷积神经网络的立体视频编码质量增强方法_余伟杰提出的基于TSAN的合成视点质量增强方法全局信息提取流像素重组局部信息提取流多尺度空间注意力机制提出的基于RDEN的轻量级合成视点质量增强方法特征蒸馏注意力块轻量级多尺度空间注意力机制概念扭曲失真孔洞问题失真和伪影提…...

【2023unity游戏制作-mango的冒险】-3.基础动作和动画API实现

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 收录于专栏&#xff1a;unity游戏制作 ⭐mango的基础动作动画的添加⭐ 文章目录⭐mango的基础动作动画的添加⭐&#x1f…...

跨域的几种解决方案?

1-jsonp 【前端后端实现】jsonp: 利用 <script> 标签没有跨域限制的漏洞&#xff0c;网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。JSONP优点是简单兼容性好&#xff0c;可用于解决主流浏览器的跨域数据访问的问题。缺点是仅…...

2022年山东省职业院校技能大赛网络搭建与应用赛项正式赛题

2022年山东省职业院校技能大赛 网络搭建与应用赛项 第二部分 网络搭建与安全部署&服务器配置及应用 竞赛说明&#xff1a; 一、竞赛内容分布 竞赛共分二个模块&#xff0c;其中&#xff1a; 第一模块&#xff1a;网络搭建及安全部署项目 第二模块&#xff1a;服务…...

【JUC并发编程】ArrayBlockingQueue和LinkedBlockingQueue源码2分钟看完

文章目录1、BlockingQueue1&#xff09;接口方法2&#xff09;阻塞队列分类2、ArrayBlockingQueue1&#xff09;构造函数2&#xff09;put()入队3&#xff09;take()出队3、LinkedBlockingQueue1&#xff09;构造函数2&#xff09;put()入队3&#xff09;take()出队1、Blocking…...

GitHub个人资料自述与管理主题设置

目录 关于您的个人资料自述文件 先决条件 添加个人资料自述文件 删除个人资料自述文件 管理主题设置 补充&#xff1a;建立一个空白文件夹 关于您的个人资料自述文件 可以通过创建个人资料 README&#xff0c;在 GitHub.com 上与社区分享有关你自己的信息。 GitHub 在个…...

Express篇-连接mysql

创建数据库配置文件config/sqlconfig.jsconst sqlconfig {host: localhost, // 连接地址user: root, //用户名password: ****, //密码port: 3306 , //端口号database: mysql01_dbbooks //数据库名 } module.exports sqlconfig封装数据库管理工具 utils/mysqlUtils.…...

win10 安装rabbitMQ详细步骤

win10 安装rabbitMQ详细步骤 win10 安装rabbitMQ详细步骤win10 安装rabbitMQ详细步骤一、下载安装程序二、安装配置erlang三、安装rabbitMQ四、验证初始可以通过用户名&#xff1a;guest 密码guest来登录。报错&#xff1a;安装RabbitMQ出现Plugin configuration unchanged.问题…...

【成为架构师课程系列】一线架构师:6个经典困惑及其解法

目录 一线架构师:6个经典困惑及其解法 多阶段还是多视图&#xff1f; 内置最佳实践 架构方法论:3个阶段&#xff0c;一个贯穿 Pre-architecture阶段&#xff1a;ADMEMS矩阵方法 Conceptual Architecture阶段&#xff1a;重大需求塑造做概念架构 Refined Architecture…...

光耦合器的定义与概述

光耦合器或光电耦合器是一种电子元件&#xff0c;基本上充当具有不同电压电平的两个独立电路之间的接口。光耦合器是可在输入和输出源之间提供电气隔离的常用元件。它是一个 6 引脚器件&#xff0c;可以有任意数量的光电探测器。 在这里&#xff0c;光源发出的光束作为输入和输…...

谷粒商城--品牌管理详情

目录 1.简单上传测试 2.Aliyun Spring Boot OSS 3.模块mall-third-service 4.前端 5.数据校验 6.JSR303数据校验 7.分组校验功能 8.自定义校验功能 9.完善代码 1.简单上传测试 OSS是对象存储服务&#xff0c;有什么用呢&#xff1f;把图片存储到云服务器上能让所有人…...

stack、queue和priority_queue

目录 一、栈&#xff08;stack&#xff09; 1.stack的使用 2.容器适配器 3.stack的模拟实现 二、队列&#xff08;queue&#xff09; 1.queue的使用 2.queue的模拟实现 三、双端队列&#xff08;deque&#xff09; 1.vector&#xff0c;list的优缺点 2.认识deque 四…...

面试题(二十二)消息队列与搜索引擎

2. 消息队列 2.1 MQ有什么用&#xff1f; 参考答案 消息队列有很多使用场景&#xff0c;比较常见的有3个&#xff1a;解耦、异步、削峰。 解耦&#xff1a;传统的软件开发模式&#xff0c;各个模块之间相互调用&#xff0c;数据共享&#xff0c;每个模块都要时刻关注其他模…...

Spring Security in Action 第三章 SpringSecurity管理用户

本专栏将从基础开始&#xff0c;循序渐进&#xff0c;以实战为线索&#xff0c;逐步深入SpringSecurity相关知识相关知识&#xff0c;打造完整的SpringSecurity学习步骤&#xff0c;提升工程化编码能力和思维能力&#xff0c;写出高质量代码。希望大家都能够从中有所收获&#…...

Java面试——maven篇

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

基于微信小程序的游戏账号交易小程序

文末联系获取源码 开发语言&#xff1a;Java 框架&#xff1a;ssm JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7/8.0 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 浏览器…...

Matlab绘制隐函数总结-二维和三维

1.二维隐函数 二维隐函数满足f(x,y)0f(x,y)0f(x,y)0&#xff0c;这里无法得到yf(x)yf(x)yf(x)的形式。不能通过普通函数绘制。 我们要关注的是使用fplot函数和fimplicit函数。 第1种情况&#xff1a;基本隐函数 基本的隐函数形式形如&#xff1a; x2y22x2(x2y2)12x^{2}y^{…...

如何直观地理解傅立叶变换?频域和时域的理解

如何直观地理解傅立叶变换 傅里叶变换连续形式的傅立叶变换如何直观地理解傅立叶变换?一、傅里叶级数1.1傅里叶级数的三角形式1.2 傅里叶级数的复指数形式二、傅里叶变换2.1一维连续傅里叶变换三、频谱和功率谱3.1频谱的获得3.2频谱图的特征3.3频谱图的组成频域(frequency do…...

STC15读取内部ID示例程序

STC15读取内部ID示例程序&#x1f389;本案例基于STC15F2K60S2为验证对象。 &#x1f4d1;STC15 ID序列介绍 STC15系列STC最新一代STC15系列单片机出厂时都具有全球唯一身份证号码(ID号)。最新STC15系列单片机的程序存储器的最后7个字节单元的值是全球唯一ID号&#xff0c;用…...

Xml格式化与高亮显示

具体请参考&#xff1a;Xml格式化与高亮显示...

wordpress前台注册地址/网站建设定制

本文由玉刚说写作平台提供写作赞助 原作者&#xff1a;Zackratos 版权声明&#xff1a;本文版权归微信公众号 玉刚说 所有&#xff0c;未经许可&#xff0c;不得以任何形式转载 什么是 MVP MVP 全称&#xff1a;Model-View-Presenter &#xff1b;MVP 是从经典的模式 MVC 演变而…...

受欢迎的广州做网站/成免费的crm

Educational Codeforces Round 116&#xff08;Rated for Div.2&#xff09;简训导语涉及的知识点题目A AB BalanceB Update FilesC Banknotes参考文献导语 涉及的知识点 数学&#xff0c;思维 链接&#xff1a;Educational Codeforces Round 116&#xff08;Rated for Div.2…...

wordpress发送邮箱验证码/北京seo招聘信息

中秋节快乐&#xff01; 转载于:https://www.cnblogs.com/Alanf/p/9687702.html...

南昌专业网站制作公司/百度sem

背景。元素的背景显示区域在内容区和内边距区&#xff0c;并且边框是画在背景之上的。这就是说如果边框的样式是dotted之类的&#xff0c;则边框空隙之间是可以看到背景的。 可以设置背景的声明有&#xff1a;background-color、background-image、background-position、backgr…...

我的世界是怎么做的视频网站/免费的网站推广平台

导读&#xff1a;习惯的力量是惊人的。习惯能载着你走向成功&#xff0c;也能驮着你滑向失败。如何选择&#xff0c;完全取决于你自己。 1.习惯的力量:35岁以前养成好习惯 你想成功吗&#xff1f;那就及早培养有利于成功的好习惯。 习惯的力量是惊人的&#xff0c;35岁以前养成…...

网站规划与建设周正刚/互联网营销顾问是做什么的

- 基础&#xff1a; 下载安装  声明变量的方法  数据的三种基础类型&#xff1a;bool&#xff0c;数字&#xff0c;string  数据类型&#xff1a;数组和切片  数据类型&#xff1a;Maps  条件判断以及循环  函数  包管理 package  指针  结构体- 初步进阶&am…...