利用管道、信号量、信号、共享内存和消息队列进行多进程通信

一.管道(分为命名管道和匿名管道)

管道的特点:

①无论是命名管道还是匿名管道,写入管道的数据都存放在内存之中。

②管道是一种半双工的通信方式(半双工是指终端A能发信号给终端B,终端B也能发信号给终端A,但这两个过程不能同时进行)

③命名管道和匿名管道的区别:命名管道可以在任意进程间使用,匿名管道主要在父子进程间使用。

命名管道 

int mkfifo( const char *filename, mode_t mode);//filename 是管道名 mode 是创建的文件访问权限

 实践

打开两个独立的终端窗口,分别运行两个进程。

//终端1   进程a.c 创建管道 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main(){const char *fifoPath = "./myfifo";//'./'是指在当前目录下创建mode_t fifoMode = 0600;//第一个0代表10进制,第二个6代表用户有读写权限,后面两个0代表组用户和其他用户无权限// 使用 mkfifo 创建命名管道if (mkfifo(fifoPath, fifoMode) == -1) {printf("mkfifo error\n");exit(1);}printf("Named pipe created at %s\n", fifoPath);exit(0);
}

先运行终端1,其运行结果如图: 

//终端2    进程b.c   读取数据
#include <stdio.h>
#include<stdlib.h>
#include <fcntl.h>
#include <unistd.h>int main() 
{int fd = open("./myfifo", O_RDONLY);char buffer[20];read(fd, buffer, sizeof(buffer));close(fd);printf("Received: %s\n", buffer);exit(0);
}

由于程序是以阻塞模式打开 命名管道并尝试读取数据。这意味着如果没有其他进程写入数据到该管道,它会一直等待,直到有数据可读为止。所以b.c运行结果如图:

 再次打开终端1,并在其中写入如下程序:

//终端1 进程c.c  写入数据
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<string.h>int main()
{int fd = open("./myfifo", O_WRONLY);const char *message = "Hello, Process B!";write(fd, message, strlen(message) + 1);close(fd);exit(0);
}

当运行c.c后,终端2中b.c的运行结果如图:

匿名管道

int pipe( int fds[2]);//pipe()成功返回 0,失败返回-1;fds[0]是管道读端的描述符;fds[1]是管道写端的描述符。

实践

用fork()创建子进程,父进程写入数据,子进程读取并输出数据。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include<string.h>
#include <sys/types.h>int main() 
{int fd[2];char buffer[100];int res = pipe(fd); // 创建管道pid_t pid = fork(); // 创建子进程if (res == -1) {printf("pipe failed\n");exit(1);}if (pid == -1) {printf("fork failed\n");exit(1);}if (pid == 0) // 子进程{//sleep(1);close(fd[1]); // 关闭写入端read(fd[0], buffer, sizeof(buffer));//fflush(stdout);printf("Child received: %s\n", buffer);} else // 父进程{  close(fd[0]);  // 关闭读取端const char *message = "Hello, Child!";write(fd[1], message, strlen(message) + 1);}close(fd[0]);close(fd[1]);exit(0);
}

二.信号

kill()

int kill(pid_t pid, int sig);//用于向指定的进程发送信号。

//pid是要发送信号的目标进程的进程编号。

//sig是要发送的信号的编号。

实践:

打开两个独立的终端窗口,分别运行两个进程

//终端1 send.cpp#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main() 
{pid_t receiver_pid;  // 需要知道接收方进程的pid// 获取接收方进程的PIDprintf("输入接收进程的pid: ");scanf("%d", &receiver_pid);// 发送自定义信号到接收方进程if (kill(receiver_pid, SIGUSR1) == 0) {printf(" 发送给进程号为:%d的进程\n", receiver_pid);} else {perror("信号量发送失败");}return 0;
}
//终端2 recv.cpp#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>// 信号处理函数
void signal_handler(int signum) 
{if (signum == SIGUSR1) {printf("自定义信号量收到\n");}
}int main() 
{// 约定信号signal(SIGUSR1, signal_handler);printf("接收进程的pid为: %d\n", getpid());while (1) {// 等待信号的到来sleep(1);}return 0;
}

运行结果: 

三.信号量

信号量是一个特殊的变量,一般取正数值。它的值代表允许访问的资源数目。

p操作--原子减一--代表获取资源--可能阻塞(当信号量值为0,即无资源可用时,会阻塞)

v操作--原子加一--代表释放资源--不会阻塞

semget()

int semget(key_t key, int nsems, int semflg);//创建一个新信号量或取得一个已有信号量的键。

//key,算是一个标识符,拥有相同key值的进程可以进行通讯

//nesms,通常取值为1

//semflg,通常是IPC_CREAT,不过使用IPC_CREAT时要加权限,如0666

//成功返回一个正数,失败返回-1

semctl()

int semctl(int semid, int semnum, int cmd, ... );//控制信号量信息。

//semid,由semget()返回

//semnum,一般取0

//cmd,用于指定要进行的操作,如:GETVAL(获取信号量的值) 、SETVAL(设置信号量的值),                                                                IPC_RMID(删除信号量)

//...,也许会用到这个参数,它是union semun类型的参数,用于传递额外信息

union semun
{int              val;  //用于设置或获取单个信号量的值struct semid_ds *buf;  //用于传递IPC_STAT或IPC_SET命令的参数unsigned short  *array;//用于传递GETALL或SETALL命令的参数
};

实践:

用信号量和两个进程来模拟打印机:进程A输出'a',代表开始使用,再次输出'a'代表结束使用,进程B输出'b'代表开始使用,再次输出'b'代表结束使用,所以预期结果输出为aabbaabb......

/*sem.h文件*///封装P、V操作
#include<iostream>
#include<unistd.h>
#include<string.h>
#include<sys/sem.h>using namespace std;union semnum
{int val;struct semid_ds *buf;unsigned short *array;
};void sem_init();   //创建信号量
void sem_p();      //p操作
void sem_v();      //v操作
void sem_destroy();//销毁信号量
/*sem.cpp文件*/#include"sem.h"static int semid = -1;//初始化信号量void sem_init()
{semid = semget((key_t)1234, 1, IPC_CREAT | 0600);union semnum a;a.val = 1;//信号量的初始值if(semctl(semid,0,SETVAL,a)==-1){cout << "semctl failed" << endl;}
}void sem_p()
{struct sembuf buf;// 信号量的标志,通常使用 IPC_NOWAIT 或 SEM_UNDObuf.sem_flg = SEM_UNDO;// 信号量的编号,如果有多个信号量,可以使用不同的编号进行区分buf.sem_num = 0;buf.sem_op = -1;//p操作if(semop(semid,&buf,1)==-1){cout << "semop falied" << endl;}
}void sem_v()
{struct sembuf buf;buf.sem_flg = SEM_UNDO;buf.sem_num = 0;buf.sem_op = 1;//v操作if(semop(semid,&buf,1)==-1){cout << "semop falied" << endl;}
}void sem_destroy()
{if(semctl(semid,0,IPC_RMID)==-1){cout << "semctl move failed" << endl;}
}
/*a.cpp文件*/#include"sem.h"int main()
{sem_init();for (int i = 0; i < 5;i++){sem_p();cout << "a";fflush(stdout);sleep(5);cout << "a";fflush(stdout);sem_v();sleep(5);}sleep(10);//等待另一个进程完成sem_destroy();return 0;
}
/*b.cpp文件*/#include"sem.h"int main()
{sem_init();for (int i = 0; i < 5;i++){sem_p();cout << "b";fflush(stdout);sleep(5);cout << "b";fflush(stdout);sem_v();sleep(5);}return 0;
}

另外两个重要的概念

临界资源:同一时刻,只允许被一个进程或线程访问的资源
临界区:访问临界资源的代码段

四.共享内存

共享内存是先在物理内存上申请一块空间,多个进程可以将其映射到自己的虚拟地址空间中。这是是一种用于在不同进程之间共享数据的机制,它允许多个进程访问同一块内存区域,从而实现高效的进程间通信。

shmget()

int shmget(key_t key, size_t size, int shmflg); //创建或获取共享内存段,并返回一个共享内存标识符,通常称为 shmid。

//key:共享内存标识键值

//size:指定要创建的共享内存段的大小

//shmflg:用于指定创建共享内存的权限和行为,通常是IPC_CREAT(表示如果指定的 IPC 资源不存在,则创建它,如果已经存在则忽略)或者IPC_EXCL(表示只创建 IPC 资源,如果资源已经存在,创建操作将失败)

shmat()

void *shmat(int shmid, const void *shmaddr, int shmflg);//用于将共享内存附加到进程的地址空间,以便进程可以访问共享内存中的数据。

//shmid:是由 shmget 函数创建的共享内存的标识符

//shmaddr:通常设置为 NULL

//shmflg:通常设置为0

shmdt()

int shmdt(const void *shmaddr);//用于将共享内存从进程的地址空间中分离,使得该进程不能再访问共享内存中的数据。

//shmaddr:是共享内存的地址,通常是在使用 shmat 函数时获得的指针。

shmctl()

int shmctl(int shmid, int cmd, struct shmid_ds *buf);//用于获取关于共享内存段的信息、修改权限、删除共享内存段等。

//shmid:使用 shmget 函数获得。

//cmd:指定要执行的操作,例如获取信息(IPC_STAT)、修改权限(IPC_SET)、删除共享内存段(IPC_RMID)等。

//buf:用于传递或接收有关共享内存段的信息。如果不需要传递或接收信息,可以将其设置为 NULL。

实践

write进程和read进程通过共享内存分别来写入数据和读取数据。

/*shm.h文件*///封装P、V操作
#include<iostream>
#include<unistd.h>
#include<string.h>
#include<sys/sem.h>using namespace std;//创建两个信号量
enum INDEX
{SEM1=0,SEM2
};union semnum
{int val;struct semid_ds *buf;unsigned short *array;
};void sem_init();   //创建信号量
void sem_p(enum INDEX i);      //p操作
void sem_v(enum INDEX i);      //v操作
void sem_destroy();//销毁信号量
/*shm.cpp文件*/
#include "shm.h"static int semid = -1; // 初始化信号量void sem_init()
{semid = semget((key_t)1234, 2, IPC_CREAT | 0600); // 创建2个信号量int arr[2] = {1, 0};union semnum a;for (int i = 0; i < 2; i++){a.val = arr[i]; // 信号量的初始值if (semctl(semid, i, SETVAL, a) == -1){cout << "semctl failed" << endl;}}
}void sem_p(enum INDEX i)
{struct sembuf buf;buf.sem_flg = SEM_UNDO;buf.sem_num = i;buf.sem_op = -1; // p操作if (semop(semid, &buf, 1) == -1){cout << "semop falied" << endl;}
}void sem_v(enum INDEX i)
{struct sembuf buf;buf.sem_flg = SEM_UNDO;buf.sem_num = i;buf.sem_op = 1; // v操作if (semop(semid, &buf, 1) == -1){cout << "semop falied" << endl;}
}void sem_destroy()
{if (semctl(semid, 0, IPC_RMID) == -1)//销毁所有信号量{cout << "semctl move failed" << endl;}
}
/*write.cpp文件*/
#include"shm.h"
#include <sys/shm.h>int main()
{// 创建共享内存int shmid = shmget((key_t)1234, 128, IPC_CREAT | 0600);if (shmid == -1){cout << "write shget filed" << endl;exit(1);}// 将共享内存映射到当前的地址空间char *s = (char *)shmat(shmid, NULL, 0);if (s == (char *)-1){cout << "write shmat failed" << endl;exit(1);}sem_init();// 往共享内存中写入数据while (1){cout << "请输入内容:" << endl;char buff[128] = {0};cin >> buff;sem_p(SEM1);strcpy(s, buff);sem_v(SEM2);if (strncmp(buff, "end", 3) == 0){break;}}//分离共享内存shmdt(s);return 0;
}
/*read.cpp文件*/
#include"shm.h"
#include <sys/shm.h>int main()
{// 创建共享内存int shmid = shmget((key_t)1234, 128, IPC_CREAT | 0600);if (shmid == -1){cout << "read shget filed" << endl;exit(1);}// 将共享内存映射到当前的地址空间char *s = (char *)shmat(shmid, NULL, 0);if (s == (char *)-1){cout << "read shmat failed" << endl;exit(1);}sem_init();// 从共享内存中读取数据while (1){sem_p(SEM2);if (strncmp(s, "end", 3) == 0){break;}cout << s << endl;sem_v(SEM1);}//分离共享内存shmdt(s);//销毁共享内存shmctl(shmid, IPC_RMID, NULL);//销毁信号量sem_destroy();return 0;
}

 运行结果:

五.消息队列

msgget()

int msgget(key_t key, int msgflg);//创建消息队列

//key,键值

//msgflg,用于指定消息队列的创建方式和权限

msgctl()

int msgctl(int msqid, int cmd, struct msqid_ds *buf);//控制消息队列
//msqid,消息队列的标识符,由msgget()返回

//cmd,操作命令

//*buf,消息队列信息的结构体指针

msgrcv()

int msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);//从消息队列中接收消息

//msgid,消息队列的标识符,由msgget()返回

//*msgp,消息缓冲区的指针,数据将被复制到这个缓冲区

//msgsz,缓冲区的大小,以字节为单位

//msgtyp,接收消息的类型,如果设为0,则不区分类型

//msgflg,控制信息接收的行为

msgsnd()

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);//用于向消息队列中发送信息

//msgid,消息队列的标识符,由msgget()返回

//*msgp,消息缓冲区的指针,其中包含要发送的数据

//msgsz,缓冲区的大小,以字节为单位

//msgflg,控制信息接收的行为

/*msga.cpp文件*/
#include<iostream>
#include<unistd.h>
#include<string.h>
#include<sys/msg.h>using namespace std;// 定义消息结构
struct message 
{long type;//必须为长整形char buff[32];
};int main()
{int msgid = msgget((key_t)1234, IPC_CREAT | 0600);if(msgid==-1){cout << "msgget failed" << endl;exit(1);}struct message dt;dt.type = 1;//消息类型为1strcpy(dt.buff, "hello");msgsnd(msgid, &dt, 32, 0);return 0;
}
/*msgb.cpp文件*/
#include<iostream>
#include<unistd.h>
#include<string.h>
#include<sys/msg.h>using namespace std;// 定义消息结构
struct message 
{long type;//必须为长整形char buff[32];
};int main()
{int msgid = msgget((key_t)1234, IPC_CREAT | 0600);if(msgid==-1){cout << "msgb.cpp msgget failed" << endl;exit(0);}struct message dt;msgrcv(msgid, &dt, 32, 1, 0);cout << "接收到内容为:" << dt.buff << "的消息" << endl;return 0;
}

运行结果:

 入上图:运行了2此msga.cpp,消息队列中有2条消息,当运行msgb.cpp之后,消息队列剩余消息如下:

六.socket

此部分详见文章用c语言编写简单的一对一服务器和多对一服务器。

 

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

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

相关文章

软件开发安全指南

2.1.应用系统架构安全设计要求 2.2.应用系统软件功能安全设计要求 2.3.应用系统存储安全设计要求 2.4.应用系统通讯安全设计要求 2.5.应用系统数据库安全设计要求 2.6.应用系统数据安全设计要求 软件开发全资料获取&#xff1a;点我获取

Linux 网络协议

1 网络基础 1.1 网络概念 网络是一组计算机或者网络设备通过有形的线缆或者无形的媒介如无线&#xff0c;连接起来&#xff0c;按照一定的规则&#xff0c;进行通讯的集合( 缺一不可 )。 5G的来临以及IPv6的不断普及&#xff0c;能够进行联网的设备将会是越来越多&#xff08…

vue 商品列表案例

my-tag 标签组件的封装 1. 创建组件 - 初始化 2. 实现功能 (1) 双击显示&#xff0c;并且自动聚焦 v-if v-else dbclick 操作 isEdit 自动聚焦&#xff1a; 1. $nextTick > $refs 获取到dom&#xff0c;进行focus获取焦点 2. 封装v-focus指令 (2) 失去焦点&#xff0c;隐藏…

用Rust刷LeetCode之66 加一

66. 加一[1] 难度: 简单 func plusOne(digits []int) []int { length : len(digits) // 从最低位开始遍历&#xff0c;逐位加一 for i : length - 1; i > 0; i-- { if digits[i] < 9 { digits[i] return digits } d…

【Mac】brew提示arch -arm64 brew以及uname返回x86_64的问题

背景 使用MacBook 14 M1 Pro两年了&#xff0c;自从使用了第三方Shell工具WindTerm后&#xff0c;使用brew时会提示我使用arch -arm64 brew安装&#xff0c;一开始没太在意&#xff0c;直到今天朋友问我uname -a返回的是什么架构&#xff0c;我才惊讶的发现竟然返回的是x86_64…

优化系统性能:深入性能测试的重要性与最佳实践

目录 引言 1. 为什么性能测试重要&#xff1f; 1.1 用户体验 1.2 系统稳定性 1.3 成本节约 1.4 品牌声誉 2. 性能测试的关键步骤 2.1 制定性能测试计划 2.2 确定性能测试类型 2.3 设计性能测试用例 2.4 配置性能测试环境 2.5 执行性能测试 2.6 分析和优化 2.7 回…

QT----Visual Studio打开.ui文件报错无法打开

问题 在我安装完qt后将它嵌入vs&#xff0c;后新建的文件无法打开ui文件 解决方法 右击ui文件打开方式,添加,程序找到你qt的安装目录里的designer.exe。点击确定再次双击就能够打开。

VMware提示:此虚拟机似乎正在使用中,取得该虚拟机的所有权失败错误的解决方案

当你遇到这个的时候是不是很疑惑&#xff0c;现在给你解决方案 step1: 先找到配置文件目录 D:\centOs7_mini\ 这里写成你的这个 step2: 在这个地方查找最后面是 .vmx.lck文件夹,然后进行修改、删除、移动都可以 step3: 去虚拟机那边重新启动就行

RabbitMQ-学习笔记(初识 RabbitMQ)

本篇文章学习于 bilibili黑马 的视频 (狗头保命) 同步通讯 & 异步通讯 (RabbitMQ 的前置知识) 同步通讯&#xff1a;类似打电话&#xff0c;只有对方接受了你发起的请求,双方才能进行通讯, 同一时刻你只能跟一个人打视频电话。异步通讯&#xff1a;类似发信息&#xff0c…

【mysql】下一行减去上一行数据、自增序列场景应用

背景 想获取if_yc为1连续账期数据 思路 获取所有if_yc为1的账期数据下一行减去上一行账期&#xff0c;如果为1则为连续&#xff0c;不等于1就为断档获取不等于1的最小账期&#xff0c;就是离当前账期最近连续账期 代码 以下为mysql语法&#xff1a; select acct_month f…

查看Linux的Ubuntu的版本

我的Ubuntu版本是 Jammy x86_64&#xff0c;即 Ubuntu 22.04.3 LTS&#xff0c;代号为"Jammy Jellyfish"&#xff0c;架构是 x86_64&#xff08;64位&#xff09;。

NLP自然语言处理学习笔记

参考&#xff1a;NLP&#xff08;自然语言处理&#xff09;介绍 - 知乎 (zhihu.com) 一、NLP是什么 自然语言处理( Natural Language Processing, NLP)是计算机科学领域与人工智能领域中的一个重要方向。它研究能实现人与计算机之间用自然语言进行有效通信的各种理论和方法。自…

最新版本——Hadoop3.3.6单机版完全部署指南

大家好&#xff0c;我是独孤风&#xff0c;大数据流动的作者。 本文基于最新的 Hadoop 3.3.6 的版本编写&#xff0c;带大家通过单机版充分了解 Apache Hadoop 的使用。本文更强调实践&#xff0c;实践是大数据学习的重要环节&#xff0c;也能在实践中对该技术有更深的理解&…

105.长度最小的子数组(力扣)|滑动窗口

代码演示 class Solution { public:int minSubArrayLen(int target, vector<int>& nums) {int result INT_MAX; // 用于存储最小子数组的长度int sum 0; // 滑动窗口的长度int i 0; // 滑动窗口的起始位置int sumlength 0; // 当前子数…

深度学习与逻辑回归模型的融合--TensorFlow多元分类的高级应用

手写数字识别 文章目录 手写数字识别1、线性回归VS逻辑回归Sigmoid函数 2、逻辑回归的基本模型-神经网络模型3、多元分类基本模型4、TensorFlow实战解决手写数字识别问题准备数据集数据集划分 特征数据归一化归一化方法归一化场景 标签数据独热编码One-Hot编码构建模型损失函数…

探索人工智能领域——每日20个名词详解【day11】

目录 前言 正文 总结 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Filotimo__✍️原创&#xff0c;首发于CSDN&#x1f4da;。 &#x1f4e3;如需转载&#xff0c;请事先与我联系以…

uni-app 微信小程序之好看的ui登录页面(四)

文章目录 1. 页面效果2. 页面样式代码 更多登录ui页面 uni-app 微信小程序之好看的ui登录页面&#xff08;一&#xff09; uni-app 微信小程序之好看的ui登录页面&#xff08;二&#xff09; uni-app 微信小程序之好看的ui登录页面&#xff08;三&#xff09; uni-app 微信小程…

论文阅读:LSeg: LANGUAGE-DRIVEN SEMANTIC SEGMENTATION

可以直接bryanyzhu的讲解&#xff1a;CLIP 改进工作串讲&#xff08;上&#xff09;【论文精读42】_哔哩哔哩_bilibili 这里是详细的翻译工作 原文链接 https://arxiv.org/pdf/2201.03546.pdf ICLR 2022 0、ABSTRACT 我们提出了一种新的语言驱动的语义图像分割模型LSeg。…

【webpack】应用篇

基础应用 代码分离常用的代码分离方法方法一&#xff1a;配置入口节点方法二&#xff1a;防止重复方法三&#xff1a;动态导入 缓存原因解决思路 缓存第三方库原因解决思路 将所有js文件单独存放文件夹拆分开发环境和生产环境配置公共路径环境变量和区分环境代码压缩 拆分配置文…

【Python】np.save()和np.load()函数详解和示例

本文通过函数原理和运行示例&#xff0c;对np.save()和np.load()函数进行详解&#xff0c;以帮助大家理解和使用。 更多Numpy函数详解和示例&#xff0c;可参考 【Python】Numpy库近50个常用函数详解和示例&#xff0c;可作为工具手册使用 目录 np.save &#xff08;&#xff…