消息队列实现进程间通信

本文将采用多线程的方式,实现:

A终端发送消息,B终端接收;B终端发送消息,A终端接收。

同时A、B终端都可以连续发送消息,且被另一个终端实时接收

任意一个终端输入quit(不区分大小写),可同时退出两个进程

首先,先创建消息包结构体变量

//消息包结构体
struct msgbuf
{
	long mtype;   //消息类型,必须大于0
	char mtext[128];   //消息内容,可以自行制定类型、大小
};

因为消息队列不同于管道,它可以通过修改消息类型,做到单个消息队列互不干扰的处理两个进程通信。例如:A进程用1类型消息发送给B,用2类型接收B的消息;而B进程用1类型接收消息,用2类型发送消息。所以,我只需要创建一个消息队列即可。

//计算key值
key_t key = ftok("./", 1);  //key值由路径和自己指定的非0值唯一生成
if(key < 0)
{
	perror("ftok");
	return -1;
}
printf("key = %#x\n", key);


//创建队列
msqid = msgget(key, IPC_CREAT | 0755);  //队列1
if(msqid < 0)
{
	perror("msgget");
	return -1;
}
printf("msqid = %d\n", msqid);

为了不让主线程看着太过臃肿,我创建两个分支线程分别用来处理读和写(这样看着对称,个人习惯罢了)。

    /*******************************************************************/
	
	//创建线程
	if(pthread_create(&tid1, NULL, my_read, NULL) != 0)  //读线程
	{
		perror("pthread_create");
		return -1;
	}
	
    
    if(pthread_create(&tid2, NULL, my_write, NULL) != 0)  //写线程
	{
		perror("pthread_create");
		return -1;
	}

	/******************************************************************/

因为题目要求读到quit就退出程序。假设A写入了quit,此时,B会读取到quit,所以我们必须要留一个read线程退出的语句,而A读取到输入了quit后,可以直接申请read线程退出,read线程退出后自己再退出。所以我在主线程只阻塞了read线程。

pthread_join(tid1, NULL);//阻塞读线程

//读线程退出后,删除消息队列
msgctl(msqid, IPC_RMID, NULL);

为了让write线程可以申请read线程退出,要把read线程号(tie1)传参给write线程,同理,消息队列的名字也要传参给read和write线程,这就要用到结构体了,我图省事,把它都定义成了全局变量。

pthread_t tid1, tid2;
int msqid;

以A进程为例。A进程的read线程用于接收B进程的write线程发送的消息,我让A进程读取1类型,让B进程读取2类型,以阻塞的方式读取消息,当它接受的B进程的write线程发送来的quit,退出循环,即退出线程,此时,主线程接收到read线程的“尸体”,结束堵塞,同时删除队列结束程序(删库跑路(雾))。

在具体运行代码时,A进程的read线程读取消息并输出后,会立刻进入循环,并阻塞在读取消息的代码处。但是与此同时,由于A进程的write线程早就运行过 printf("请输入消息内容>>>"); 代码段,所以会导致输出信息后下一行会是一片空白(输出语句后的\n)。所以,出于美观性的考虑,我们可以在read线程输出后的下一行,补充一句 printf("请输入消息内容>>>"); 语句,代码运行会相对更加美观

void *my_read(void *arg)
{	//接收消息
	struct msgbuf rcv;   //用相同类型接收消息
	ssize_t res = 0;

	while(1)
	{
		//阻塞的方式读取消息队列中的消息,msgtyp==0,先进先出		
		res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 1, 0);  //读取1号类型
		if(res <0)
		{
			perror("msgrcv");
			return NULL;
		}
		if(strcasecmp(rcv.mtext, "quit") == 0)
		{
			fprintf(stderr,"程序已退出\n");
			break;
		}
		fprintf(stderr,"\n消息包大小:%ld [%ld信道消息: %s]\n",res, rcv.mtype, rcv.mtext);
		//美观性考虑,当进程A重复输入时,若无此语句,会空出一行
		fprintf(stderr,"请输入消息内容>>>");
	}

}

A进程的write线程,发送2类型消息,以阻塞方式发送。当输入quit时,直接用pthread_cancel(tid1); 函数申请read线程退出,同时,直接break或进入死循环,不然会再输出一句printf("请输入消息内容>>>");语句,影响美观性

void *my_write(void *arg)
{
	//发送消息包
	struct msgbuf snd;
	snd.mtype = 2;   //消息类型2号
	while(1)
	{
		printf("请输入消息内容>>>");
		fgets(snd.mtext, sizeof(snd.mtext), stdin);
		snd.mtext[strlen(snd.mtext)-1] = 0;

		//阻塞方式发送,消息队列满了,阻塞
		if(msgsnd(  msqid,        &snd,         sizeof(snd.mtext),         0) < 0) 
		{//       消息队列id, 消息包首地址,    消息包内消息内容大小   阻塞方式发送
			perror("msgsnd");
			return NULL;
		}
		if(strcasecmp(snd.mtext, "quit") == 0)
		{
			pthread_cancel(tid1);
			printf("程序已退出\n");
			while(1);         //防止重复输出“请输入消息内容”
		}
	}
}

最后,当你使用Ubuntu的gcc编译器进行编译时,要注意,需要在最后链接pthread

gcc MAG_queue_A.c -o A -pthread

// MAG_queue_A.c  原文件名
// -o             重命名标识符
// A              重命名后的可执行文件名
// -pthread       链接库
//讲道理,这种东西我觉得不需要我赘述

以下是A进程完整代码,注释掉的部分为双消息队列的方案

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

#define MSG_EXCEPT  020000
int msqid/*,msqid_2*/;

pthread_t tid1, tid2;

//消息包结构体
struct msgbuf
{
	long mtype;   //消息类型,必须大于0
	char mtext[128];   //消息内容,可以自行制定
};

void *my_read(void *arg)
{	//接收消息
	struct msgbuf rcv;   //用相同类型接收消息
	ssize_t res = 0;

	while(1)
	{
		//阻塞的方式读取消息队列中的消息,msgtyp==0,先进先出		
		res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 1, 0);  //读取1号类型
		if(res <0)
		{
			perror("msgrcv");
			return NULL;
		}
		if(strcasecmp(rcv.mtext, "quit") == 0)
		{
			fprintf(stderr,"程序已退出\n");
			break;
		}
		fprintf(stderr,"\n消息包大小:%ld [%ld信道消息: %s]\n",res, rcv.mtype, rcv.mtext);
		//美观性考虑,当进程A重复输入时,若无此语句,会空出一行
		fprintf(stderr,"请输入消息内容>>>");
	}

}

void *my_write(void *arg)
{
	//发送消息包
	struct msgbuf snd;
	snd.mtype = 2;   //消息类型2号
	while(1)
	{
		printf("请输入消息内容>>>");
		fgets(snd.mtext, sizeof(snd.mtext), stdin);
		snd.mtext[strlen(snd.mtext)-1] = 0;

		//阻塞方式发送,消息队列满了,阻塞
		if(msgsnd(  msqid,       &snd,           sizeof(snd.mtext),       0) < 0) 
		{//       消息队列id, 消息包首地址,    消息包内消息内容大小   阻塞方式发送
			perror("msgsnd");
			return NULL;
		}
		if(strcasecmp(snd.mtext, "quit") == 0)
		{
			pthread_cancel(tid1);
			printf("程序已退出\n");
			while(1);         //防止重复输出“请输入消息内容”
		}
	}
}


int main(int argc, const char *argv[])
{
	//计算key值********************************************************
/*	key_t key_2 = ftok("./", 2);  //key值由路径和自己指定的非0值唯一生成
	if(key_2 < 0)
	{
		perror("ftok");
		return -1;
	}
	printf("key_2 = %#x\n", key_2);
*/	
	key_t key = ftok("./", 1);  //key
	if(key < 0)
	{
		perror("ftok");
		return -1;
	}
	printf("%#x\n", key);
	/**********************************************************************/

	//创建队列
/*	msqid_2 = msgget(key_2, IPC_CREAT | 0755);   //二号队列
	if(msqid_2 < 0)
	{
		perror("msgget");
		return -1;
	}
	printf("msqid_2 = %d\n", msqid_2);
*/
	msqid = msgget(key, IPC_CREAT | 0755);  //一号队列
	if(msqid < 0)
	{
		perror("msgget");
		return -1;
	}
	printf("msqid = %d\n", msqid);
/***********************************************************************/
	
	//创建线程
	if(pthread_create(&tid1, NULL, my_read, NULL) != 0)  //读
	{
		perror("pthread_create");
		return -1;
	}
	if(pthread_create(&tid2, NULL, my_write, NULL) != 0)  //写
	{
		perror("pthread_create");
		return -1;
	}

/************************************************************************/	
	pthread_join(tid1, NULL);//阻塞读
	//	pthread_join(tid2, NULL);
	//删除消息队列
	msgctl(msqid, IPC_RMID, NULL);
//	msgctl(msqid_2, IPC_RMID, NULL);
//	system("ipcs -q");
	return 0;
}

B进程完整代码

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

#define  MSG_EXCEPT  020000
int msqid/*,msqid_2*/;

pthread_t tid1, tid2;
//消息包结构体
struct msgbuf
{
	long mtype;   //消息类型,必须大于0
	char mtext[128];   //消息内容,可以自行制定
};

void *my_read(void *arg)
{
	//接收消息
	struct msgbuf rcv;   //用相同类型接收消息
	ssize_t res = 0;

	while(1)
	{
		//阻塞的方式读取消息队列中的消息,msgtyp==0,先进先出		
		res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 2, 0);  //用2号类型读取
		if(res <0)
		{
			perror("msgrcv");
			return NULL;
		}
		if(strcasecmp(rcv.mtext, "quit") == 0)
		{
			fprintf(stderr,"程序已退出\n");
			break;
		}
		fprintf(stderr,"\n消息包大小:%ld [%ld信道消息: %s]\n",res, rcv.mtype, rcv.mtext);
		//美观性考虑,当进程A重复输入时,若无此语句,会空出一行
		fprintf(stderr,"请输入消息内容>>>");
	}
}

void *my_write(void *arg)
{
	//发送消息包
	struct msgbuf snd;
	snd.mtype = 1;   //消息类型1号
	while(1)
	{
		printf("请输入消息内容>>>");
		fgets(snd.mtext, sizeof(snd.mtext), stdin);
		snd.mtext[strlen(snd.mtext)-1] = 0;
		//阻塞方式发送,消息队列满了,阻塞
		if(msgsnd(  msqid,       &snd,          sizeof(snd.mtext),        0) < 0) 
		{//       消息队列id, 消息包首地址,    消息包内消息内容大小   阻塞方式发送
			perror("msgsnd");
			return NULL;
		}
		if(strcasecmp(snd.mtext, "quit") == 0)
		{
			pthread_cancel(tid1);
			printf("程序已退出\n");
			while(1);	//防止重复输出“请输入消息内容”
		}
	}
}


int main(int argc, const char *argv[])
{
	//计算key值**********************************************************
	key_t key = ftok("./", 1);  //key值由路径和自己指定的非0值唯一生成
	if(key < 0)
	{
		perror("ftok");
		return -1;
	}
	printf("key = %#x\n", key);
	
/*	key_t key_2 = ftok("./", 2);  //key_2
	if(key_2 < 0)
	{
		perror("ftok");
		return -1;
	}
	printf("%#x\n", key_2);
*/	/*******************************************************************/

	//创建队列
	msqid = msgget(key, IPC_CREAT | 0755);  //一号队列
	if(msqid < 0)
	{
		perror("msgget");
		return -1;
	}
	printf("msqid = %d\n", msqid);
	
/*	msqid_2 = msgget(key_2, IPC_CREAT | 0755);   //二号队列
	if(msqid_2 < 0)
	{
		perror("msgget");
		return -1;
	}
	printf("msqid_2 = %d\n", msqid_2);
*/ /*******************************************************************/
	
	//创建线程
	if(pthread_create(&tid1, NULL, my_read, NULL) != 0)  //读
	{
		perror("pthread_create");
		return -1;
	}
	if(pthread_create(&tid2, NULL, my_write, NULL) != 0)  //写
	{
		perror("pthread_create");
		return -1;
	}

	/**********************************************************************/
	pthread_join(tid1, NULL);//阻塞读
	//	pthread_join(tid2, NULL);
	//删除消息队列
//	msgctl(msqid_2, IPC_RMID, NULL);
	msgctl(msqid, IPC_RMID, NULL);
//	system("ipcs -q");
	return 0;
}

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老K殿下

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值