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

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

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


在这里插入图片描述

🎄一、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)

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

相关推荐

  1. linux 进程常见通信方式介绍

    2024-01-27 10:30:08       36 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-27 10:30:08       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-27 10:30:08       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-27 10:30:08       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-27 10:30:08       18 阅读

热门阅读

  1. C# 中的接口

    2024-01-27 10:30:08       32 阅读
  2. 目标检测中目标的尺寸差异大会存在什么问题?

    2024-01-27 10:30:08       38 阅读
  3. Compose中添加Android原生控件

    2024-01-27 10:30:08       43 阅读
  4. vue3中页面传参汇总

    2024-01-27 10:30:08       40 阅读
  5. sql注入之into outfile语句写入一句话木马

    2024-01-27 10:30:08       42 阅读
  6. 我要成为嵌入式高手之1月26日第十一天!!

    2024-01-27 10:30:08       32 阅读
  7. 04_前后端交互技术之Ajax异步请求

    2024-01-27 10:30:08       28 阅读
  8. RuoYi微服务部署运行报错

    2024-01-27 10:30:08       44 阅读
  9. Midjourney:AI创意的新航程

    2024-01-27 10:30:08       42 阅读
  10. Redis学习指南(23)-Redis的分布式集群插槽的分配

    2024-01-27 10:30:08       32 阅读
  11. Jupyter快捷键的使用 --(编辑模式、命令模式)

    2024-01-27 10:30:08       31 阅读