POSIX 消息队列是 Linux 下高性能的进程间通信(IPC)机制,支持结构化数据传输、消息优先级、阻塞 / 非阻塞模式,以下是涵盖核心函数、属性、使用规则、避坑要点的完整总结,重点对每个核心函数做精细化讲解。
一、核心概念
- 本质 :内核维护的消息存储队列,跨进程可见,每个队列有唯一名称(必须以
/ 开头,如 /test_mq)。
- 核心特性 :
- 消息带优先级(0~31,数值越大优先级越高),高优先级消息优先被读取;
- 支持阻塞 / 非阻塞模式,适配不同业务场景;
- 队列属性(最大消息数、单消息长度)创建后不可修改;
- 每个进程需通过
mq_open() 获取独立的队列描述符(进程私有)。
- 仅传输 "实际数据",不支持跨进程传输指针 / 动态分配内存(核心避坑点)。
- 编译要求 :编译时必须链接实时库
rt,命令为 gcc 代码文件.c -o 可执行文件 -lrt。
二、核心函数详解(必掌握)
1. mq_open () ------ 创建 / 打开消息队列(唯一入口)
函数原型
复制代码
#include <mqueue.h>
mqd_t mq_open(const char *name, int oflag, mode_t mode, const struct mq_attr *attr);
核心作用
- 队列不存在时:带
O_CREAT 标志则创建 + 打开队列;
- 队列已存在时:不带
O_CREAT 标志则仅打开队列。
参数详解
| 参数 |
含义与取值 |
name |
队列名称,必须以 / 开头(如 /test_mq),长度不超过 NAME_MAX(通常 255) |
oflag |
打开标志(可组合):- O_RDONLY:只读打开- O_WRONLY:只写打开- O_RDWR:读写打开- O_CREAT:队列不存在则创建- O_EXCL:与 O_CREAT 配合,队列已存在则报错(避免重复创建)- O_NONBLOCK:非阻塞模式打开(仅影响当前进程的操作模式) |
mode |
队列权限(如 0664),仅 O_CREAT 时有效,规则同文件权限(所有者 / 组 / 其他) |
attr |
队列属性结构体指针,仅 O_CREAT 时有效:- 传 NULL:使用系统默认属性- 传结构体:设置 mq_maxmsg/mq_msgsize(其他属性无效) |
返回值
- 成功:返回
mqd_t 类型的队列描述符(非负整数);
- 失败:返回
(mqd_t)-1,并设置 errno(如 EEXIST 队列已存在、EINVAL 名称格式错误)。
2. mq_send () ------ 发送消息到队列
函数原型
复制代码
int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio);
核心作用
将字节流(字符串、结构体等)作为一条消息发送到队列,消息长度需 ≤ 队列的 mq_msgsize。
参数详解
| 参数 |
含义与注意事项 |
mqdes |
mq_open() 返回的队列描述符(唯一合法值,不可填随机数) |
msg_ptr |
消息缓冲区指针:- 字符串:直接传字符串地址(如 const char *msg = "test")- 结构体:强制转换为 char*(如 (const char *)&stu) ❌ 禁止传指向动态内存(malloc)的指针(仅传地址,不传数据) |
msg_len |
消息字节长度:- 字符串:strlen(msg) + 1(包含 \0 结束符)- 结构体:sizeof(结构体)- 必须 ≤ 队列的 mq_msgsize |
msg_prio |
消息优先级(0~31,0 最低):- 同优先级按 FIFO 顺序读取- 不同优先级:高优先级先读取 |
返回值
- 成功:返回 0;
- 失败:返回 -1,设置
errno(如 EAGAIN 非阻塞模式下队列满、EMSGSIZE 消息长度超过 mq_msgsize)。
3. mq_receive () ------ 从队列接收消息
函数原型
复制代码
ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio);
核心作用
从队列读取一条消息(优先高优先级),存入缓冲区,返回实际读取的字节数。
参数详解
| 参数 |
含义与注意事项 |
mqdes |
mq_open() 返回的队列描述符 |
msg_ptr |
接收缓冲区指针:- 字符串:char buf[256] = {0}(初始化避免乱码)- 结构体:强制转换为 char*(如 (char *)&stu) - ❌ 接收含动态指针的结构体时,指针地址无效(段错误 / 乱码 |
msg_len |
缓冲区字节长度:- 必须 ≥ 队列的 mq_msgsize(否则返回 -1,errno=EMSGSIZE)- 推荐设为 mq_msgsize 或结构体大小 |
msg_prio |
输出参数,存储接收到的消息优先级:- 不需要则传 NULL- 需要则传 unsigned int 变量地址 |
返回值
- 成功:返回实际读取的字节数;
- 失败:返回 -1,设置
errno(如 EAGAIN 非阻塞模式下队列空、EMSGSIZE 缓冲区长度不足)。
4. mq_close () ------ 关闭队列描述符
函数原型
复制代码
int mq_close(mqd_t mqdes);
核心作用
释放当前进程的队列描述符(类似文件的 close()),不删除队列本身。
参数详解
| 参数 |
含义与注意事项 |
mqdes |
mq_open() 返回的队列描述符(唯一合法值,已关闭的描述符再次传入会报错) |
返回值
- 成功:返回 0;
- 失败:返回 -1,设置
errno(如 EBADF 无效的描述符)。
使用规则
- 进程退出时内核会自动关闭未显式关闭的描述符,但建议显式调用(良好编程习惯);
- 关闭后描述符失效,不可再用于
mq_send()/mq_receive()。
5. mq_unlink () ------ 删除消息队列
函数原型
复制代码
int mq_unlink(const char *name);
核心作用
删除队列名称,内核会等待所有进程关闭该队列的描述符后,释放队列的内核资源(彻底删除队列)。
参数详解
| 参数 |
含义与注意事项 |
name |
队列名称(与 mq_open() 的 name 一致,如 /test_mq) |
返回值
- 成功:返回 0;
- 失败:返回 -1,设置
errno(如 ENOENT 队列不存在)。
使用规则
- 仅需一个进程调用(通常是最后退出的进程,如父进程);
- 调用后,新进程无法打开该队列,但已打开的进程仍可操作,直到所有进程关闭描述符。
6. mq_getattr () ------ 获取队列属性
函数原型
复制代码
int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
核心作用
读取队列的当前属性(如当前消息数、阻塞模式、最大消息数等)。
参数详解
| 参数 |
含义与注意事项 |
mqdes |
队列描述符 |
attr |
输出参数,存储队列属性(需提前分配内存) |
返回值
- 成功:返回 0;
- 失败:返回 -1,设置
errno。
7. mq_setattr () ------ 设置队列属性
函数原型
复制代码
int mq_setattr(mqd_t mqdes, const struct mq_attr *newattr, struct mq_attr *oldattr);
核心作用
仅能修改队列的 mq_flags(阻塞 / 非阻塞模式),其他属性(mq_maxmsg/mq_msgsize)创建后不可改。
参数详解
| 参数 |
含义与注意事项 |
mqdes |
队列描述符 |
newattr |
新属性结构体:仅 mq_flags 字段有效(0 = 阻塞,O_NONBLOCK = 非阻塞) |
oldattr |
输出参数,存储修改前的属性:不需要则传 NULL |
返回值
- 成功:返回 0;
- 失败:返回 -1,设置
errno。
三、队列属性(struct mq_attr)详解
函数原型
复制代码
struct mq_attr {
long mq_flags; // 阻塞/非阻塞模式(0 / O_NONBLOCK)
long mq_maxmsg; // 队列最大消息数(创建时设置,不可改)
long mq_msgsize; // 单消息最大字节数(创建时设置,不可改)
long mq_curmsgs; // 当前消息数(只读,内核维护)
};
参数详解
| 成员名 |
赋值 / 修改规则 |
注意事项 |
mq_flags |
仅能通过 mq_setattr() 修改,mq_open() 时设置无效 |
作用于 mq_send() 和 mq_receive():- 阻塞:队列满 / 空时等待- 非阻塞:队列满 / 空时返回 -1(EAGAIN) |
mq_maxmsg |
仅 mq_open(O_CREAT) 时设置,≤ 系统限制(/proc/sys/fs/mqueue/msg_max) |
创建后不可修改,超出限制会导致 mq_open() 失败 |
mq_msgsize |
仅 mq_open(O_CREAT) 时设置,≤ 系统限制(/proc/sys/fs/mqueue/msgsize_max) |
接收缓冲区长度必须 ≥ 该值,否则 mq_receive() 失败 |
mq_curmsgs |
只读,手动赋值无效,通过 mq_getattr() 读取 |
反映队列当前的消息数量,可用于监控队列状态 |
四、常见问题与避坑要点
| 问题现象 |
根本原因 |
解决方案 |
| 栈溢出(stack smashing) |
接收缓冲区长度 < 消息长度 /mq_msgsize |
缓冲区大小设为 ≥ mq_msgsize,初始化缓冲区为 0(如 char buf[256] = {0}) |
| 结构体解析乱码 |
内存对齐 / 字节序不一致 |
用 __attribute__((packed)) 取消内存对齐;跨平台转换字节序(htons/ntohs) |
| 接收顺序错乱 |
消息优先级不同 |
统一消息优先级(如都设为 0),保证 FIFO 顺序 |
mq_open 返回 -1 |
队列已存在且带 O_EXCL |
去掉 O_EXCL,或先调用 mq_unlink() 删除旧队列 |
mq_receive 返回 -1 |
非阻塞模式下队列空 |
改用阻塞模式,或在代码中处理 EAGAIN(循环重试 + 休眠) |
| 多进程操作队列失败 |
未重新调用 mq_open() 获取描述符 |
每个进程需独立调用 mq_open(),描述符是进程私有 |
五、核心规则总结
- 唯一性 :队列名称以
/ 开头,系统内唯一;
- 权限 :创建队列时的
mode 参数决定其他进程能否打开;
- 资源清理 :
mq_close() 仅关描述符,mq_unlink() 才彻底删除队列;
- 数据传输:支持任意字节流(字符串 / 结构体),仅关注长度,不解析内容;禁止传输指针 / 动态内存(仅传地址,不传数据),必须用固定数组嵌入结构体;
- 阻塞 / 非阻塞 :
mq_flags 作用于所有操作(发送 + 接收),需按需设置。