一.消息队列概念

消息队列(Message Queue)是操作系统提供的一种进程间通信机制(IPC,Inter-Process Communication)

消息队列是IPC机制的重要组成部分
它允许多个进程通过操作系统内核维护的消息队列交换数据。
发送进程将消息放入队列,接收进程再从队列中读取消息
两个进程不需要同时运行,也不要求具有亲缘关系
与管道不同,消息队列中每条消息都带有类型标识符

可以先把它理解成一个由内核管理的“公共信箱”:
![[Pasted image 20261030234730.png]]

例如:

  1. 进程 A 发送一条内容为 hello 的消息

  2. 操作系统将这条消息放入消息队列

  3. 进程 B 可以稍后从队列中读取这条消息

  4. 读取成功后,这条消息会从队列中移除

这意味着,发送进程不必等待接收进程立即读取和处理消息

面试定义:
消息队列是操作系统提供的一种 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 消息队列主要通过四个系统调用完成操作

  1. msgget() —— 创建或获取消息队列

    根据 key 获取消息队列标识符

  2. msgsnd() —— 发送消息

    将消息添加到内核维护的消息队列中

  3. msgrcv() —— 接收消息

    根据指定条件从消息队列中读取并移除消息

  4. 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]]

我们梳理一下整个运行过程

  1. 发送端先运行:
  • ./msg创建或连接到key=1234的消息队列
  • 向队列中发送一条类型为2的消息(内容为"hello2")
  1. 接收端后运行
  • ./msgtest连接到相同的key=1234队列
  • 调用msgrcv,指定接收类型为2的消息
  • 成功匹配并读取消息,打印 result:hello2

消息队列通信的核心要素:

唯一键值:发送端和接受端必须使用相同的key_t(如1234),否则无法连接到同一个队列
消息类型:接收端通过msgrcv的第四个参数指定接收的消息类型(如2),若发送端消息不匹配,接收端会阻塞
阻塞特性:msgrcv默认是系统调用,若队列中没有类型为2的消息,接收端会一直等待,直到消息到达

四.面试高频考点

考点一:消息队列是什么?

推荐回答:

消息队列是操作系统提供的一种进程间通信机制,由内核负责维护。进程可以将数据封装成一条条独立的消息发送到队列中,其他进程再从队列中读取消息。消息可以携带类型信息,接收方能够根据类型选择性地读取消息。它支持不相关进程之间的通信,并且发送方和接收方不需要同时运行。

考点二:消息队列有什么特点?

主要有以下几点:

  1. 内核维护:消息队列由操作系统管理。

  2. 异步通信:发送方发送消息后,不必等待接收方立即处理。

  3. 消息边界:以独立消息为单位传递数据。

  4. 类型筛选:接收方可以根据消息类型选择消息。

  5. 容量限制:队列具有容量限制,队列满时默认发送可能阻塞。

  6. 持久性:进程退出后,队列不会自动消失,需要显式删除。

考点三:消息队列如何实现不相关进程之间的通信?

推荐回答:

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);

将其删除。

这也是实际编写程序时需要注意的资源管理问题。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐