【Linux C | 进程】Linux 进程间通信的10种方式(2)

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍 🍭
😎金句分享😎:🍭你不能选择最好的,但最好的会来选择你——泰戈尔🍭

本文未经允许,不得转发!!!

目录

  • 🎄一、POSIX消息队列
    • ✨1.1 POSIX消息队列介绍
    • ✨1.2 例子
  • 🎄二、POSIX信号量
    • ✨2.1 POSIX信号量介绍
    • ✨2.2 例子
  • 🎄三、 POSIX共享内存
    • ✨3.1 POSIX共享内存介绍
    • ✨3.2 例子
  • 🎄四、信号
  • 🎄五、套接字
  • 🎄六、总结


在这里插入图片描述

🎄一、POSIX消息队列

✨1.1 POSIX消息队列介绍

POSIX消息队列与System V消息队列有一定的相似之处, 信息交换的基本单位是消息, 但也有显著的区别。Linux实现里POSIX消息队列的句柄本质是文件描述符,所以可以使用I/O多路复用系统调用(select、 poll或epoll
等) 来监控这个文件描述符。其次, POSIX消息队列提供了通知功能, 当消息队列中有消息可用时, 就会通知到进程。

编程步骤:

  • 1、创建/获取消息队列
    #include <fcntl.h>           /* For O_* constants */
    #include <sys/stat.h>        /* For mode constants */
    #include <mqueue.h>
    mqd_t mq_open(const char *name, int oflag);
    mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr);
    
    name:必须以/打头, 而且后续字符不允许出现/,最大长度为255个字符;
    oflag:允许的标志位包括O_RDONLY、O_WRONLY、O_RDWR、O_CREAT、O_EXCL、O_NONBLOCK
    mode:mode设置的是访问权限,创建时有效;
    attr:attr设置的是消息队列的属性,创建时有效。
    int mq_fd = mq_open("/mqPosix", O_RDWR | O_CREAT, 0666, NULL); //创建
    int mq_fd = mq_open("/mqPosix", O_RDWR); // 获取
    
  • 2、发送/获取数据
    服务端发送:mq_send(mq_fd, buf, strlen(buf), i);
    客户端获取:mq_receive(mq_fd, buf, mqAttr.mq_msgsize, &prio);
    获取数据之前,可能需要先获取消息队列的属性,mq_getattr(mq_fd,&attr)
  • 3、关闭消息队列句柄
    mq_close(mq_fd);
    
  • 4、删除消息队列
    mq_unlink("/mqPosix");
    

✨1.2 例子

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <mqueue.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
int main()
{
	// 创建子进程
	pid_t pid = fork();
    if(pid == 0) {// 子进程
		printf("子进程[%d]开始执行, 创建POSIX消息队列,循环往里写数据\n", getpid());
		// 创建消息队列
		int mq_fd = mq_open("/mqPosix", O_RDWR|O_CREAT|O_EXCL, 0664, NULL);
		if(mq_fd < 0)
		{
			if (errno == EEXIST)
			{
				printf("/mqPosix EEXIST\n");
				mq_unlink("/mqPosix");
				mq_fd = mq_open("/mqPosix", O_RDWR | O_CREAT, 0666, NULL);
			}
			else
			{
				perror("mq_open failed");
				exit(-1);
			}
		}
		// 发送数据
		int i = 9;
		while(i>=0)
		{
			char buf[256] = {0,};
			sprintf(buf, "hello-%d", i);
			if (mq_send(mq_fd, buf, strlen(buf), i) < 0)
			{
				perror("mq_send failed");
				exit(-1);
			}
			printf("子进程[%d]写入数据:hello-%d\n", getpid(), i);
			i--;
			sleep(1);
		}
		
		mq_close(mq_fd);
		
		printf("子进程[%d]退出\n", getpid());
        return 0;
    }
	else if(pid > 0)// 父进程
	{
		sleep(3); //延时一会,让子进程先运行
		printf("父进程[%d]开始执行, 获取消息队列,读取数据\n", getpid()); 
		int mq_fd = mq_open("/mqPosix", O_RDWR);
		if(mq_fd == -1)
		{
			perror("mq_open failed");
			exit(1);
		}
		struct mq_attr mqAttr;
		mq_getattr(mq_fd, &mqAttr);
		printf("mq_msgsize=%ld\n",mqAttr.mq_msgsize);
		char *buf = (char*)malloc(mqAttr.mq_msgsize);
		while(1)
		{
			unsigned prio = 0;
			int res = mq_receive(mq_fd, buf, mqAttr.mq_msgsize, &prio);//阻塞
			printf("res=%d, 消息:%s, prio:%u\n", res, buf, prio);
			if(res == -1)
			{
				perror("mq_receive failed");
				break;
			}
		}
		
		mq_close(mq_fd);
		mq_unlink("/mqPosix");
		
		printf("父进程[%d]退出\n", getpid());
        return 0;
	}
	else
	{
		printf("Error in fork\n"); 
        exit(1); 
	}
	
	return 0;
}

保存上面代码,运行gcc mq_posix.c -lrt
运行结果:
在这里插入图片描述

在这里插入图片描述

🎄二、POSIX信号量

✨2.1 POSIX信号量介绍

POSIX信号量和System V信号量的作用是相同的, 都是用于同步进程之间及线程之间的操作, 以达到无冲突地访问共享资源的目的。

POSIX提供了两类信号量: 有名信号量和无名信号量。
无名信号量, 又称为基于内存的信号量, 由于其没有名字, 没法通过open操作直接找到对应的信号量, 所以很难直接用于没有关联的两个进程之间。 无名信号量多用于线程之间的同步
有名信号量由于其有名字, 多个不相干的进程可以通过名字来打开同一个信号量, 从而完成同步操作, 所以有名信号量的操作要方便一些, 适用范围也比无名信号量更广。

有名信号量编程步骤:

  • 1、创建/获取有名信号量
    #include <fcntl.h>
    #include <sys/stat.h>
    #include <semaphore.h>
    sem_t *sem_open(const char *name, int oflag);
    sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
    
    name:可以以1个或多个/打头, 也可以不以/打头。打头的一个或多个/字符不计入长度。最大长度为255个字符;
    oflag:oflag标志位支持的标志包括O_CREAT和O_EXCL标志位。 如果带了O_CREAT标志位,则表示要创建信号量;
    mode:mode设置的是访问权限,创建时有效;
    value:value是新建信号量的初始值,创建时有效。
    sem_t *sem_p = sem_open("/semPosix", O_RDWR|O_CREAT|O_EXCL, 0664, 1);//创建
    sem_t *sem_p = sem_open("/semPosix", O_RDWR); // 获取
    
  • 2、申请该资源
    当申请该资源时, 需要先调用sem_wait函数; 当发布该资源或使用完毕释放该资源时,则调用sem_post函数。
    // 使用资源,数量 -1
    sem_wait(sem_p);
    // 使用资源...
    // 释放资源,数量 +1
    sem_post(sem_p);
    
    可能还有使用下面两个等待信号量的函数:
    int sem_trywait(sem_t *sem);
    int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
    
  • 3、关闭信号量句柄
    sem_close(sem_p);
    
  • 4、删除信号量
    sem_close(sem_p);
    

无名信号量编程步骤和上面的基本差不多,就创建和销毁不一样:

  • 初始化无名信号量
    #include <semaphore.h>
    int sem_init(sem_t *sem, int pshared, unsigned int value);
    
  • 销毁无名信号量
    #include <semaphore.h>
    int sem_destroy(sem_t *sem);
    

✨2.2 例子

// gcc sem_posix.c -lpthread
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <errno.h>
#include <sys/stat.h>

int main()
{
	// 2 创建子进程
	pid_t pid = fork();
    if(pid == 0) {// 子进程
		printf("子进程[%d]开始执行, 创建信号量,使用资源\n", getpid());
		// 创建信号量集
		sem_t *sem_p = sem_open("/semPosix", O_RDWR|O_CREAT|O_EXCL, 0664, 1);
		if(SEM_FAILED == sem_p)
		{
			if (errno == EEXIST)
			{
				printf("/semPosix EEXIST\n");
				sem_unlink("/semPosix");
				sem_p = sem_open("/semPosix", O_RDWR | O_CREAT, 0666, 1);
			}
			else
			{
				perror("sem_open failed");
				exit(-1);
			}
		}
		
		// 使用资源,数量 -1
		sem_wait(sem_p);
		printf("子进程[%d]访问共享资源\n", getpid());
		sleep(20);
		printf("子进程[%d]完成共享资源的访问\n",getpid());
		
		// 释放资源,数量 +1
		sem_post(sem_p);
		sem_close(sem_p);
		
		printf("子进程[%d]退出\n", getpid());
		
        return 0;
    }
	else if(pid > 0)// 父进程
	{
		sleep(3); //延时一会,让子进程先运行
		printf("父进程[%d]开始执行, 获取信号量,准备使用资源\n", getpid()); 
		sem_t *sem_p = sem_open("/semPosix", O_RDWR);
		if(SEM_FAILED == sem_p)
		{
			perror("sem_open failed");
			exit(1);
		}
		
		// 使用资源,数量 -1
		sem_wait(sem_p);
		printf("父进程[%d]访问共享资源\n", getpid());
		sleep(3);
		printf("父进程[%d]完成共享资源的访问\n",getpid());
		
		// 释放资源,数量 +1
		sem_post(sem_p);
		sem_close(sem_p);
		sem_unlink("/semPosix");
		printf("父进程[%d]退出\n", getpid());
        return 0;
	}
	else
	{
		printf("Error in fork\n"); 
        exit(1); 
	}
	
	return 0;
}

注意,编译时需要加-lpthread,保持上面代码,运行gcc sem_posix.c -lpthread 编译,
运行结果如下:
在这里插入图片描述


在这里插入图片描述

🎄三、 POSIX共享内存

✨3.1 POSIX共享内存介绍

Linux系统中,POSIX共享内存的本质是将一个文件通过mmap函数将共享内存映射到进程的地址空间。

编程步骤:

  • 1、创建/获取共享内存句柄
    #include <sys/mman.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    int shm_open(const char *name, int oflag, mode_t mode);
    
    name:可以以1个或多个/打头, 也可以不以/打头。打头的一个或多个/字符不计入长度。最大长度为255个字符;
    oflag:oflag标志位要包含O_RDONLY或O_RDWR,另外,可以选择的标志位还有O_CREAT(表示创建) 、 O_EXCL(配合O_CREAT表示排他创建)、O_TRUNC(表示将共享内存的size截断成0)。
    mode:mode设置的是访问权限;
    int shmid = shm_open("/shmPosix", O_RDWR|O_CREAT|O_EXCL, 0666);//创建
    int shmid = shm_open("/shmPosix", O_RDWR, 0666); // 获取
    
    创建时,需要调用ftruncate(shmid, SHM_SIZE);调整共享内存文件大小。
  • 2、映射共享内存,得到虚拟地址
    使用mmap函数映射共享内存,得到虚拟地址后,可以直接操作。
    void *p = mmap(NULL, SHM_SIZE, PROT_WRITE|PROT_READ, MAP_SHARED, shmid, 0);
    
  • 3、使用后,按需解除映射
    munmap(p, SHM_SIZE);
    
  • 4、销毁共享内存
    shm_unlink("/shmPosix")
    

✨3.2 例子

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>

#define SHM_SIZE	8192

int main()
{
	// 创建子进程
	pid_t pid = fork();
    if(pid == 0) {// 子进程
		printf("子进程[%d]开始执行, 创建共享内存段,使用创建共享内存\n", getpid());
		// 2.1 创建共享内存段
		int shmid = shm_open("/shmPosix", O_RDWR|O_CREAT|O_EXCL, 0666);
		if(shmid == -1)
		{
			perror("shm_open failed");
			exit(1);
		}
		ftruncate(shmid, SHM_SIZE); // 调整文件大小为 8192, 最好为页的整数倍
		
		// 2.2 映射共享内存,得到虚拟地址
		void *p = mmap(NULL, SHM_SIZE, PROT_WRITE|PROT_READ, MAP_SHARED, shmid, 0);
		if((void *)MAP_FAILED == p)
		{
			perror("mmap failed");
			exit(2);
		}
		
		// 2.3 读写共享内存
		int *pi = p;
		*pi = 0xaaaaaaaa;
		*(pi+1) = 0x55555555;
		printf("子进程[%d]写入%x, %x\n", getpid(), *pi, *(pi+1));
		
		// 2.4 解除映射
		if(munmap(p, SHM_SIZE) == -1)
		{
			perror("munmap failed");
			exit(3);
		}
		printf("子进程[%d]解除映射, 结束进程\n\n", getpid());
        return 0;
    }
	else if(pid > 0)// 父进程
	{
		sleep(3); //延时一会,让子进程先运行
		printf("父进程[%d]开始执行, 获取共享内存段,准备使用资源\n", getpid()); 
		// 3.1 获取共享内存段
		int shmid = shm_open("/shmPosix", O_RDWR, 0666);
		if(shmid == -1)
		{
			perror("shm_open failed");
			exit(1);
		}
		
		// 3.2 映射共享内存,得到虚拟地址
		void *p = mmap(NULL, SHM_SIZE, PROT_WRITE|PROT_READ, MAP_SHARED, shmid, 0);
		if((void *)MAP_FAILED == p)
		{
			perror("mmap failed");
			exit(2);
		}
		
		// 3.3 读写共享内存
		int x = *((int *)p);
		int y = *((int *)p + 1);
		printf("父进程[%d]读取数据:x=%#x y=%#x\n",getpid(), x, y);
		
		// 3.4 解除映射
		if(munmap(p, SHM_SIZE) == -1)
		{
			perror("munmap failed");
			exit(3);
		}
		printf("父进程[%d]解除映射\n", getpid());
		
		// 3.5 销毁共享内存
		if( shm_unlink("/shmPosix") == -1)
		{
			perror("shm_unlink");
			exit(4);
		}
		printf("父进程[%d]销毁共享内存, 结束进程\n", getpid());
        return 0;
	}
	else
	{
		printf("Error in fork\n"); 
        exit(1); 
	}
	
	return 0;
}

编译:gcc shm_posix.c -lrt

运行结果:
在这里插入图片描述


在这里插入图片描述

🎄四、信号

信号也可以勉强算作进程间通信的一种方式。信号是一种事件通知机制,当接收到该信号的进程会执行相应的操作。

进程可以通过kill函数给另一进程发送信号,收到信号的进程,对信号的处理有三种方式:忽略、捕捉和默认动作。

关于进程间的信号的,在前面的文章有介绍了,这里不再赘述。可以参看文章:
进程间通信 | 信号 (带C语言例子,8352字详细讲解)


在这里插入图片描述

🎄五、套接字

前面提到的通信方式都是在同一台主机上进行进程间通信,如果想要在不同主机上的进程进行通信,则需要用到socket(套接字)。

Socket可以在不同主机之间的进程进行通信,当然也可以在同一主机的不同进程进行通信。

Socket是操作系统提供给程序员操作网络的接口,根据底层不同的实现方式,通信方式也不同。

关于socket的内容,在后面的文章会介绍,这里先提一下,有个了解,知道它也是进程间通信的重要方式之一就行了。


在这里插入图片描述

🎄六、总结

本文介绍进程间通信的五种方式:POSIX消息队列、POSIX信号量、POSIX共享内存、信号、套接字。
想了解另外5种方式的,可以看上篇文章:
Linux 进程间通信的10种方式(1)

在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

手写一个图形验证码

文章目录 需求分析 需求 使用 JS 写一个验证码&#xff0c;并在前端进行校验 分析 新建文件 VueImageVerify.vue <template><div class"img-verify"><canvas ref"verify" :width"state.width" :height"state.height&qu…

OpenCV-Python(51):基于Haar特征分类器的面部检测

目标 学习了解Haar 特征分类器为基础的面部检测技术将面部检测扩展到眼部检测等。 基础 以Haar 特征分类器为基础的对象检测技术是一种非常有效的对象检测技术(2001 年Paul_Viola 和Michael_Jones 提出)。它是基于机器学习的,通过使用大量的正负样本图像训练得到一个cascade_…

socket以及字节序

1. socket 介绍&#xff1a; 简介&#xff1a; 所谓 socket&#xff08; 套接字&#xff09;&#xff0c;就是对网络中不同主机上的应用进程之间进行双向通信的 端点的抽象。 一个套接字就是网络上进程通信的一端&#xff0c;提供了应用层进程利用网络协议交换数据的机制。从所…

推荐一个还可以的windows ssh工具

1.下载 https://github.com/kingToolbox/WindTerm/releases 2.解压 3.使用 上传 下载都很快 比cmd窗口好用 当然和finalshell有点像

Linux编辑器vim(含vim的配置)

文章目录 前言vim的基本概念vim基本操作进入vim模式切换退出vim vim指令vim命令模式指令vim底行模式命令 简单vim配置 前言 本篇文章&#xff0c;小编将介绍Linux编辑器–>vim以及vim的配置。 vim的基本概念 正常/普通/命令模式(Normal mode) 控制屏幕光标的移动&#xf…

云贝教育 |【分享课】1月25日Oracle分享主题:Oracle 单实例DG

分享主题&#xff1a;Oracle 19c 单实例DG-1 讲师&#xff1a;刘峰 直播时间&#xff1a;1月25日周四19:30 直播平台&#xff1a;微信视频号 云贝学院

(更新)“高铁开通”地级市-多期DID工具变量(2000-2022年)

参照卞元超&#xff08;2019&#xff09;、邓慧慧&#xff08;2020&#xff09;、汪克亮&#xff08;2021&#xff09;等人做法&#xff0c;将开通高铁的城市作为处理组&#xff0c;未开通高铁的城市作为对照组。地级市开通高铁之后的DID赋值为1&#xff0c;未开通则赋值为0 一…

云计算中的出口数据是指什么?

谷歌云&#xff08;Google Cloud&#xff09;近日宣布了一项重大政策变动&#xff0c;决定免除那些选择终止使用其服务并将数据迁移到其他云服务商或本地环境的客户的出口数据费用&#xff08;数据导出费用&#xff09;。 这一举措由谷歌云平台负责人阿米特扎维里&#xff08;A…

docker 基础手册

文章目录 docker 基础手册docker 容器技术镜像与容器容器与虚拟机docker 引擎docker 架构docker 底层技术docker 二进制安装docker 镜像加速docker 相关链接docker 生态 docker 基础手册 docker 容器技术 开源的容器项目&#xff0c;使用 Go 语言开发原意“码头工人”&#x…

SpringBoot责任链与自定义注解:优雅解耦复杂业务

引言 责任链模式是一种行为设计模式&#xff0c;它允许你将请求沿着处理者链进行传递&#xff0c;直到有一个处理者处理请求。在实际应用中&#xff0c;责任链模式常用于解耦发送者和接收者&#xff0c;使得请求可以按照一定的规则被多个处理者依次处理。 首先&#xff0c;本…

【LeetCode】104. 二叉树的最大深度(简单)——代码随想录算法训练营Day16

题目链接&#xff1a;104. 二叉树的最大深度 题目描述 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3示例…

Docker网络配置与自定义IP容器通信

目录 前言 一、docker网络配置 1. bridge 虚拟网桥 2. host 网络模式 3. none 网络模式 4. 自定义container网络模式 二、自定义IP容器通信 1. 自定义IP 2. 创建所需容器&#xff08;mysql&#xff0c;tomcat&#xff09; 3. 准备项目资源 4. 构建Nginx实现负载均衡…

垃圾回收小程序:环保与便捷的完美结合

一、引言 随着科技的发展&#xff0c;移动应用程序已经成为人们日常生活中不可或缺的一部分。其中&#xff0c;废品回收小程序以其独特的价值和功能&#xff0c;日益受到人们的关注和青睐。本文将探讨废品回收小程序开发的重要性、功能特点、技术实现和未来发展趋势。 二、废…

AOP切面

什么是Spring的AOP AOP在spring中又叫“面向切面编程”&#xff0c;它可以说是对传统我们面向对象编程的一个补充&#xff0c;从字面上顾名思义就可以知道&#xff0c;它的主要操作对象就是“切面”&#xff0c;所以我们就可以简单的理解它是贯穿于方法之中&#xff0c;在方法…

springboot家乡特色推荐系统源码和论文

在Internet高速发展的今天&#xff0c;我们生活的各个领域都涉及到计算机的应用&#xff0c;其中包括家乡特色推荐的网络应用&#xff0c;在外国家乡特色推荐系统已经是很普遍的方式&#xff0c;不过国内的管理网站可能还处于起步阶段。家乡特色推荐系统采用java技术&#xff0…

E3 基于Mysql的SQL应用和存储过程

一、实验目的: Mysql平台要求你熟练使用MySQL基本指令&#xff0c;完成对程序的控制与管理&#xff0c;并根据要求写存储过程。 二、实验要求: 1、基本硬件配置:英特尔Pentium III 以上,大于4G内存&#xff1b; 2、软件要求:Mysql&#xff1b; 3、时间:1小时&#xff1b; …

ggplot2 -- x轴相关操作

文章目录 刻度标签倾斜替换x轴刻度标签改变X刻度标签大小及颜色 演示数据集 library(ggplot2)# 示例数据 data <- data.frame(x 1:5,y c(3, 5, 2, 7, 4) ) data # x y #1 1 3 #2 2 5 #3 3 2 #4 4 7 #5 5 4刻度标签倾斜 p1 <- ggplot(data, aes(x x, y y)) geom_bar…

产品解读 | 新一代湖仓集存储,多模型统一架构,高效挖掘数据价值

星环科技TDH一直致力于给用户带来高性能、高可靠的一站式大数据基础平台&#xff0c;满足对海量数据的存储和复杂业务的处理需求。 同时在易用性方面持续深耕&#xff0c;降低用户开发和运维成本&#xff0c;让数据处理平民化&#xff0c;助力用户以更便捷、高效的方式去挖掘数…

【Kafka】Kafka安装:Linux本地和Docker

目录 Linux本地安装kafkajava环境配置Zookeeper的安装配置Kafka的安装与配置生产与消费 Docker安装kafkaZookeeper安装Kafka安装 Linux本地安装kafka java环境配置 1、上传jdk-8u261-linux-x64.rpm到服务器并安装&#xff1a; rpm -ivh jdk-8u261-linux-x64.rpm2、配置环境变…

WorkPlus移动应用管理平台,助力企业实现高效移动办公

在移动办公成为当今工作方式的主流趋势下&#xff0c;管理和运营企业移动应用成为了提高工作效率和数据安全的重要环节。而移动应用管理平台作为实现移动办公高效管理的关键工具&#xff0c;WorkPlus以其领先的性能和全面的功能&#xff0c;助力企业实现高效移动办公。 为何选…