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

news2024/11/16 21:51:58

😁博客主页😁:🚀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)

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

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

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

相关文章

jieba.net使用NuGet管理器安装后初始化TfidfExtractor对象时报错

在引用安装jieba.net后,引用的Resources下只有如图几个文件 导致初始化TfidfExtractor时报错,报找不到 Could not find file E:\\TZKJNet\\robotindustry\\modules\\Tzkj.Superhard.SupplyDemand\\src\\Tzkj.Superhard.SupplyDemand.HttpApi.Host\\bin\\Debug\\net7.0\\Reso…

【C++干货基地】C++入门篇:输入输出流 | 缺省函数 | 函数重载(文末送书)

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引入 哈喽各位铁汁们好啊&#xff0c;我是博主鸽芷咕《C干货基地》是由我的襄阳家乡零食基地有感而发&#xff0c;不知道各位的…

[leetcode] 18. 四数之和

文章目录 题目描述解题方法排序 双指针java代码 相似题目 题目描述 给你一个由 n 个整数组成的数组 nums &#xff0c;和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] &#xff08;若两个四元组元素一一对应&a…

《解锁R统计分析:深度探索R Commander图形界面》

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 在数据科学与大数据技术的浪潮中&#xff0c;R…

Python爬取猫眼电影专业评分数据中的应用案例

在数据分析和可视化展示中&#xff0c;获取准确的电影专业评分数据至关重要。猫眼电影作为中国领先的电影信息与票务平台&#xff0c;其专业评分对于电影行业和影迷的数据来说具有重要意义。通过Python爬虫技术&#xff0c;我们可以实现从猫眼电影网站上自动获取这些数据目标。…

go swagger怎么玩(使用swagger为go项目生成python的SDK)

异常详细&#xff01;所到之处&#xff0c;问题全量解决、你值得拥有&#xff01; 目录 go方面需要做的准备&#xff08;步骤代码&#xff09; 生成对应语言的SDK 生成后怎么调用验证 提示&#xff1a;生成哪种语言的SDK只是本文的其中一步&#xff0c;具体哪种语言可以选择…

数字图像处理(实践篇)二十七 Python-OpenCV 滑动条的使用

目录 1 涉及的函数 2 实践 1 涉及的函数 ⒈ setWindowProperty()用于设置GUI应用程序的属性 cv2.setWindowProperty(windowsName, prop_id, prop_value) 参数: ①

Oracle篇—分区表的管理(第二篇,总共五篇)

☘️博主介绍☘️&#xff1a; ✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ ✌✌️擅长Oracle、MySQL、SQLserver、Linux&#xff0c;也在积极的扩展IT方向的其他知识面✌✌️ ❣️❣️❣️大佬们都喜欢静静的看文章&#xff0c;并且也会默默的点赞收藏加关注❣…

Wordpress seo优化该怎么做?

Wordpress作为开源管理系统&#xff0c;目前已然是世界上最流行的cms之一&#xff0c;这不仅仅因为他开源&#xff0c;对用户友好&#xff0c;让任何人都能轻而易举的制作网站&#xff0c;更是因为这套程序对于搜索引擎非常友好&#xff0c;是做谷歌seo的不二之选 Wordpress作为…

消息队列RabbitMQ.03.死信交换机的讲解与使用

目录 一、死信队列(延迟队列) 概念讲解 二、确认消息&#xff08;局部方法处理消息&#xff09; 三、代码实战 1.编写生产者代码&#xff0c;配置消息、直连交换机、路由键 1.1代码解析&#xff1a; 2.配置消费者接受类接受直连交换机的路由键 2.1. String msg&#xff…

nsenter比docker exec更底层的命令

文章目录 nsenter介绍安装方法简单使用 nsenter介绍 nsenter命令是一个可以在指定进程的命令空间下运行指定程序的命令。它位于util-linux包中。典型的用途就是进入容器的网络命令空间。相当多的容器为了轻量级&#xff0c;是不包含较为基础的命令的&#xff0c;比如说ip addr…

响应式Web开发项目教程(HTML5+CSS3+Bootstrap)第2版 例4-7 datalist

代码 <!doctype html> <html> <head> <meta charset"utf-8"> <title>datalist</title> </head><body> <input id"address" list"addressList"> <datalist id"addressList"…

【数据结构】 链队列的基本操作 (C语言版)

目录 一、链队列 1、链栈的定义&#xff1a; 2、链栈的优缺点&#xff1a; 二、链队列的基本操作算法&#xff08;C语言&#xff09; 1、宏定义 2、创建结构体 3、链栈的初始化 4、链队列的入队 5、链队列的出队 6、取链队列的对头元素 7、链队列的销毁 8、链…

4.列表选择弹窗(CenterListPopup)

愿你出走半生,归来仍是少年&#xff01; 环境&#xff1a;.NET 7、MAUI 在屏幕中间弹窗的列表选择弹窗。 1.布局 <?xml version"1.0" encoding"utf-8" ?> <toolkit:Popup xmlns"http://schemas.microsoft.com/dotnet/2021/maui"x…

【AIGC】Diffusers:扩散模型的开发手册说明2

前言 扩散器被设计成一个用户友好且灵活的工具箱&#xff0c;用于构建适合您用例的扩散系统。工具箱的核心是模型和调度程序。然而 DiffusionPipeline 为方便起见将这些组件捆绑在一起&#xff0c;但您也可以解包管道并分别使用模型和调度程序来创建新的扩散系统。 解构 Stab…

uniapp组件库Modal 模态框 的使用方法

目录 #平台差异说明 #基本使用 #传入富文本内容 #异步关闭 #点击遮罩关闭 #控制模态框宽度 #自定义样式 #缩放效果 #API #Props #Event #Method #Slots 弹出模态框&#xff0c;常用于消息提示、消息确认、在当前页面内完成特定的交互操作。 #平台差异说明 AppH5微…

gin如何实现热更新

什么是热更新&#xff1f; 一种不需要用户关闭应用或重新启动设备就能进行的软件更新技术。它可以快速地在线修复或升级应用程序的错误或功能&#xff0c;从而减少用户的等待时间并提高用户体验。 如何优雅停止服务&#xff1f; Go 1.8版本之后&#xff0c; http.Server 内置…

CentOS使用

1.使用SSH连接操作虚拟机中的CentOS 使用代理软件(MobaX/Xshell)通过ssh连接vmware中的虚拟机,可以摆脱vmware笨重的软件,直接在代理软件中进行操作. 包括使用云虚拟器,其实也只是在本地通过ssh连接别处的云服务商的硬件而已. 1.1 配置静态IP 为什么要配置静态IP? 想要使用…

构建高可用消息队列系统 01

构建高可用消息队列系统 01 引言1. RabbitMQ简介介绍1.1 什么是RabbitMQ1.2 RabbitMQ的核心特性1.3 RabbitMQ与AMQP 2.安装RabbitMQ3.消息队列实践总结 引言 在当今互联网时代&#xff0c;消息队列系统扮演着至关重要的角色&#xff0c;它们被广泛应用于分布式系统、微服务架构…

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

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