Linux进程间通信之消息队列

17 篇文章 0 订阅
11 篇文章 0 订阅

消息队列的介绍

消息队列与有名管道有许多相似之处,但少了在打开和关闭管道方面的复杂性。但使用消息队列并未解决我们在使用命名管道时遇到的一些问题, 比如管道满时的阻塞问题。
消息队列提供了一种在两个不相关的进程之间传递数据的相当简单且有效的方法。与命名管道相比,消息队列的优势在于,它独立于发送和接收进程而存在,这消除了在同步命名管道的打开和关闭时可能产生的一些困难。
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。而且,每个数据块都被认为含有一个类型, 接收进程可以独立地接收含有不同类型值的数据块。好消息是,我们可以通过发送消息来几乎完全避免命名管道的同步和阻塞问题。更好的是,我们可以用一些方法来提前查看紧急消息。坏消息是:与管道 一样, 每个数据块都有一个最大长度的限制,系统中所有队列所包含的全部数据块的总长度也有一个上限。
Linux 系统有两个宏定义MSGMAX和MSGMNB,它们以字节为单位分别定义了一条消息的最大长度和一个队列的最大长度。其他系统中的这些宏定义可能会不一样或甚至根本就不存在。

消息队列函数

消息队列函数的定义如下:

#include <sys/msg.h>

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
int msgget(key_t key, int msgflg);
int msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int msgtype, intmsgflg);
int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg);

msgget函数

msgget函数的作用是创建和访问一个消息队列:
int msgget(key_t key, int msgflg);
与其他IPC机制一样, 程序必须提供一个键值来命名某个特定的消息队列。特殊键值IPC_PRIVATE用于创建私有队列,从理论上来说,它应该只能被当前进程访问,但同信号量和共享内存的情况一样,消息队列在某些Linux系统中事实上并非私有。由于私有队列没有什么用处,所以这并不是一个很严重的问题。与以前一样, 第二个参数msgflg由9个权限标志组成。
由IPC_CREAT定义的一个特殊位必须和权限标志按位或才能创建一个新的消息队列。在设置IPC_CREAT标志时,如果给出的是一个已有消息队列的键也不会产生错误。如果消息队列已有,则IPC_CREAT标志就被悄悄地忽略掉。
成功时msgget函数返回一个正整数,即队列标识符,失败时返回-1。

msgsnd函数

msgsnd函数的作用是把消息添加到消息队列中:
int msqsnd(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);

消息的结构受到两方面的约束。首先,它的长度必须小于系统规定的上限;其次,它必须以一个长整型成员变量开始,接收函数将用这个成员变量来确定消息的类型。当使用消息时,最好把消息结构定义为下面这样:

struct my_message {
	long int message_type;
	/*The data you wish to transfer */
}

由于在消息的接收中要用到message_ type, 所以你不能忽略它。你必须在声明自己的数据结构时包含它,并且最好将它初始化为一个已知值。
第一个参数msqid是由msgget函数返回的消息队列标识符。
第二个参数msg_ptr是一个指向准备发送消息的指针,消息必须像刚才说的那样以一个长整型成员变量开始。
第三个参数msg_sz是msg_ptr指向的消息的长度。这个长度不能包括长整型消息类型成员变量的长度。
第四个参数msgflg控制在当前消息队列满或队列消息到达系统范围的限制时将要发生的事情。如果msgflg中设置了IPC_NOWAIT标志,函数将立刻返回,不发送消息并且返回值为-1。如果msgflg中的IPC_NOWAIT标志被清除,则发送进程将挂起以等待队列中腾出可用空间。
成功时这个函数返回0,失败时返回-1。如果调用成功,消息数据的一份副本将 被放到消息队列中。

msgrcv函数

msgrcv函数的作用是从一个消息队列中获取消息:
int msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);
第一个参数msqid是由msgget函数返回的消息队列标识符。
第二个参数msg_ptr是一个指向准备接收消息的指针,消息必须像前面msgsnd函数中介绍的那样以一个长整型成员变量开始。
第三个参数msg_sz是msg_ptr指向的消息的长度,它不包括长整型消息类型成员变量的长度。
第四个参数msgtype是一个长整数,它可以实现一种简单形式的接收优先级。如果msgtype的值为0,就获取队列中的第一个可用消息。如果它的值大于零,将获取具有相同消息类型的第一个消息。如果它的值小于零,将获取消息类型等于或小于msgtype的绝对值的第一个消息。
这个函数看起来好像很复杂,但实际应用时很简单。如果只想按照消息发送的顺序来接收它们,就把msgtype设置为0。如果只想获取某一特定类型的消息,就把msgtype设置为相应的类型值。如果想接收类型等于或小于n的消息,就把msgtype设置为-n。
第五个参数msgflg用于控制当队列中没有相应类型的消息可以接收时将发生的事情。如果msgflg中的IPC_NOWAIT标志被设置,函数将会立刻返回,返回值是-1。如果msgflg中的IPC_NOWAIT标志被清除,进程将会挂起以等待一条相应类型的消息到达。
成功时msgrcv函数返回放到接收缓存区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,然后删除消息队列中的对应消息。失败时返回-1。

msgctl函数

这个函数的作用和共享内存的控制函数非常相似:
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msqid_ds结构至少包含以下成员:

struct msqid_ds{
	uid_t msg_perm.uid;
	uid_t msg_perm.gid;
	mode_t msg_perm.mode;
}

第一个参数msqid是由msgget返回的消息队列标识符。
第二个参数command是将要采取的动作。它可以取3个值,如下表所示。

命令说明
IPC_STAT把msqid_ds结构中的数据设置为消息队列的当前关联值
IPC_SET如果进程有足够的权限,就把消息队列的当前关联值设置为msqid_ds结构中给出的值
IPC_RMID删除消息队列

成功时它返回0,失败时返回-1。如果删除消息队列时,某个进程正在msgsnd或msgrcv函数中等待,这两个函数将失败。

编程实例

我们将编写两个程序,msg1.c用于接收消息,msg2.c用于发送消息。我们将允许两个程序都可以创建消息队列,但只要接收者在接收完最后一个消息之后才可以删除它。

msg1.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

#include <sys/msg.h>

struct my_msg_set {
	long int my_msg_type;
	char some_text[BUFSIZ];
};

int main()
{
	int running = 1;
	int msgid;
	struct my_msg_set some_data;
	long int msg_to_receive = 0;

	msgid = msgget((key_t)1234, 0666 | IPC_CREAT);

	if(msgid == -1){
		fprintf(stderr, "msgget failed with error:%d\n",errno);
		exit(EXIT_FAILURE);
	}

	while(running){
		if(msgrcv(msgid, (void*)&some_data, BUFSIZ, msg_to_receive, 0) == -1){
			fprintf(stderr, "msgrcv failed with error:%d\n", errno);
			exit(EXIT_FAILURE);
		}
		printf("You write: %s", some_data.some_text);
		if(strncmp(some_data.some_text,"end",3) == 0){
			running = 0;
		}
	}

	if(msgctl(msgid, IPC_RMID, 0) == -1){
		fprintf(stderr, "msgctl(IPC_RMID) failed\n");
		exit(EXIT_FAILURE);
	}

	exit(EXIT_SUCCESS);
}

msg2.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

#include <sys/msg.h>

#define MAX_TEXT 512

struct my_msg_set {
	long int my_msg_type;
	char some_text[BUFSIZ];
};

int main()
{
	int running = 1;
	int msgid;
	struct my_msg_set some_data;
	long int msg_to_receive = 0;
	char buffer[BUFSIZ];

	msgid = msgget((key_t)1234, 0666 | IPC_CREAT);

	if(msgid == -1){
		fprintf(stderr, "msgget failed with error:%d\n",errno);
		exit(EXIT_FAILURE);
	}

	while(running){
		printf("Enter some text:");
		fgets(buffer, BUFSIZ, stdin);
		some_data.my_msg_type = 1;
		strcpy(some_data.some_text, buffer);

		if(msgsnd(msgid, (void*)&some_data, MAX_TEXT, 0) == -1){
			fprintf(stderr, "msgsnd failed\n");
			exit(EXIT_FAILURE);
		}
		if(strncmp(buffer, "end", 3) == 0){
			running = 0;
		}
	}

	exit(EXIT_SUCCESS);
}

程序分析

和管道不同,这里不再需要由进程自己来提供同步方法。这是消息相对于管道的一个明显优势。
加上消息队列中有空间,发送者可以创建队列,放一些数据到队列中,然后在接收者启动之前就退出。我们将先运行发送者msg2,下面是一个样本输出:

在这里插入图片描述
发送者程序通过msgget来创建一个消息队列,然后用msgsnd向队列中增加消息。接收者用msgget获得消息队列标识符,然后开始接收消息,直到接收到特殊的文本end为止。然后它用msgctl来删除消息队列以完成清理工作。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

_200_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值