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

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来删除消息队列以完成清理工作.

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-12-24 17:36:36

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

【linux高级程序设计】(第十四章)TCP高级应用

文件I/O方式比较 1.阻塞式文件I/O 进程从调用函数开始,直到返回这段时间都处于阻塞状态. 2.非阻塞式文件I/O 如果当前没有数据可操作,将不阻塞当前进程,而是立即返回一个错误信息.需要反复尝试. 3.多路复用I/O 仍然是阻塞方式等待,但是可以同时等待多个文件描述符. 4.信号驱动I/O 异步方式,等到数据准备好后通知处理进程,不需要重复询问,效率高. I/O阻塞与非阻塞操作 阻塞方式:默认情况下read/write和 把flag设为0的recv/send 非阻塞方式:如果没有数据,立刻

【linux高级程序设计】(第十四章)TCP高级应用 3

控制socket文件描述符属性 1.set/getsockopt()修改socket属性 int getsockopt (int __fd, int __level, int __optname, void *__restrict __optval, socklen_t *__restrict __optlen):获得某个套接字的属性.成功0,失败-1 int setsockopt (int __fd, int __level, int __optname, __const void *__op

linux程序设计——数据报(第十五章)

15.5    数据报 在本章中,重点介绍了如何编写与客户之间维持连接的应用程序.使用面向连接的TCP套接字来完成这一工作.但在某些情况下,在程序中花费时间来建立和维持一个套接字连接是不必要的. 早先,在程序getdate.c中所使用的daytime服务就是一个很好的例子,首先创建一个套接字,然后建立连接,读取一个响应,读取一个响应,最后关闭连接.在这一过程中,使用了很多操作步骤,仅仅为了获取一个日期. daytime服务还可以用数据报通过UUDP来访问.为了访问它,发送一个数据报给该服务,然后

《Linux Device Drivers》第十四章 Linux 设备模型

简介 2.6内核的设备模型提供一个对系统结构的一般性抽象描述,用以支持多种不同的任务 电源管理和系统关机 与用户空间通信 热插拔设备 设备类型 对象生命周期 kobject.kset和子系统 kobject是组成设备模型的基本结构 对象的引用计数 sysfs表述 数据结构关联 热插拔事件处理 kobject基础知识 <linux/kobject.h> 嵌入的kobject 内核代码很少去创建一个单独的kobject对象,kobject用于控制对大型域相关对象的访问 kobject的初始化 首先

linux程序设计——多线程(第十二章)

12.8    多线程 之前,总是让程序的主线程仅仅创建一个线程,这节将演示如何在同一个程序中创建多个线程,然后如何以不同于其启动顺序将它们合并在一起.此外,还演示多线程编程时容易出现的时序问题. 编写程序thread8.c /************************************************************************* > File Name: thread8.c > Description: thread8.c程序创建多个线程,然后以不同

JavaScript高级程序设计学习笔记第十四章--表单

1.在 HTML 中,表单是由<form>元素来表示的,而在 JavaScript 中,表单对应的则是 HTMLFormElement 类型. HTMLFormElement 继承了 HTMLElement,因而与其他 HTML 元素具有相同的默认属性. 2.HTMLFormElement的独特属性和方法: acceptCharset:服务器能够处理的字符集:等价于 HTML 中的 accept-charset 特性. action:接受请求的 URL:等价于 HTML 中的 action 特

RabbitMQ消息队列(十四)-启用SSL安全通讯

如果RabbitMQ服务在内网中,只有内网的应用连接,我们认为这些连接都是安全的,但是个别情况我们需要让RabbitMQ对外提供服务.这种情况有两种解决方案: 在RabbitMQ外层在封装一层应用,应用对外提供服务,本质来说RabbitMQ还是只对内网提供服务.相对更安全,但灵活性差. RabbitMQ直接对外提供服务.这时除了服务本身的安全性还要考虑数据在互联网传输过程中是否可能被拦截破解.业界标准的解决方案就是SSL. 安装Git 1.首先应该安装好必要的依赖包,省得在安装过程中出现各种问题

javascript高级程序设计 第十四章--表单脚本

javascript高级程序设计 第十四章--表单脚本 在HTML中表单由<form>元素表示,在js中表单对应的是HTMLFormElement类型,这个类型也有很多属性和方法:取得表单元素的引用还是为它添加id特性,用DOM操作来获取表单元素:提交表单:把<input>或<button>元素的type特性设置为"submit",图像按钮把<input>元素的type特性设置为"image",也可以调用submit(

JavaScript高级程序设计:第十四章

第十四章 一.表单的基础知识 在HTML中,表单是由<form>元素来表示的,而在javascript中,表单对应的则是HTMLFormElement类型.HTMLFormElement继承了HTMLElement,因而与其他HTML元素具有相同的默认属性.不过,HTMLFormElement也有它自己下列独有的属性和方法. 取得<form>元素的引用方式有好几种.其中最常见的方式就是将它看成与其他元素一样,并为其添加id特性,然后再像下面这样使用getElementById()方