《Linux 进程间通信:一文搞懂消息队列,从底层原理到实战应用》
一.消息队列概念
消息队列(Message Queue)是操作系统提供的一种进程间通信机制(IPC,Inter-Process Communication)
消息队列是IPC机制的重要组成部分
它允许多个进程通过操作系统内核维护的消息队列交换数据。
发送进程将消息放入队列,接收进程再从队列中读取消息
两个进程不需要同时运行,也不要求具有亲缘关系
与管道不同,消息队列中每条消息都带有类型标识符
可以先把它理解成一个由内核管理的“公共信箱”:
![[Pasted image 20261030234730.png]]
例如:
-
进程 A 发送一条内容为
hello的消息 -
操作系统将这条消息放入消息队列
-
进程 B 可以稍后从队列中读取这条消息
-
读取成功后,这条消息会从队列中移除
这意味着,发送进程不必等待接收进程立即读取和处理消息
面试定义:
消息队列是操作系统提供的一种 IPC 机制,消息以独立的数据单元存储在内核维护的队列中。进程可以通过发送和接收操作进行通信,消息还可以携带类型信息,接收方能够根据消息类型选择性地读取数据
1. 消息队列的核心特点
| 特点 | 具体含义 |
|---|---|
| 内核维护 | 消息队列由操作系统内核管理 |
| 异步通信 | 发送方发送消息后,不必等待接收方立即处理 |
| 消息边界 | 数据以一条条独立消息为单位,而不是连续字节流 |
| 类型标识 | 消息可以携带类型,接收方可以按类型读取 |
| 无亲缘关系限制 | 不相关的进程也可以通过消息队列通信 |
| 有容量限制 | 队列空间有限,队列满时发送操作可能阻塞 |
注意:异步通信不代表发送操作永远不会阻塞。当队列已满时,默认的发送操作可能等待队列出现可用空间
2. 为什么需要消息队列?
假设有两个进程 A 和 B:
如果使用管道通信,数据通常按照字节流的形式传递,接收方需要自行处理数据边界
而使用消息队列时,发送方可以把数据组织成一条完整的消息
例如:
消息1:用户登录 消息2:用户退出
消息3:用户购买商品
接收方可以根据消息类型决定读取哪一类消息
因此,消息队列特别适合需要传递结构化消息、让发送方和接收方相对独立工作的场景
消息队列的核心作用:
异步通信:发送方发送消息后无需等待接收方处理
结构化数据传输:消息是结构体,包含数据和类型
跨进程通信:无需亲缘关系,只需要同一个键值(key)即可通信
二.System V 消息队列的工作原理
Linux 中有两套常见的消息队列接口:
-
System V 消息队列:本文主要讲解的机制,使用
msgget、msgsnd、msgrcv、msgctl -
POSIX 消息队列:使用
mq_open、mq_send、mq_receive、mq_close等接口
该笔记中使用的是第一种,即 System V 消息队列。两者的 API 不同,注意区分。
我将通过代码演示消息队列进行进程间通信
发送端代码:
![[Pasted image 20260328134532.png|697]]
接收端代码:
![[Pasted image 20260331154159.png]]
三.代码
发送端代码分析
![[Pasted image 20260329184304.png]]
<sys/msg.h>->必须包含的消息队列头文件
消息队列的四个核心接口
System V 消息队列主要通过四个系统调用完成操作
-
msgget() —— 创建或获取消息队列
根据 key 获取消息队列标识符
-
msgsnd() —— 发送消息
将消息添加到内核维护的消息队列中
-
msgrcv() —— 接收消息
根据指定条件从消息队列中读取并移除消息
-
msgctl() —— 控制消息队列
查询、修改消息队列属性,或者删除消息队列
获取消息队列标识符(msgid)
创建或获取一个消息队列:通过唯一的键值1234创建一个系统范围内的消息队列用于进程间通信
msgget(key_t key, int msgflg)
-
功能:用于创建一个新的消息队列或存取一个已存在的消息队列的ID
-
参数:这里的1234是键值,发送端与接收端必须使用相同的键值才能操作同一个序列
IPC_CREAT,表示如果不存在则创建;0600是权限掩码(创建者可读可写)
![[Pasted image 20260328140146.png]]
![[Pasted image 20260328210156.png]]
如果队列已经存在,msgget返回队列的ID
如果不存在且指定了IPC_CREAT,它会创建一个新的队列
内核会返回一个消息队列标识符,它是一个非负整数,也是内核用来唯一标识该消息队列的“ID"
msgid接收这个返回值,只要知道消息队列的键值,任何进程调用msgget都会得到同一个消息队列ID -
成功:返回非负整数,即
msgid -
失败:返回
-1,并设置errno
声明消息结构体变量用于存储数据
struct mess
{
long type; //消息类型 (必须为long类型)
char buff[32]; //消息正文(数据缓冲区)
}
struct mess dt; //定义叫做dt的结构体变量
dt.type=2; //设置消息类型为2
strcpy(dt.buff,“hello2”); //消息内容,将字符串“hello2"复制到缓冲区
type的核心作用是过滤:
在 System V 消息队列中,type 字段的核心作用是过滤
- 发送端(Sender):调用
msgsnd时,会将dt.type的值(例如2)写入消息 - 接收端(Receiver):调用
msgrcv时,需要传入一个msgtyp参数来决定接收哪条消息
消息结构体中的 mtype 必须大于 0,msgrcv 的第四个参数 msgtyp 为 0 时,才表示不限制消息类型,读取队列中的第一条消息。
当type的值为>0的整数时,匹配指定类型消息,例如发送端传入5,则只接收类型为5的消息
当type的值为<0的整数,按优先级接收,例如传入 -2,接收类型小于等于 2 的最小类型消息
调用msgsnd函数发送消息到队列
msgsnd(msgid, (void*)&dt, sizeof(dt.buff),0);
![[Pasted image 20260329122406.png]]
参数分析:
msgid:队列id号
(void*)&dt:指向消息结构体的指针
sizeof(dt.buff):发送的数据长度(只发送buff长度,不包括type)
0:标志位(0表示阻塞模式)
构造消息:定义一个符合消息队列要求的数据结构,包含一个消息类型(2)和一段文本内容(hello2)
发送消息:将构建好的数据发送到刚才创建的消息队列中,供其它进程接收
接收端代码
![[Pasted image 20260329183001.png]]
如图,接收端的代码在消息结构体定义,创建/获取消息队列,错误处理方面同发送端相同
调用msgrcv函数接收消息
msgrcv(msgid, &dt, 32, 2, 0);
![[Pasted image 20260329181611.png]]
参数详解:
- msgid:消息队列的ID(由msgget返回)
- &dt:接收缓冲区的地址(消息将存入dt结构体)
- 32:要接收的正文长度(即buff的大小)
- 2:消息类型(只接收type=2的消息)
- 0:标志位(0表示阻塞模式,如果没有消息会一直等待)
打印接收到的消息内容
printf(“result:%s\n”, dt.buff);
运行程序
![[Pasted image 20260329183415.png]]
我们梳理一下整个运行过程
- 发送端先运行:
- ./msg创建或连接到key=1234的消息队列
- 向队列中发送一条类型为2的消息(内容为"hello2")
- 接收端后运行
- ./msgtest连接到相同的key=1234队列
- 调用msgrcv,指定接收类型为2的消息
- 成功匹配并读取消息,打印 result:hello2
消息队列通信的核心要素:
唯一键值:发送端和接受端必须使用相同的key_t(如1234),否则无法连接到同一个队列
消息类型:接收端通过msgrcv的第四个参数指定接收的消息类型(如2),若发送端消息不匹配,接收端会阻塞
阻塞特性:msgrcv默认是系统调用,若队列中没有类型为2的消息,接收端会一直等待,直到消息到达
四.面试高频考点
考点一:消息队列是什么?
推荐回答:
消息队列是操作系统提供的一种进程间通信机制,由内核负责维护。进程可以将数据封装成一条条独立的消息发送到队列中,其他进程再从队列中读取消息。消息可以携带类型信息,接收方能够根据类型选择性地读取消息。它支持不相关进程之间的通信,并且发送方和接收方不需要同时运行。
考点二:消息队列有什么特点?
主要有以下几点:
-
内核维护:消息队列由操作系统管理。
-
异步通信:发送方发送消息后,不必等待接收方立即处理。
-
消息边界:以独立消息为单位传递数据。
-
类型筛选:接收方可以根据消息类型选择消息。
-
容量限制:队列具有容量限制,队列满时默认发送可能阻塞。
-
持久性:进程退出后,队列不会自动消失,需要显式删除。
考点三:消息队列如何实现不相关进程之间的通信?
推荐回答:
System V 消息队列通过 key 来标识和查找队列。
不同进程使用相同的 key 调用 msgget,在同一个 IPC 命名空间且具有相应权限的情况下,就能获取同一个消息队列的标识符 msgid,之后通过 msgsnd 和 msgrcv 完成通信。
考点四:消息队列中的消息类型有什么作用?
推荐回答:
消息类型主要用于消息筛选。
发送方在消息结构体中设置 mtype,接收方通过 msgrcv 的 msgtyp 参数指定接收条件。
当 msgtyp 为 0 时,接收队列中的第一条消息;大于 0 时,接收指定类型的第一条消息;小于 0 时,接收不超过其绝对值的消息中类型最小的一种。
需要注意,发送时的消息类型必须是正数。
考点五:消息队列的阻塞和非阻塞有什么区别?
默认情况下,System V 消息队列采用阻塞式行为。
| 操作 | 默认阻塞条件 | 非阻塞标志 |
|---|---|---|
msgsnd | 队列空间不足 | IPC_NOWAIT |
msgrcv | 没有符合条件的消息 | IPC_NOWAIT |
使用 IPC_NOWAIT 后,如果条件不满足,系统调用会立即返回失败,而不是一直等待。
考点六:消息队列中的消息读取后会消失吗?
会。
msgrcv 成功接收消息时,会将该条消息从队列中移除。
因此,普通接收操作不是复制一份消息供多个进程读取,而是取出并消费该消息。
考点七:消息队列和共享内存哪个效率更高?
通常共享内存效率更高。
消息队列发送和接收消息时需要通过内核进行数据传递,涉及数据复制和系统调用。
共享内存允许多个进程直接访问同一块物理内存映射区域,减少了数据传递过程中的复制开销。
但是,共享内存需要额外解决进程之间的同步问题。
考点八:消息队列使用完之后需要释放吗?
需要。
System V 消息队列是内核维护的 IPC 资源,不会随着某个进程退出就自动销毁。
可以调用:
msgctl(msgid, IPC_RMID, nullptr);
将其删除。
这也是实际编写程序时需要注意的资源管理问题。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)