消息队列是在两个进程之间传递二进制块数据的一种简单有效的方式。每个数据块都有一个特定的类型,接收方可以根据类型来有选择地接收数据,而不一定像管道和命名管道那样必须以先进先出的方式接收数据。
一、创建消息队列
创建一个消息队列或者获取一个已经存在的消息队列
#include <sys/msg.h>int msgget(key_t key, int msgflg);
-
key
参数是一个键值,用来标识一个全局唯一的消息队列。 -
msgflg
参数是一组标志,用于指定消息队列的创建方式和权限。常见的标志包括IPC_CREAT
:如果消息队列不存在则创建它IPC_EXCL
:与IPC_CREAT
一起使用,确保只在消息队列不存在时创建它- 权限标志:例如
IPC_PRIVATE
,表示创建一个私有的消息队列
-
成功时返回一个正整数值,它是消息队列的标识符。msgget失败时返回-1,并设置errno。‘
如果msgget用于创建消息队列,则与之关联的内核数据结构msqid_ds
将被创建并初始化。msqid_ds
结构体的定义如下:
struct msqid_ds {struct ipc_perm msg_perm;/*消息队列的操作权限*/time_t msg_stime;/*最后一次调用msgsnd的时间*/time_t msg_rtime;/*最后一次调用msgrcv的时间*/time_t msg_ctime;/*最后一次被修改的时间*/unsigned long __msg_cbytes;/*消息队列中已有的字节数*/msgqnum_t msg_qnum;/*消息队列中已有的消息数*/msglen_t msg_qbytes;/*消息队列允许的最大字节数*/pid_t msg_lspid;/*最后执行msgsnd的进程的PID*/pid_t msg_lrpid;/*最后执行msgrcv的进程的PID*/
};
二、添加消息
把一条消息添加到消息队列中
#include <sys/msg.h>int msgsnd(int msqid, const void* msg_ptr, size_t msg_sz, int msgflg);
-
msqid
参数是由msgget调用返回的消息队列标识符。 -
msg_ptr
参数指向一个准备发送的消息,消息必须被定义为如下类型:
struct msgbuf {long mtype;/*消息类型*/char mtext[512];/*消息数据*/
};
-
mtype
指定消息类型,必须是一个正整数。mtext
是数据
msg_sz
是数据的长度,这个长度可以为0,表示没有数据msgflg
参数控制msgsnd的行为。它通常仅支持IPC_NOWAIT标志,即以非阻塞的方式发送消息。并设置errno为EAGAIN。
处于阻塞状态的msgsnd调用可能被如下两种异常情况所中断:
- 消息队列被移除。此时msgsnd调用将立即返回并设置errno为EIDRM。
- 程序接收到信号。此时msgsnd调用将立即返回并设置errno为EINTR。
三、读取消息
从消息队列中获取消息
#include <sys/msg.h>int msgrcv(int msqid, void* msg_ptr, size_t msg_sz, long int msgtype, int msgflg);
-
msqid
参数是由msgget调用返回的消息队列标识符。 -
msg_ptr
参数用于存储接收的消息,msg_sz参数指的是消息数据部分的长度。 -
msgtype
参数指定接收何种类型的消息。我们可以使用如下几种方式来指定消息类型:msgtype
等于0。读取消息队列中的第一个消息。msgtype
大于0。读取消息队列中第一个类型为msgtype
的消息。(除非指定了标志MSG_EXCEPT)msgtype
小于0。读取消息队列中第一个类型值比msgtype
的绝对值小的消息。
-
msgflg
控制msgrcv函数的行为。它可以是如下一些标志的按位或:IPC_NOWAIT
。如果消息队列中没有消息,则msgrcv调用立即返回并设置errno为ENOMSG。MSG_EXCEPT
。如果msgtype大于0,则接收消息队列中第一个非msgtype类型的消息。MSG_NOERROR
。如果消息数据部分的长度超过了msg_sz,就将它截断。
处于阻塞状态的msgrcv调用还可能被如下两种异常情况所中断:
- 消息队列被移除。此时msgrcv调用将立即返回并设置errno为EIDRM。
- 程序接收到信号。此时msgrcv调用将立即返回并设置errno为EINTR。
msgrcv成功时返回0,失败则返回-1并设置errno。
四、设置消息队列属性
控制消息队列的某些属性
#include <sys/msg.h>int msgctl(int msqid, int command, struct msqid_ds* buf);
-
msqid
参数是由msgget调用返回的共享内存标识符 -
command
参数指定要执行的命令- IPC_STAT:将消息队列关联的内核数据结构复制到buf,成功返回0。
- IPC_SET:将buf中的部分成员复制到关联的内核数据结构,同时
msg_ctime
被更新,成功返回0。 - IPC_RMID:立即移除消息队列,唤醒所有等待读消息和写消息的进程,成功返回0。
- IPC_INFO:获取系统消息资源配置信息,结果存放在buf,读取的话需要将buf转换为msginfo结构,成功返回内核消息队列个数。
- MSG_INFO:与IPC_INFO类似,不过返回的时已经分配的消息队列占用的资源信息。
- MSG_STAT:与IPC_STAT类似,不过此时msqid参数表示内核消息队列信息数组的索引。返回索引值为msqid的消息队列标识符。
五、仿真
发送消息的进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>#define MAX_MSG_SIZE 100struct msg_buffer {long msg_type;char msg_text[MAX_MSG_SIZE];
};int main() {// 生成一个唯一的键值key_t key = ftok("/tmp", 'A');// 创建一个消息队列int msgid = msgget(key, IPC_CREAT | 0666);if (msgid == -1) {perror("msgget");exit(EXIT_FAILURE);}// 生成消息struct msg_buffer message;message.msg_type = 1;strcpy(message.msg_text,"hello, this is thread A\n");// 发送消息到队列if (msgsnd(msgid, &message, sizeof(message), 0) == -1) {perror("msgsnd");exit(EXIT_FAILURE);}// 休眠sleep(100);// 删除消息队列if (msgctl(msgid, IPC_RMID, NULL) == -1) {perror("msgctl");exit(EXIT_FAILURE);}while(1);return 0;
}
接收消息的进程
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>#define MAX_MSG_SIZE 100struct msg_buffer {long msg_type;char msg_text[MAX_MSG_SIZE];
};int main() {// 生成一个唯一的键值key_t key = ftok("/tmp", 'A');// 获取已存在的消息队列int msgid = msgget(key, 0666);if (msgid == -1) {perror("msgget");exit(EXIT_FAILURE);}// 从消息队列中接收消息struct msg_buffer message;if (msgrcv(msgid, &message, sizeof(message), 1, 0) == -1) {perror("msgrcv");exit(EXIT_FAILURE);}printf("Received message: %s", message.msg_text);return 0;
}
仿真
查看当前系统上拥有哪些共享资源实例
ipcs
可以看到我们有一个消息队列。