进程间通信(上)
进程间通信(上)
- 背景
- 进程间通信目的
- 进程间通信发展
- 进程间通信分类
- 管道
- 什么是管道
- 匿名管道
- 实例代码
- 简单的匿名管道实现
- 一个父进程控制单个子进程完成指定任务
- 父进程控制一批子进程完成任务(进程池)
- 用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中最古老的进程间通信的形式。
-
我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”

匿名管道

里面封装了两次open,第一次是以读方式打开,返回值写在fd[0]中,也就是打开文件的fd,第二次是以写方式打开,返回值写在fd[1]中,也就是打开文件的fd。同时通过上面的联合体标定它是一个管道文件。
#include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码-1

实例代码
简单的匿名管道实现
#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来共享管道

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

问:为什么父进程要分别打开读和写?
答:为了让子进程继承,让子进程不必再打开了。
问:为什么父子要关闭对应的读和写?
答:因为管道必须是单向通信的,一端是读端,另已端必须是写端。
问:谁决定父子关闭读端还是写端?
答:由需求决定。
站在内核角度-管道本质

理解管道操作 – |
注意:|操作的本质就是匿名管道。

sleep 1000 | sleep 100
这两个进程(sleep 1000和sleep 100)的关系是什么呢?两个进程的ppid是一样的,即有同样的父进程。
以下面的命令进行举例:
cat pipe.cc | wc -l
父进程fork两个子进程即cat pipe.cc和wc -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将不再保证写入的原子性。
管道的特点
- 管道只能用来进行具有血缘关系的进程之间进行进程间通信,常用于父子间通信或者兄弟间通信。(只属于匿名管道)
- 管道只能单向通信(内核实现决定的),半双工的一种特殊情况
- 管道自带同步机制(内核会对管道操作进行同步与互斥)(pipe满,writer等,pipe空,reader等)
- 管道是面向字节流的,先写的字符,一定是先被读取的,没有格式边界,需要用户来定义区分内容的边界
- 管道的生命周期跟随进程 – 管道是文件 – 进程退出了,曾经打开是文件引用计数到达0就会自动退出
命名管道
- 管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
- 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
- 命名管道是一种特殊类型的文件
原理

和匿名管道的区别:
匿名管道:子进程继承父进程。
命名管道:通过打开同一个fifo文件,进行信息的交互(路径具有唯一性)
注意:我们使用的命名管道,更多的时候是作为一种标定的作用,内存中的管道文件中的数据不会刷新到磁盘中,即使在进行通信的时候,命名管道的大小也始终是0个字节。
创建一个命名管道
-
命名管道可以从命令行上创建,命令行方法是使用下面这个命令:
$ mkfifo filename -
命名管道也可以从程序里创建,相关函数有:
int mkfifo(const char *filename,mode_t mode);
命名管道具体示例
命令行创建
简单使用:


代码示例
创建管道文件:

删除管道文件:

代码示例:
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
相关文章:
进程间通信(上)
进程间通信(上)背景进程间通信目的进程间通信发展进程间通信分类管道什么是管道匿名管道实例代码简单的匿名管道实现一个父进程控制单个子进程完成指定任务父进程控制一批子进程完成任务(进程池)用fork来共享管道站在文件描述符角…...
【Unity3D】Unity 3D 连接 MySQL 数据库
1.Navicat准备 test 数据库,并在test数据库下创建 user 数据表,预先插入测试数据。 2.启动 Unity Hub 新建一个项目,然后在Unity编辑器的 Project视图 中,右击新建一个 Plugins 文件夹将连接 MySQL的驱动包 导入(附加驱…...
vue通用后台管理系统
用到的js库 遇到的问题 vuex和 localStorage区别 vuex在内存中,localStorage存在本地localStorage只能存储字符串类型数据,存储对象需要JSON.stringify() 和 parse()…读取内存比读取硬盘速度要快刷新页面vuex数据丢失,localStorage不会vuex…...
IDEA设置只格式化本次迭代变更的代码
趁着上海梅雨季节,周末狠狠更新一下。平常工作在CR的时候,经常发现会有新同事出现大量代码变更行..一看原因竟是在格式化代码时把历史代码也格式化掉了这样不仅坑了自己(覆盖率问题等),也可能会影响原始代码责任到人&a…...
算法训练——剑指offer(Hash集合问题)
摘要 数据结构中有一个用于存储重要的数据结构,它们就是HashMap,HasSet,它典型特征就是存储key:value键值对。在查询制定的key的时候查询效率最高O(1)。Hashmap,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!谁忍的了?
一、背景 本人电脑性能一般,但是拥有着一台高性能的VDI(虚拟桌面基础架构),以下是具体的配置 二、问题描述 但是,即便是拥有这么高的性能,每次运行基于Dubbo微服务架构下的微服务都贼久,以下…...
2022年山东省中职组“网络安全”赛项比赛任务书正式赛题
2022年山东省中职组“网络安全”赛项 比赛任务书 一、竞赛时间 总计: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版本号后,先下载安装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实现
👨💻个人主页:元宇宙-秩沅 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 本文由 秩沅 原创 收录于专栏:unity游戏制作 ⭐mango的基础动作动画的添加⭐ 文章目录⭐mango的基础动作动画的添加⭐…...
跨域的几种解决方案?
1-jsonp 【前端后端实现】jsonp: 利用 <script> 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅…...
2022年山东省职业院校技能大赛网络搭建与应用赛项正式赛题
2022年山东省职业院校技能大赛 网络搭建与应用赛项 第二部分 网络搭建与安全部署&服务器配置及应用 竞赛说明: 一、竞赛内容分布 竞赛共分二个模块,其中: 第一模块:网络搭建及安全部署项目 第二模块:服务…...
【JUC并发编程】ArrayBlockingQueue和LinkedBlockingQueue源码2分钟看完
文章目录1、BlockingQueue1)接口方法2)阻塞队列分类2、ArrayBlockingQueue1)构造函数2)put()入队3)take()出队3、LinkedBlockingQueue1)构造函数2)put()入队3)take()出队1、Blocking…...
GitHub个人资料自述与管理主题设置
目录 关于您的个人资料自述文件 先决条件 添加个人资料自述文件 删除个人资料自述文件 管理主题设置 补充:建立一个空白文件夹 关于您的个人资料自述文件 可以通过创建个人资料 README,在 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四、验证初始可以通过用户名:guest 密码guest来登录。报错:安装RabbitMQ出现Plugin configuration unchanged.问题…...
【成为架构师课程系列】一线架构师:6个经典困惑及其解法
目录 一线架构师:6个经典困惑及其解法 多阶段还是多视图? 内置最佳实践 架构方法论:3个阶段,一个贯穿 Pre-architecture阶段:ADMEMS矩阵方法 Conceptual Architecture阶段:重大需求塑造做概念架构 Refined Architecture…...
光耦合器的定义与概述
光耦合器或光电耦合器是一种电子元件,基本上充当具有不同电压电平的两个独立电路之间的接口。光耦合器是可在输入和输出源之间提供电气隔离的常用元件。它是一个 6 引脚器件,可以有任意数量的光电探测器。 在这里,光源发出的光束作为输入和输…...
wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...
P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
