目录
一、消息队列的基本原理
1、基本概念
2、基本原理
3、消息类型的关键作用
4、重要特性总结
5、生命周期管理
6、典型应用场景
二、System V 消息队列的内核数据结构
1、消息队列的管理结构 msqid_ds(消息队列标识符结构)
关键字段解析
2、权限控制结构 ipc_perm
权限控制字段解析
3、消息结构
4、消息队列与共享内存的对比
5、关键系统限制
6、总结
三、消息队列的创建:msgget() 函数
1、函数原型
2、参数解析
key 的生成方式
msgflg 的组成
1. 权限位(低 9 位)
2. 选项标志(高位)
3、返回值
4、典型使用场景
(1) 创建新队列
(2) 获取已有队列
(3) 创建独占队列(检测是否已存在)
5、与共享内存 (shmget) 的对比
6、注意事项
7、完整示例
输出示例
说明
验证
8、总结
四、 消息队列的释放与管理:msgctl() 函数
1、函数原型
2、参数解析
cmd 的常用选项
3、返回值
4、典型使用场景
(1) 删除消息队列(释放资源)
(2) 查询队列状态
(3) 修改队列属性
5、与共享内存 (shmctl) 的对比
6、注意事项
7、完整示例
分析:
8、总结
五、 向消息队列发送数据:msgsnd() 函数
1、函数原型
2、参数解析
消息结构体 msgbuf
msgflg 的常用选项
3、返回值
4、使用示例
(1) 定义消息结构
(2) 发送消息(阻塞模式)
(3) 发送消息(非阻塞模式)
5、关键注意事项
6、完整代码示例
代码逻辑分析
7、总结
六、从消息队列获取数据:msgrcv() 函数详解
1、函数原型
2、参数解析
消息结构体 msgbuf
msgtyp 的接收规则
msgflg 的常用选项
3、返回值
4、使用示例
(1) 定义消息结构
(2) 接收特定类型的消息(阻塞模式)
(3) 接收任意消息(非阻塞模式)
(4) 处理长消息(自动截断)
5、关键注意事项
6、完整代码示例
关键点说明:
注意:
7、总结
一、消息队列的基本原理
System V消息队列是Unix/Linux系统中一种进程间通信(IPC)机制,允许不相关的进程通过消息队列交换数据。
1、基本概念
System V消息队列是一种内核维护的消息链表,具有以下特点:
-
消息队列由唯一的标识符(队列ID)标识
-
消息是类型化的,每个消息都有一个长整型的类型字段
-
消息可以按照类型顺序读取,而不一定是先进先出(后面会具体介绍)
-
消息队列是持久的,会持续到系统重启或显式删除
2、基本原理
消息队列是System V IPC机制中的一种进程间通信方式,其本质是在内核空间维护的一个链表结构。这个链表中的每个节点都是一个独立的数据块,每个数据块包含两个主要部分:
-
消息类型:一个正整数,用于标识消息的类别
-
消息内容:实际传输的数据
通信双方通过以下机制实现交互:
-
队列共享:不同进程通过唯一的key访问同一个消息队列
-
写入机制:发送方总是在队列尾部添加新的消息
-
读取机制:接收方可以按照特定规则从队列中获取消息(不一定先进先出)
3、消息类型的关键作用
消息类型决定了消息的归属和读取方式:
-
发送方标识:通过指定不同类型的值来区分消息的接收者
-
选择性接收:接收方可以指定只接收特定类型的消息
-
优先级控制:类型值也可以用于实现消息优先级(数值越小优先级越高)
4、重要特性总结
- 消息队列提供了一个从一个进程向另一个进程发送数据块的方法。
-
结构化通信:支持类型化数据块(每个数据块都被认为是有一个类型的,接收者进程接收的数据块可以有不同的类型值)的传输,比管道等字节流通信更结构化
-
异步通信:发送和接收进程不需要同时存在
-
多路复用:单个队列可支持多个进程间的多种消息交换
-
内核持久性:消息会一直保留在队列中直到被读取(和共享内存一样,消息队列的资源也必须自行删除,否则不会自动清除,因为system V IPC资源的生命周期是随内核的)
-
容量限制:受系统配置参数限制(如MSGMAX, MSGMNB等)
5、生命周期管理
与System V IPC的其他资源相同:
-
显式删除:必须通过
msgctl(..., IPC_RMID,...)
主动删除 -
查看命令:使用
ipcs -q
查看现有消息队列 -
清理命令:可通过
ipcrm -q
删除指定的队列
6、典型应用场景
-
生产者-消费者模型
-
多进程事件通知
-
跨进程的任务分发系统
-
需要保证消息边界的通信场景
补充说明:
虽然POSIX也定义了消息队列接口(mq_*系列函数),但System V消息队列在现有系统中仍广泛使用,两者在实现和特性上有显著差异。
二、System V 消息队列的内核数据结构
1、消息队列的管理结构 msqid_ds(
消息队列标识符结构)
System V 消息队列在内核中通过 msqid_ds
结构体进行管理(内核为每个消息队列维护一个msqid_ds
结构),该结构体存储了消息队列的所有关键信息,定义在 <linux/msg.h>
中:
struct msqid_ds {struct ipc_perm msg_perm; // 权限控制结构struct msg *msg_first; // 指向队列中的第一个消息(内核内部使用)struct msg *msg_last; // 指向队列中的最后一个消息(内核内部使用)__kernel_time_t msg_stime; // 最后一次调用 `msgsnd()` 的时间(发送时间)__kernel_time_t msg_rtime; // 最后一次调用 `msgrcv()` 的时间(接收时间)__kernel_time_t msg_ctime; // 最后一次修改队列的时间(如权限变更)unsigned long msg_lcbytes; // 保留字段(32位兼容)unsigned long msg_lqbytes; // 保留字段(32位兼容)unsigned short msg_cbytes; // 当前队列中的总字节数unsigned short msg_qnum; // 当前队列中的消息数量unsigned short msg_qbytes; // 队列允许的最大字节数(`msg_qbytes ≤ MSGMNB`)__kernel_ipc_pid_t msg_lspid; // 最后一个执行 `msgsnd()` 的进程PID__kernel_ipc_pid_t msg_lrpid; // 最后一个执行 `msgrcv()` 的进程PID
};
关键字段解析
-
msg_perm
:权限控制结构,决定哪些进程可以访问该消息队列。 -
msg_first
/msg_last
:内核内部使用,指向消息链表(用户层不可见)。 -
时间戳(
msg_stime
/msg_rtime
/msg_ctime
):记录消息队列的关键操作时间,可用于监控和调试。 -
msg_qnum
和msg_cbytes
:-
msg_qnum
:当前队列中的消息数量。 -
msg_cbytes
:当前队列占用的总字节数(所有消息大小之和)。
-
-
msg_qbytes
:队列的最大容量(字节数),可通过msgctl(IPC_SET)
调整,但不得超过系统限制(MSGMNB
)。 -
msg_lspid
/msg_lrpid
:记录最后一个发送(msgsnd
)和接收(msgrcv
)消息的进程PID,便于调试。
2、权限控制结构 ipc_perm
可以看到消息队列数据结构的第一个成员是msg_perm
,它和shm_perm
是同一个类型的结构体变量。ipc_perm
是 System V IPC 机制的通用权限控制结构,定义在 <linux/ipc.h>
中,用于管理消息队列、共享内存和信号量的访问权限:
struct ipc_perm {__kernel_key_t key; // 创建 IPC 资源时指定的 key(`ftok()` 生成)__kernel_uid_t uid; // 所有者的用户ID__kernel_gid_t gid; // 所有者的组ID__kernel_uid_t cuid; // 创建者的用户ID__kernel_gid_t cgid; // 创建者的组ID__kernel_mode_t mode; // 访问权限(如 0644)unsigned short seq; // 序列号(内核用于管理 IPC 标识符)
};
权限控制字段解析
-
key
:用于生成 IPC 标识符(msgget()
的第一个参数),通常由ftok()
生成。 -
uid
/gid
:资源当前的所有者(可通过msgctl(IPC_SET)
修改)。 -
cuid
/cgid
:资源的创建者(不可修改)。 -
mode
:-
访问权限位,格式类似文件权限(如
0600
表示仅所有者可读写)。 -
可通过
msgctl(IPC_SET)
修改。
-
3、消息结构
发送和接收消息时使用的结构:
struct msgbuf {long mtype; // 消息类型,必须>0char mtext[1]; // 消息数据(实际长度可变)
};
4、消息队列与共享内存的对比
特性 | 消息队列 (msqid_ds ) | 共享内存 (shmid_ds ) |
---|---|---|
数据结构 | 链表结构,存储消息块 | 线性内存区域 |
访问方式 | 类型化消息(msgrcv 按类型读取) | 直接内存读写 |
同步需求 | 自带消息边界,无需额外同步 | 通常需要信号量同步 |
内核持久性 | 消息保留直到被读取 | 内存段持久,直到显式删除 |
适用场景 | 结构化通信、异步通知 | 大数据量、高性能共享 |
5、关键系统限制
-
MSGMAX
:单个消息的最大长度(字节),可通过sysctl kernel.msgmax
查看。 -
MSGMNB
:单个消息队列的最大容量(字节),可通过sysctl kernel.msgmnb
查看。 -
MSGMNI
:系统范围内最大消息队列数量,可通过sysctl kernel.msgmni
查看。
6、总结
-
System V 消息队列通过
msqid_ds
结构管理,包含队列状态、时间戳、权限等信息。 -
ipc_perm
控制访问权限,确保安全性。 -
消息队列适用于结构化、异步的进程间通信,但需注意手动释放资源(
msgctl(IPC_RMID)
)。 -
与共享内存相比,消息队列自带消息边界,但性能较低,适用于中小数据量通信。
三、消息队列的创建:msgget()
函数
1、函数原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgget(key_t key, int msgflg);
2、参数解析
参数 | 类型 | 说明 |
---|---|---|
key | key_t | 消息队列的唯一键值,通常由 ftok() 生成,或使用 IPC_PRIVATE 创建私有队列。 |
msgflg | int | 控制队列创建的标志位,由权限位(如 0644 )和选项标志(如 IPC_CREAT )按位或(|)组合。 |
key
的生成方式
-
ftok()
生成(推荐)
通过文件路径和项目ID生成唯一键值:key_t key = ftok("/path/to/file", 'A'); // 'A' 是项目ID(1~255)
-
IPC_PRIVATE
创建一个仅当前进程可访问的私有队列(通常用于父子进程间通信):
key_t key = IPC_PRIVATE;
msgflg
的组成
在 System V IPC(如消息队列、共享内存、信号量)中,msgflg
参数是一个整型(int
),它由 权限位(低 9 位) 和 选项标志(高位) 组合而成,通过 按位或(|
) 运算来设置。
1. 权限位(低 9 位)
-
作用:控制消息队列的访问权限(类似 Linux 文件权限)。
-
格式:一个 9 位 的八进制数,结构如下:
位组 含义 示例 0700
所有者(User)权限 rwx
(读/写/执行)0070
组(Group)权限 rw-
(读/写)0007
其他用户(Others)权限 r--
(只读)示例:
-
0644
→ 所有者可读可写(6
=110
),组和其他用户只读(4
=100
)。 -
0600
→ 仅所有者可读可写,其他用户无权限。
-
2. 选项标志(高位)
标志 | 作用 | 示例 |
---|---|---|
IPC_CREAT | 如果队列不存在,则创建;否则直接返回现有队列标识符。 | IPC_CREAT | 0644 |
IPC_EXCL | 与 IPC_CREAT 联用,若队列已存在则返回错误(EEXIST )。 | IPC_CREAT | IPC_EXCL | 0644 |
IPC_NOWAIT | 非阻塞模式,若队列满/空时立即返回错误(ENOMSG )。 | IPC_CREAT | IPC_NOWAIT | 0644 |
3、返回值
返回值 | 说明 |
---|---|
成功 | 返回消息队列的标识符(非负整数)(一个有效的消息队列标识符(用户层标识符),类似共享内存的shmid),用于后续操作(如 msgsnd /msgrcv )。 |
失败 | 返回 -1 ,并设置 errno (如 EACCES 权限不足、ENOENT 队列不存在)。 |
4、典型使用场景
(1) 创建新队列
key_t key = ftok("/tmp/msgqueue", 65);
int msqid = msgget(key, IPC_CREAT | 0666);
if (msqid == -1)
{perror("msgget failed");exit(EXIT_FAILURE);
}
(2) 获取已有队列
int msqid = msgget(key, 0666); // 不指定 IPC_CREAT,若队列不存在则失败
(3) 创建独占队列(检测是否已存在)
int msqid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);
if (msqid == -1 && errno == EEXIST)
{printf("Message queue already exists.\n");
}
5、与共享内存 (shmget
) 的对比
特性 | 消息队列 (msgget ) | 共享内存 (shmget ) |
---|---|---|
键值生成 | 同样使用 ftok() 或 IPC_PRIVATE | 相同 |
权限控制 | 通过 msgflg 的低9位设置 | 相同 |
创建标志 | IPC_CREAT /IPC_EXCL | 相同 |
资源类型 | 消息链表结构 | 线性内存段 |
6、注意事项
-
键值冲突:不同进程使用相同的
key
会访问同一队列,需确保ftok()
的参数唯一。 -
权限管理:若权限不足(如
msgflg=0600
),其他进程无法访问队列。 -
资源泄漏:消息队列不会自动释放,需通过
msgctl(msqid, IPC_RMID, NULL)
手动删除。 -
系统限制:队列数量受
MSGMNI
限制,可通过sysctl kernel.msgmni
查看。
7、完整示例
#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>int main()
{key_t key = ftok("/tmp/msgqueue", 65);if (key == -1) {perror("ftok failed");exit(EXIT_FAILURE);}int msqid = msgget(key, IPC_CREAT | 0666);if (msqid == -1) {perror("msgget failed");exit(EXIT_FAILURE);}printf("Message queue created with ID: %d\n", msqid);return 0;
}
输出示例
Message queue created with ID: 0
(实际 msqid 由内核动态分配)
说明
-
ftok()
成功:-
文件
/home/hmz/learn4/demo1.c
存在且可读。 -
key
计算成功(基于文件的inode
和proj_id=65
)。
-
-
msgget()
成功:-
如果消息队列 不存在,则创建新队列,返回其标识符
msqid
。 -
如果队列 已存在,则直接返回其
msqid
(不会报错,因为未使用IPC_EXCL
)。
-
验证
运行 ipcs -q
可查看新创建的消息队列:(key
由 ftok()
生成,msqid
由内核分配)
ipcs -q
8、总结
-
msgget()
是创建/访问消息队列的入口函数,依赖key
和权限标志。 -
需合理管理队列生命周期,避免资源泄漏。
-
适用于需要结构化通信的场景(如生产者-消费者模型)。
四、 消息队列的释放与管理:msgctl()
函数
1、函数原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgctl(int msqid, int cmd, struct msqid_ds *buf);
2、参数解析
参数 | 类型 | 说明 |
---|---|---|
msqid | int | 消息队列标识符,由 msgget() 返回。 |
cmd | int | 控制命令,决定该操作的类型(如删除、查询或设置队列属性)。 |
buf | struct msqid_ds* | 用于存储或修改队列状态信息的缓冲区(部分命令需传入,部分命令可设为 NULL )。 |
说明一下:
msgctl函数的参数与释放共享内存时使用的shmctl函数的三个参数相同,只不过msgctl函数的第三个参数传入的是消息队列的相关数据结构。
cmd
的常用选项
命令 | 作用 |
---|---|
IPC_RMID | 立即删除消息队列,并唤醒所有阻塞的读写进程(errno 设为 EIDRM )。buf 参数可设为 NULL 。 |
IPC_STAT | 获取队列的当前状态信息(如权限、消息数量等),存储到 buf 指向的 msqid_ds 结构体中。 |
IPC_SET | 修改队列属性(如权限 msg_perm.mode 或最大容量 msg_qbytes ),需通过 buf 传入新值。 |
3、返回值
返回值 | 说明 |
---|---|
成功 | 返回 0 。 |
失败 | 返回 -1 ,并设置 errno (如 EINVAL 无效队列ID、EPERM 权限不足)。 |
4、典型使用场景
(1) 删除消息队列(释放资源)
if (msgctl(msqid, IPC_RMID, NULL) == -1)
{perror("msgctl(IPC_RMID) failed");exit(EXIT_FAILURE);
}
printf("Message queue %d deleted.\n", msqid);
(2) 查询队列状态
struct msqid_ds info;
if (msgctl(msqid, IPC_STAT, &info) == -1)
{perror("msgctl(IPC_STAT) failed");exit(EXIT_FAILURE);
}
printf("Queue stats: %d messages, %d bytes max.\n", info.msg_qnum, info.msg_qbytes);
(3) 修改队列属性
struct msqid_ds info;
msgctl(msqid, IPC_STAT, &info); // 先获取当前状态
info.msg_perm.mode = 0600; // 修改权限为仅所有者可读写
info.msg_qbytes = 8192; // 调整队列最大容量为8KB
if (msgctl(msqid, IPC_SET, &info) == -1)
{perror("msgctl(IPC_SET) failed");
}
5、与共享内存 (shmctl
) 的对比
特性 | 消息队列 (msgctl ) | 共享内存 (shmctl ) |
---|---|---|
删除命令 | IPC_RMID | 相同 |
查询命令 | IPC_STAT (返回 msqid_ds 结构) | IPC_STAT (返回 shmid_ds 结构) |
设置命令 | IPC_SET (修改权限或容量) | 相同 |
关键差异 | 管理消息链表结构 | 管理内存段属性 |
6、注意事项
-
权限要求
-
删除队列(
IPC_RMID
)需具备所有者或超级用户权限。 -
修改属性(
IPC_SET
)需与原权限匹配。
-
-
资源释放时机:即使所有进程退出,消息队列仍会保留,必须显式调用
IPC_RMID
删除。 -
阻塞进程的处理:删除队列会唤醒所有阻塞在
msgsnd
/msgrcv
的进程,并使其返回错误(EIDRM
)。 -
内核限制:队列删除后,其标识符可能被后续新建的队列复用,需避免误操作。
7、完整示例
#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
#define PATHNAME "/home/hmz/learn4/demo2.c"int main()
{key_t key = ftok(PATHNAME, 65);int msqid = msgget(key, IPC_CREAT | 0666);if (msqid == -1) {perror("msgget failed");exit(EXIT_FAILURE);}// 删除队列if (msgctl(msqid, IPC_RMID, NULL) == -1) {perror("msgctl(IPC_RMID) failed");exit(EXIT_FAILURE);}printf("Message queue %d released.\n", msqid);return 0;
}
分析:
-
ftok()函数:
-
使用给定的路径名
/home/hmz/learn4/demo2.c
和项目ID65
生成一个唯一的键值(key)。 -
如果文件存在且可访问,ftok()会成功返回一个key_t类型的键;否则返回-1。
-
-
msgget()函数:
-
使用生成的key创建一个新的消息队列或访问已存在的队列。
-
IPC_CREAT | 0666
表示如果队列不存在则创建,并设置权限为0666(所有用户可读写)。 -
成功时返回消息队列标识符(msqid),失败时返回-1。
-
-
msgctl()函数:
-
使用
IPC_RMID
命令删除消息队列。 -
成功时返回0,失败时返回-1。
-
-
可能的运行结果:
-
如果所有操作都成功,程序将输出:Message queue [队列ID] released.
其中[队列ID]是系统分配的消息队列标识符。
-
可能的错误情况:
a) 如果ftok()失败(如路径不存在),会输出错误并退出
b) 如果msgget()失败(如权限不足),会输出"msgget failed"并退出
c) 如果msgctl()失败,会输出"msgctl(IPC_RMID) failed"并退出
-
-
程序特点:
-
这是一个"创建后立即删除"的示例程序,实际使用中通常不会这样操作。
-
程序没有发送或接收任何实际消息,只是演示了消息队列的创建和删除。
-
退出时使用了EXIT_FAILURE宏(值为1)表示失败退出。
-
注意:每次运行程序时,系统可能会分配不同的消息队列标识符,所以输出的数字可能会变化。
8、总结
-
msgctl()
是管理消息队列的核心函数,支持删除、查询和配置操作。 -
IPC_RMID
是释放资源的唯一方式,需主动调用以避免内存泄漏。 -
通过
IPC_STAT
/IPC_SET
可实现精细化的队列监控与权限控制。 -
与共享内存的管理接口高度相似,但操作对象为消息链表而非内存段。
五、 向消息队列发送数据:msgsnd()
函数
1、函数原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
2、参数解析
参数 | 类型 | 说明 |
---|---|---|
msqid | int | 消息队列标识符,由 msgget() 返回。 |
msgp | const void* | 指向消息缓冲区的指针,必须符合 msgbuf 结构。 |
msgsz | size_t | 消息数据的实际长度(字节数),不包括 mtype 字段。 |
msgflg | int | 发送标志位,控制发送行为(如非阻塞模式)。 |
- 第一个参数msqid,表示消息队列的用户级标识符。
- 第二个参数msgp,表示待发送的数据块。
- 第三个参数msgsz,表示所发送数据块的大小
- 第四个参数msgflg,表示发送数据块的方式,一般默认为0即可。
消息结构体 msgbuf
struct msgbuf {long mtype; // 消息类型(必须 > 0)char mtext[1]; // 消息数据(柔性数组,实际长度由用户定义)
};
-
mtype
:消息类型,接收方可通过该字段筛选消息(值必须为正整数)。 -
mtext
:实际消息数据,定义时可扩展为任意长度(如char mtext[100]
)。
msgflg
的常用选项
标志 | 作用 |
---|---|
0 | 默认阻塞模式,若队列已满则阻塞,直到空间可用。 |
IPC_NOWAIT | 非阻塞模式,若队列满则立即返回 -1 ,并设置 errno 为 EAGAIN 。 |
3、返回值
返回值 | 说明 |
---|---|
成功 | 返回 0 ,消息被添加到队列尾部。 |
失败 | 返回 -1 ,并设置 errno (如 EINVAL 无效参数、EIDRM 队列被删除、EACCES 权限不足)。 |
4、使用示例
(1) 定义消息结构
#define MAX_MSG_SIZE 1024struct my_msg {long mtype;char mtext[MAX_MSG_SIZE];
};
(2) 发送消息(阻塞模式)
struct my_msg msg;
msg.mtype = 1; // 设置消息类型
strcpy(msg.mtext, "Hello, Message Queue!");if (msgsnd(msqid, &msg, strlen(msg.mtext) + 1, 0) == -1) {perror("msgsnd failed");exit(EXIT_FAILURE);
}
(3) 发送消息(非阻塞模式)
if (msgsnd(msqid, &msg, strlen(msg.mtext) + 1, IPC_NOWAIT) == -1) {if (errno == EAGAIN) {printf("Queue is full. Try later.\n");} else {perror("msgsnd failed");}
}
5、关键注意事项
-
消息大小限制
-
单条消息的长度(
msgsz
)不能超过系统限制MSGMAX
(可通过cat /proc/sys/kernel/msgmax
查看)。 -
队列总容量受
MSGMNB
限制(所有消息的msgsz
之和 ≤msg_qbytes
)。
-
-
内存对齐问题:
msgbuf
结构中的mtext
字段建议定义为柔性数组(C99)或足够大的静态数组,避免内存越界。 -
消息类型必须为正数:若
mtype ≤ 0
,msgsnd()
会返回EINVAL
错误。 -
资源竞争处理:多进程同时发送消息时,需通过额外同步机制(如信号量)避免数据混乱。
6、完整代码示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>
#include <unistd.h>#define MAX_MSG_SIZE 1024
#define PATHNAME "/home/hmz/learn4/demo4.c"struct my_msg
{long mtype;char mtext[MAX_MSG_SIZE];
};int main()
{key_t key = ftok(PATHNAME, 65);int msqid = msgget(key, IPC_CREAT | 0666); // 队列不存在时自动创建if (msqid == -1){perror("msgget failed");exit(EXIT_FAILURE);}struct my_msg msg;msg.mtype = 1; // 消息类型snprintf(msg.mtext, MAX_MSG_SIZE, "PID %d: Hello, World!", getpid());if (msgsnd(msqid, &msg, strlen(msg.mtext) + 1, 0) == -1){perror("msgsnd failed");exit(EXIT_FAILURE);}printf("Message sent to queue %d.\n", msqid);return 0;
}
代码逻辑分析
-
生成键值 (
ftok
)-
使用文件路径
/home/hmz/learn4/demo4.c
和项目 ID65
生成唯一的key
。 -
如果文件不存在或不可访问,
ftok
会失败,返回-1
。
-
-
创建/获取消息队列 (
msgget
)-
IPC_CREAT | 0666
表示:-
如果队列不存在,则创建新队列;
-
权限设置为
0666
(所有用户可读写)。
-
-
如果失败(如权限不足),返回
-1
。
-
-
构造并发送消息 (
msgsnd
)-
消息类型
mtype = 1
(可用于接收端筛选消息)。 -
消息内容为字符串
"PID [进程ID]: Hello, World!"
(例如"PID 1234: Hello, World!"
)。 -
strlen(msg.mtext) + 1
表示发送消息长度(包括终止符\0
)。 -
如果队列已满或权限不足,
msgsnd
会失败。
-
7、总结
-
msgsnd()
用于向消息队列发送结构化数据,需通过msgbuf
指定类型和内容。 -
消息长度需明确指定(不包括
mtype
),且不能超过系统限制。 -
支持阻塞和非阻塞模式,适用于不同实时性要求的场景。
-
务必检查返回值,确保消息成功入队。
六、从消息队列获取数据:msgrcv()
函数详解
1、函数原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
2、参数解析
参数 | 类型 | 说明 |
---|---|---|
msqid | int | 消息队列标识符,由 msgget() 返回。 |
msgp | void* | 输出型参数,指向接收消息的缓冲区,需符合 msgbuf 结构(见下文)。 |
msgsz | size_t | 缓冲区 mtext 的最大容量(字节数),不包括 mtype 字段。 |
msgtyp | long | 指定接收的消息类型,决定消息筛选规则(见下文)。 |
msgflg | int | 接收标志位,控制接收行为(如非阻塞模式、截断长消息等)。 |
消息结构体 msgbuf
struct msgbuf {long mtype; // 消息类型(由发送方指定)char mtext[1]; // 消息数据(柔性数组,实际长度由用户定义)
};
msgtyp
的接收规则
msgtyp 值 | 接收行为 |
---|---|
> 0 | 读取队列中 第一条类型等于 msgtyp 的消息。 |
= 0 | 读取队列中的 第一条消息(无论类型)。 |
< 0 | 读取队列中 类型值 ≤ |msgtyp| 的最小类型的消息(如 -3 接收类型为 1 、2 或 3 的消息)。 |
msgflg
的常用选项
标志 | 作用 |
---|---|
IPC_NOWAIT | 非阻塞模式,若队列无匹配消息则立即返回 -1 ,并设置 errno 为 ENOMSG 。 |
MSG_NOERROR | 若消息实际长度 > msgsz ,则截断消息(不返回错误);未设置时,长消息会触发 E2BIG 错误。 |
MSG_EXCEPT | 当 msgtyp > 0 时,接收 类型不等于 msgtyp 的第一条消息(Linux 特有扩展)。 |
3、返回值
返回值 | 说明 |
---|---|
成功 | 返回实际接收到的 mtext 的字节数(不包括 mtype )。 |
失败 | 返回 -1 ,并设置 errno (如 EINVAL 无效参数、EIDRM 队列被删除、EACCES 权限不足)。 |
4、使用示例
(1) 定义消息结构
#define MAX_MSG_SIZE 1024struct my_msg {long mtype;char mtext[MAX_MSG_SIZE];
};
(2) 接收特定类型的消息(阻塞模式)
struct my_msg msg;
ssize_t bytes = msgrcv(msqid, &msg, MAX_MSG_SIZE, 1, 0); // 接收类型为1的消息
if (bytes == -1) {perror("msgrcv failed");exit(EXIT_FAILURE);
}
printf("Received: %s\n", msg.mtext);
(3) 接收任意消息(非阻塞模式)
ssize_t bytes = msgrcv(msqid, &msg, MAX_MSG_SIZE, 0, IPC_NOWAIT);
if (bytes == -1) {if (errno == ENOMSG) {printf("No message available.\n");} else {perror("msgrcv failed");}
}
(4) 处理长消息(自动截断)
ssize_t bytes = msgrcv(msqid, &msg, 100, 0, MSG_NOERROR); // 最多接收100字节
if (bytes == -1) {perror("msgrcv failed");
} else if (bytes == 100) {printf("Message truncated to 100 bytes.\n");
}
5、关键注意事项
-
缓冲区大小
-
msgsz
必须 ≥ 消息的实际长度(除非使用MSG_NOERROR
)。 -
建议将
mtext
定义为足够大的数组(如char mtext[4096]
)。
-
-
消息类型筛选
-
使用
msgtyp
可实现优先级消息处理(如类型值越小优先级越高)。 -
msgtyp < 0
时,内核会遍历队列寻找匹配的最小类型消息。
-
-
阻塞与非阻塞
-
默认阻塞模式下,若队列无匹配消息,进程会休眠等待。
-
IPC_NOWAIT
适用于需要轮询的场景。
-
-
队列删除处理:若队列在阻塞期间被删除(
IPC_RMID
),msgrcv()
会立即返回-1
,并设置errno
为EIDRM
。
6、完整代码示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>
#include <unistd.h>#define MAX_MSG_SIZE 1024
#define PATHNAME "/home/hmz/learn4/demo4.c"struct my_msg
{long mtype;char mtext[MAX_MSG_SIZE];
};int main()
{key_t key = ftok(PATHNAME, 65);int msqid = msgget(key, 0666);if (msqid == -1){perror("msgget failed");exit(EXIT_FAILURE);}struct my_msg msg;if (msgrcv(msqid, &msg, MAX_MSG_SIZE, 1, 0) == -1) // 接收类型为1的消息{perror("msgrcv failed");exit(EXIT_FAILURE);}printf("Received message: %s\n", msg.mtext);// 可选:删除消息队列// if (msgctl(msqid, IPC_RMID, NULL) == -1) {// perror("msgctl failed");// exit(EXIT_FAILURE);// }return 0;
}
上面第五点的发送代码,消息队列中已经有了,下面是一个对应的接收端代码。这个代码会从消息队列中读取发送的消息:
关键点说明:
-
使用相同的
PATHNAME
和项目ID(65)生成相同的key -
使用
msgget
获取相同的消息队列ID(不需要IPC_CREAT标志) -
使用
msgrcv
接收类型为1的消息(与发送端设置的mtype匹配) -
接收的消息会存储在msg结构体中,可以通过msg.mtext访问消息内容
注意:
-
接收端运行时需要确保消息队列已存在(即发送端已运行过)
-
如果要删除消息队列,可以取消注释最后的msgctl代码
-
如果发送端和接收端在不同的终端运行,确保工作目录相同或PATHNAME路径正确
7、总结
-
msgrcv()
是消息队列的核心接收函数,支持按类型筛选和多种接收模式。 -
通过
msgtyp
和msgflg
可灵活控制消息选择策略(如优先级、非阻塞等)。 -
务必检查返回值和错误码,确保消息完整性和程序健壮性。
-
与
msgsnd()
配合使用,可实现进程间结构化通信(如任务分发、事件通知等)。