您的位置:首页 > 运维架构 > Linux

linux程序设计——消息队列(第十四章)

2015-07-19 22:52 316 查看

14.3 消息队列

这章介绍第三个也是最后一个System V IPC机制;消息队列(message queue).消息队列与命名管道有许多相似之处,但少了在打开和关闭管道方面的复杂性.使用消息队列并未解决在使用命名管道时遇到的一些问题,比如管道满时的阻塞问题.

消息队列提供了一种在两个不相关的进程之间传递数据的相当简单且有效的方法.

与命名管道相比,消息队列的优势在于,它独立与发送和接收进程而存在,这消除了在同步命名管道的打开和关闭时可能产生的一些困难.

消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法.而且,每个数据块都认为含有一个类型,接收进程可以独立地接收含有不同类型值的数据块.


好消息是:可以通过发送消息来几乎完全避免命名管道的同步和阻塞问题.更好的是,可以用一些方法来提前查看紧急消息.

坏消息是:与管道一样,每个数据块都有一个最大长度的限制,系统中所有队列包含的全部数据块的总长度也有一个上限.

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, int msgflg);
int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg);
与信号量和共享内存一样,头文件sys/types.h和sys/ipc.h通常被msg.h自动包含进程序.

14.3.1 msgget函数

函数作用

msgget函数创建和访问一个消息队列

函数原型

int msgget(key_t key, int msgflg);

函数参数

第一个参数key是一个键值,它命名某个特定的消息队列.不相关的进程可以通过它访问同一个消息队列.

第二个参数msgflg由9个权限标志组成.由IPC_CREAT定义的一个特殊位必须和权限标志按位或才能创建一个新的消息队列.

函数返回值

如果成功,则返回一个正整数,即队列标识符.如果失败,则返回-1

14.3.2 msgsnd函数

函数作用

msgsnd函数用来把消息添加到消息队列中.

函数原型

int msgsnd(int msqid, 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.

14.3.3 msgrcv函数

函数作用

msgrcv函数从一个消息队列中获取消息

函数原型

int msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);

函数参数

第一个参数msqid是由message函数返回的消息队列标识符

第二个参数msg_ptr是一个指向准备接收消息的指针,消息必须像前面msgsnd函数介绍的那样以一个长整型成员变量开始.

第三个参数msg_sz是msg_ptr指向的消息的长度,它不包括长整型消息类型成员变量的长度

第四个参数msgtype是一个长整数,它可以实现一种简单形式的接收优先级.如果msgtype的值为0,就获取队列中的第一个可用消息.如果它的值大于0,将获取具有相同消息类型的第一个消息.如果它的值小于0,将获取消息类型等于或小于msgtype的绝对值的第一个消息.

这个函数看起来很复杂,实际应用时很简单.如果只想按照消息发送的顺序来接收它们,就把msgtype设置为0.如果只想获取某一特定类型的消息,就把msgtype设置为相应的类型值.如果想接收类型等于或小于n的消息,就把msgtype设置为-n.

第五个参数msgflg用于控制当队列中没有相应类型的消息可以接收时将要发生的事情.如果msgflg中的IPC_NOWAIT标志被设置,函数就会立刻返回,返回值是-1.如果msgflg中的IPC_NOWAIT标志被清除,进程将会挂起以等待一条相应类型的消息到达.

函数返回值

如果成功,则返回放到接收缓冲区中的字节数(消息被复制到msg_ptr指向的用户分配的缓冲区中,然后删除消息队列中的对应消息).如果失败,则返回-1.

14.3.4 msgctl函数

函数作用

msgctl函数与共享内存的控制函数shmctl非常相似.

函数原型

int msgctl(int msqid, int command, struct msqid_ds *buf);

函数参数

第一个参数msqid是由msgget返回的消息队列标识符

第二个参数command是将要采取的动作,它可以取3个值,如下所示:

命令 说明

IPC_STAT 把msqid_ds结构中的数据设置为消息队列的当前关联值

IPC_SET 如果进程有足够权限,就把消息队列的当前关联值设置为msqid_ds结构中给出的值

IPC_RMID 删除消息队列

第三个参数buf是指向结构msqid_ds的指针.

msqid_ds结构至少包含以下成员:

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

函数返回值

如果成功,则返回0.如果失败,则返回-1.

如果删除消息队列时,某个进程正在msgsnd或msgrcv函数中等待,这两个函数将失败.

实验 消息队列

编写程序msg1.c用于接收消息,msg2.c用于发送消息.

/*************************************************************************
 > File Name:    msg1.c
 > Description:  msg1.c程序用于接收消息
 > Author:       Liubingbing
 > Created Time: 2015年07月19日 星期日 15时28分22秒
 > Other:        msg1.c
 ************************************************************************/

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

#include <sys/msg.h>

/* 消息结构的定义,接收函数用长整型变量my_msg_type来确定消息的类型,some_text是要传递的数据 */
struct my_msg_st {
	long int my_msg_type;
	char some_text[BUFSIZ];
};

int main() 
{
	int running = 1;
	int msgid;
	struct my_msg_st some_data;
	long int msg_to_receive = 0;
	
	/* msgget函数用于创建和访问一个消息队列
	 * 第一个参数是key用来命名某个特定的消息队列,不相关的进程通过它来访问同一个消息队列
	 * 第二个参数是权限标志 
	 * 如果成功,则返回一个正整数,即队列标识符.如果失败,则返回-1 */
	msgid = msgget((key_t)123, 0666 | IPC_CREAT);

	/* 判断消息队列是否创建成功 */
	if (msgid == -1) {
		fprintf(stderr, "msgget failed with error: %d\n", errno);
		exit(EXIT_FAILURE);
	}

	while (running) {
		/* msgrcv函数从一个消息队列中获取消息
		 * 第一个参数是msgget函数返回的消息队列标识符
		 * 第二个参数是一个指向准备接收消息的指针
		 * 第三个参数是指向的消息的长度
		 * 第四个参数是一个长整数,它可以实现一种简单形式的接收优先级,设置为0时,就获取队列中的第一个消息(按照消息发送的顺序接收它们)
		 * 第五个参数用于控制当队列中没有相应类型的消息可以接收时将发生的事情
		 * 如果成功,则返回放到接收缓冲区中的字节数.如果失败,则返回-1*/
		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 wrote: %s", some_data.some_text);
		/* 如果遇到end,则退出循环 */
		if (strncmp(some_data.some_text, "end", 3) == 0) {
			running = 0;
		}
	}

	/* msgctl函数用于控制消息
	 * 第一个参数是msgget函数返回的消息队列标识符
	 * 第二个参数command是将要采取的动作,IPC_RMID表示要删除消息队列
	 * 第三个参数是一个指向msqid_ds结构的指针 
	 * 如果成功,则返回0.如果失败,则返回-1 */
	if (msgctl(msgid, IPC_RMID, 0) == -1) {
		fprintf(stderr, "msgctl(IPC_RMID) failed\n");
		exit(EXIT_FAILURE);
	}

	exit(EXIT_SUCCESS);
}
msg2.c

/*************************************************************************
 > File Name:    msg2.c
 > Description:  msg2.c用于发送消息
 > Author:       Liubingbing
 > Created Time: 2015年07月19日 星期日 16时07分47秒
 > Other:        msg2.c
 ************************************************************************/

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

#include <sys/msg.h>

#define MAX_TEXT 512

struct my_msg_st {
	long int my_msg_type;
	char some_text[MAX_TEXT];
};

int main()
{
	int running = 1;
	struct my_msg_st some_data;
	int msgid;
	char buffer[BUFSIZ];

	/* msgget函数创建或访问消息队列
	 * 第一个参数key命名某个特定的消息队列,不相关的进程通过它来取得相同的消息队列
	 * 第二个参数是权限标志
	 * 如果成功,则返回一个正整数,即队列标识符.如果失败,则返回-1 */
	msgid = msgget((key_t)123, 0666 | IPC_CREAT);

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

	while (running) {
		printf("Enter some text: ");
		/* 从标准输入stdin中读入BUFSIZ个字节的数据到buffer指向的内存 */
		fgets(buffer, BUFSIZ, stdin);
		/* 将标志my_msg_type设置为1,则获取具有相同类型(1)的第一个消息 */
		some_data.my_msg_type = 1;
		/* 从buffer复制数据到some_data.some_text中 */
		strcpy(some_data.some_text, buffer);

		/* msgsnd函数把消息添加到消息队列中
		 * 第一个参数是msgget函数返回的消息队列标识符
		 * 第二个参数是一个指向准备发送消息的指针
		 * 第三个参数是消息的长度
		 * 第四个参数是msgflg控制在当前消息队列满或队列消息到达系统返回的限制时将要发生的事情 
		 * 如果成功,则返回0.如果失败,则返回-1 */
		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.c与msg1.c很相似,在main函数的变量定义部分,删除了对msg_to_receive的定义并把它替换为buffer[BUFSIZ].去掉删除消息队列的语句,在running循环中做如下的改动.通过调用msgsnd来发送用户输入的文本到消息队列中.

与管道例子不同,消息队列不需要进程自己来提供同步方法,这是消息相对管道的一个明显优势.

假设消息队列中有空间,发送者可以创建队列,放一些数据到队列中,然后在接收者启动之前就退出.先运行发送者msg2.如下所示:



程序解析

发送者程序通过msgget来创建一个消息队列,然后用msgsnd向队列中增加消息,接收者用msgget获得消息队列标识符,然后开始接收消息,直到接收到特殊的文本end为止.然后它用msgctl来删除消息队列以完成清理工作.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: