【Linux】利用消息队列实现一个简单的进程间双向通信(两种方式)_进程间通信选择一个队列还是两个队列

收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
img
img

如果你需要这些资料,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

实际上,第二个参数还需要与文件权限一起使用,如IPC_CREAT|00666表示若内核中不存在指定队列则创建它,同时进程可以对队列消息进行读写操作。
简单点儿说,就是第一个用来找到队列,第二个则是定义相关的权限及操作。

发送消息msgsnd()函数

当我们通过msgget()函数得到了队列标识符,我们就可以在对应的消息队列上来执行相关的读写操作了,如果我们要发送消息,则需要用到的就是msgsnd()函数,它的原型如下所示:

# include <sys/types.h>
# include <sys/ipc.h>
# include <sys/msg,h>
int msgsnd(int msqid, const void \*msgp, size_t msgsz, int msgflg);

其实从命名就可以大概看出来,第一个参数是消息队列的id,message queue id,即用来告诉系统向哪个消息队列发送消息;
第二个参数是message pointer,是一个空型指针,这个但从命名似乎看不太出来其意义,但是根据刚刚画的那个小模型,应该需要用到消息缓冲区,其实这个指针就是指向消息缓冲区的;
第三个参数是message size,顾名思义,就是消息的长度,它是以字节为单位的,注意,这里的大小单纯指消息的大小,并不含消息类型的大小;
第四个参数是message flag,它通常取0,也就是忽略它,也可以设置成IPC_NOWAIT,如果设置成后者,也就是不等待,即消息队列满了的话就不等了,今天你爱搭不理,明天我高攀不起,若不指定的话,则会阻塞等待,直到可以写入为止。

接受消息msgrcv()函数

同样的道理,当我们获取了队列标识符后,也可以通过msgrcv()函数来在指定消息队列上接受消息,其函数原型如下:

# include <sys/types.h>
# include <sys/ipc.h>
# include <sys/msg,h>
int msgrcv(int msqid, void \*msgp, size_t msgsz,  long msgtyp, int msgflg);

前三个参数和上述发送函数msgsnd()函数的参数作用相同
最后一个参数作用也是一样的。
而第四个参数则指定要从队列中获取的消息类型,若取0,则不管是什么类型都接收。

利用消息队列实现一个简单的进程间通信

在介绍完上述概念之后,我们可以知道每个消息队列都有一个独特的IPC值,而每个消息队列内,可以有不同类型的消息进行传递。
而进程在收发消息时,都需要借助一个消息缓冲区进行。
故而单向通信只要保证不同进程读写的是同一个消息队列(key值相同),并且收发同一种消息类型即可。
双向通信则可以通过以下两种基本方式进行:

  1. 创建两个消息队列来进行通信
    根据之前的简单模型,我们可以画出使用这种方式进行双向通信的基本示意图:
    在这里插入图片描述
    也就是说,两个不同的进程需要使用两个对应的key来调用msgget()函数。
    这里直接po代码和注释:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <unistd.h>
#include <sys/ipc.h>

int main(){
	int ret = -1;
	int msg_flags, smsg_id ,rmsg_id;/\*创建消息队列函数所用的标志位,以及收消息与发消息的队列id\*/
	key_t key1,key2;/\*队列的键值\*/
	struct msgmbuf{/\*消息的缓冲区结构\*/
		long mtype; // 注意long与int的字长问题
		char mtext[10];
		};
	struct msgmbuf msg_mbuf;/\*创建消息缓冲区\*/
	
	int msg_sflags,msg_rflags;/\*收发消息函数所用的标志位\*/
	char \*msgpath1 = "/usr/bin";/\*消息key1产生所用的路径\*/
	char \*msgpath2 = "/usr/bin";/\*消息key2产生所用的路径\*/
	key1 = ftok(msgpath1,'b');/\*产生key1\*/
	key2 = ftok(msgpath2,'a');/\*产生key2\*/
	if(key1 != -1 || key2 != -1)/\*产生key成功\*/
	{
		printf("成功建立KEY\n");		
	}
	else/\*产生key失败\*/
	{
		printf("建立KEY失败\n");		
	}
	msg_flags = IPC_CREAT;//|IPC\_EXCL; /\*设置创建消息的标志位\*/
	smsg_id = msgget(key1, msg_flags|0666); /\*建立收消息的消息队列\*/
	rmsg_id = msgget(key2, msg_flags|0666);	/\*建立发消息的消息队列\*/
	if( -1 == smsg_id || -1 == rmsg_id)
	{
		printf("消息建立失败\n");
		return 0;		
	}	

	pid_t pid;
	pid = fork();/\*通过fork()创建子进程,主进程进行发消息,子进程进行收消息\*/

	while(1){
		if(pid != 0){/\*主进程\*/
		msg_sflags = IPC_NOWAIT;/\*当消息队列满了的时候不等待\*/
		msg_mbuf.mtype = 10;/\*设置发送的消息类型\*/
		sleep(1);
		char \*content;
		content = (char\*)malloc(10\*sizeof(char));
		printf("input:\n");
		scanf("%s",content);/\*用户输入内容\*/
		if(strncmp(content,"end",3) == 0)/\*如果前三个字符为end,则跳出循环\*/
			break;
	
		memcpy(msg_mbuf.mtext,content,10);/\*复制字符串\*/
		ret = msgsnd(smsg_id, &msg_mbuf, 10, msg_sflags);/\*发送消息\*/
		if( -1 == ret)
		{
			printf("发送消息失败\n");		
		}
		}
		else{/\*子进程\*/
			sleep(1);
			msg_mbuf.mtype = 10;/\*设置收消息的类型\*/
			msg_rflags = IPC_NOWAIT;//|MSG\_NOERROR;
			ret = msgrcv(rmsg_id, &msg_mbuf,10,10,msg_rflags);/\*接收消息\*/
			if( -1 == ret)
			{
				/\*可添加出错处理等\*/
			}
			else
			{
				printf("接收消息成功,长度:%d\n",ret);	
				printf("content:%s\n",msg_mbuf.mtext);	
			}
		
		}
	}

	ret = msgctl(rmsg_id, IPC_RMID,NULL);/\*删除收消息的队列\*/
	if(-1 == ret)
	{
		printf("删除消息失败\n");
		return 0;		
	}
	return 0;
}

上述代码是其中一个文件所使用的,而另一个只需要将收发消息的队列颠倒即可(将key值交换)。
2. 通过创建不同的消息类型来进行双向通信
这种方式是指在同一个消息队列中,使用不同的消息类型来标识收发信息,基本示意图如下:
在这里插入图片描述
图中不同颜色的信息表示不同的消息类型。这就要求在发送信息时将消息缓冲区中的类型值设置好,在收信息时则要在msgrcv()函数中的第四个参数匹配发送方的消息类型。
代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <unistd.h>
#include <sys/ipc.h>

int main(){
	int ret = -1;
	int msg_flags, msg_id;/\*创建消息队列函数所用的标志位以及消息队列的id号\*/
	key_t key;/\*队列的键值\*/
	struct msgmbuf{/\*消息的缓冲区结构\*/
		long mtype; // 注意long与int类型的字长问题
		char mtext[10];
		};
	struct msgmbuf msg_mbuf;/\*创建消息缓冲区\*/
	
	int msg_sflags,msg_rflags;/\*收发消息函数所用的标志位\*/
	char \*msgpath = "/usr/bin/";/\*消息key产生所用的路径\*/
    key = ftok(msgpath,'b');/\*产生key\*/
	if(key != -1)/\*产生key成功\*/
	{
		printf("成功建立KEY\n");		
	}
	else/\*产生key失败\*/
	{
		printf("建立KEY失败\n");		
	}
	msg_flags = IPC_CREAT;//|IPC\_EXCL; /\*设置创建消息的标志位\*/
	msg_id = msgget(key, msg_flags|0666);	/\*建立消息队列\*/
	if( -1 == msg_id )
	{
		printf("消息建立失败\n");
		return 0;		
	}	

	pid_t pid;
	pid = fork();/\*通过fork()创建子进程,主进程进行发消息,子进程进行收消息\*/

	while(1){
		if(pid != 0){/\*主进程\*/
		msg_sflags = IPC_NOWAIT;
		msg_mbuf.mtype = 10;/\*发送消息的类型为10,另一个进程收消息的类型应为10\*/
		sleep(1);
		char \*content;
		content = (char\*)malloc(10\*sizeof(char));
		printf("input:\n");
		scanf("%s",content);/\*用户输入内容\*/
		if(strncmp(content,"end",3) == 0)/\*如果前三个字符为end,则跳出循环\*/
			break;
	
		memcpy(msg_mbuf.mtext,content,10);/\*复制字符串\*/
		ret = msgsnd(msg_id, &msg_mbuf, 10, msg_sflags);/\*发送消息\*/
		if( -1 == ret)
		{
			printf("发送消息失败\n");		
		}
		}
		else{/\*子进程\*/
			sleep(1);
			msg_mbuf.mtype = 11;/\*收消息的类型为11,另一个进程发消息的类型应为11\*/
			msg_rflags = IPC_NOWAIT;//|MSG\_NOERROR;
			ret = msgrcv(msg_id, &msg_mbuf,10,11,msg_rflags);/\*接收消息\*/
			if( -1 == ret)
			{
				/\*可添加出错处理等\*/
			}
			else
			{
				printf("接收消息成功,长度:%d\n",ret);	
				printf("content:%s\n",msg_mbuf.mtext);	
			}
		
		}
	}


	ret = msgctl(msg_id, IPC_RMID,NULL);/\*删除消息队列\*/
	if(-1 == ret)
	{
		printf("删除消息失败\n");
		return 0;		
	}
	return 0;
}

同样的,这里只是其中一个文件的代码,而另一个将对应的收发信息的类型颠倒即可。
最终两者实现的效果是相同的:
在这里插入图片描述

从上面的代码我们可以观察到一个没有提到的函数msgctl()函数,它是用来在消息队列上执行控制操作,如获取队列的基本情况,设置消息队列状态以及删除队列等。
在了解msgctl()函数之前,应该先了解一下消息队列的基本结构,实际上,每个消息队列都有相应的数据结构来记录其信息:

struct msqid_ds {


![img](https://img-blog.csdnimg.cn/img_convert/a312afcb5e3d5a14e7d388bff061681e.png)
![img](https://img-blog.csdnimg.cn/img_convert/80350ad7f5aaf0eb039ec8b1b6de6255.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**

7UsKE-1715689883787)]
[外链图片转存中...(img-I3uKssxO-1715689883787)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**

  • 8
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值