什么是消息队列
Linux内核提供的消息队列可以达到一个进程向另外一个进程发送消息的目的,并且消息队列可以根据消息中的类型字段自动为消息归类,接收进程可以通过指定类型字段来接收指定的消息,也可不区分类型接收队首的消息。
消息队列的生命周期和共享内存类似,都是和内核的生命周期相同的,在创建后需要用户显式删除。
消息队列的使用
消息队列相关的函数定义在sys/msg.h
中,其中包含了消息队列的创建、消息发送、消息接收、基本属性的获取和修改、生命周期控制相关的函数,使用起来很像共享内存。我们先从消息队列的创建开始:
消息队列的创建/访问
可通过msgget
函数来创建、获取消息队列:
int msgget(key_t key, int msgflg);
- 第一个参数
key
和共享内存意义相同,即消息队列的名称,key_t
等价于int
数字。 - 第二个参数
msgflg
为消息队列的访问权限(前9位)、IPC_CREATE
或IPC_EXCL
(第10~12位)。类似于Linux文件系统的访问权限,例如666、644(八进制数),可以用来分别限制进程的读取、写入权限。此外还可以添加IPC_CREATE
或IPC_EXCL
字段,IPC_CREAT
表示若存在该msgflg
对应的消息队列,则返回,否则自动创建一个ID为msgflg
的消息队列。IP_EXCL
一般不会单独使用,如果要使用一般来说传入的是IPC_CREAT | IPC_EXCL
,保证返回的共享内存是新构造的。
函数调用完成后会返回一个int
整数,如果大于等于0则表示该整数为消息队列的ID(不等价于传入的key
),如果为-1则创建/获取失败。
消息的发送
int msgsnd(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
- 第一个参数
msgid
为刚才调用msgget
的返回值,代表消息队列的ID。 - 第二个参数
msg_ptr
为指向需要发送的消息的指针,一般为结构体,需要保证的是msg_ptr
到msg_pte + sizeof(long int)
这块区域必须为一个long int
类型的数字,另外一个进程在接收消息时根据此字段来区分不同的消息类型。如果msg_ptr
指向的时结构体,需要保证开头的字段必须是long int
类型:
需要注意typedef struct message { long int msgtype; //Other... } message;
msgtype
的设置一般是大于0 - 第三个参数
msg_sz
是整个消息的长度(不包含开头long int
的类型字段) - 第四个参数
msgflg
用来控制当前消息队列即满时的行为。当取值为IPC_NOWAIT
时,该函数会在消息队列已满时立刻返回-1。如果取值为0,调用线程将被阻塞,直到消息队列可以存放更多的消息或者消息队列被删除时。
调用成功后返回0,调用失败返回-1
消息的接收
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);
- 第一个参数
msgid
意义和msgsnd
相同。 - 第二个参数
msg_ptr
所指向的内存空间用来存储接收的消息,一般传入未经初始化的原消息结构体指针。 - 第三个参数
msg_st
大小和消息发送函数的msg_sz
相同。 - 第四个参数
msgtype
为消息的类型,等价于发送消息时指定的类型字段。但是如果msgtype
为0,就会接收队列中第一个消息,不区分msgtype
。如果小于0,就会接收小于等于-msgtype
的最小消息类型的第一个消息。 - 第五个参数
msgflg
用于控制消息队列为空时的行为,可取值为0或IPC_NOWAIT
,当msgflg
为0时,调用线程将被阻塞,直到所需类型的消息已经到达消息队列,或者消息队列被删除(此时errno
会被设置)。如果设置为IPC_NOWAIT
,那么当消息队列没有消息时,函数会立刻返回-1。
成功收取消息后,该函数会返回接收到的字节数,消息会被剪切到msg_ptr
所指向的内存空间。如果失败则返回-1
消息队列的基本属性获取、生命周期控制
msgctl
函数同时具备消息队列的基本属性获取、生命周期控制的功能:
int msgctl(int msgid, int command, struct msgid_ds *buf);
- 第一个参数
msgid
意义和msgsnd
相同。 - 第二个参数
command
是对消息队列采取的行为,其取值范围包含:
IPC_STAT
:获取消息队列的基本属性,就是将消息队列的基本属性拷贝到buf
所指向的内存空间
IPC_SET
:将buf
参数所指向的结构设为该消息队列的基本属性
IPC_RMID
:删除当前消息队列 - 第三个参数
buf
指向消息队列的基本属性结构,结构体msgid_ds
定义如下:struct msqid_ds { struct ipc_perm msg_perm; /* 消息队列读写权限 */ __time_t msg_stime; /* 最后一次接收消息的时间戳 */ #ifndef __x86_64__ unsigned long int __unused1; #endif __time_t msg_rtime; /* 最后一次接收消息的时间戳 */ #ifndef __x86_64__ unsigned long int __unused2; #endif __time_t msg_ctime; /* 消息队列最后一次更新的时间 */ #ifndef __x86_64__ unsigned long int __unused3; #endif __syscall_ulong_t __msg_cbytes; /* 当前消息队列中存储消息已使用的空间 */ msgqnum_t msg_qnum; /* 当前消息队列持有的消息 */ msglen_t msg_qbytes; /* 消息队列存储的消息所占用的最大字节数 */ __pid_t msg_lspid; /* 最新发送消息的进程PID */ __pid_t msg_lrpid; /* 最新接收消息的进程PID */ __syscall_ulong_t __unused4; __syscall_ulong_t __unused5; };
程序示例
首先需要定义消息的结构:
msgdef.h
#define MSG_LENGTH 1024
typedef struct message {
long type;
char content[MSG_LENGTH];
} msg;
下面我们来实现消息的生产者:
#include <sys/msg.h>
#include <stdio.h>
#include "msgdef.h"
int main() {
const key_t key = 0xABCD;
int id = msgget(key, IPC_CREAT | 0666);
if (id == -1) {
fprintf(stderr, "can not create or get message queue\n");
return 1;
}
msg buf; //消息缓冲区
buf.type = 0x10;
while (1) {
fprintf(stdout, "Send message:");
fflush(stdout);
if (fscanf(stdin, "%s", buf.content) == -1) { //Ctrl+D退出程序
break;
}
if (msgsnd(id, &buf, MSG_LENGTH, 0) == 0) {
fprintf(stdout, "Send success\n");
} else {
fprintf(stderr, "Send failure\n");
}
}
//程序退出时删除消息队列
msgctl(id, IPC_RMID, NULL);
return 0;
}
消息消费者:
#include <stdio.h>
#include <sys/msg.h>
#include <unistd.h>
#include "msgdef.h"
int main() {
const key_t key = 0xABCD;
int id = msgget(key, IPC_CREAT | 0666);
if (id == -1) {
fprintf(stderr, "can not create or get message queue\n");
return 1;
}
struct msqid_ds msqds; //消息队列的属性
msg buf; //接收消息缓冲区
while (1) {
if (msgctl(id, IPC_STAT, &msqds) == -1) { //如果返回-1说明消息队列已被删除,退出程序即可
break;
}
if (msqds.msg_qnum > 0) { //如果有可接收的消息
int i;
for(i = 0; i < msqds.msg_qnum; i++) {
if (msgrcv(id, &buf, MSG_LENGTH, 0x10, 0) != -1) {
fprintf(stdout, "Receive message: %s\n", buf.content);
} else {
fprintf(stderr, "Receive message failure\n");
}
}
} else {
usleep(50 * 1000); //没有消息可读时睡眠50毫秒,防止占用过多CPU时间
}
}
return 0;
}
gcc编译上述两个程序,打开两个终端,分别执行消费者和生产者:
生产者:
[root@bogon queue]# ./snd
Send message:abcdefg
Send success
Send message:123456
Send success
Send message:pppppp
Send success
Send message:[root@bogon queue]#
消费者:
[root@bogon queue]# ./recv
Receive message: abcdefg
Receive message: 123456
Receive message: pppppp
[root@bogon queue]#
当生产者按下Ctrl + D
时程序退出,并删除消息队列。消费者此时无法获得消息队列的状态后也会退出程序。