
◆ 博主名称: 小此方-CSDN博客 大家好,欢迎来到小此方的博客。
⭐️Linux系列个人专栏: 【主题曲】Linux
⭐️此方的GitHub: github_此方
⭐️ Re系列专栏:我们思考 (Rethink) · 我们重建 (Rebuild) · 我们记录 (Record)
文章目录
- 概要&序論
- 一、什么是消息队列
-
- 1.1消息队列提供了一种有类型数据块传输方式
- 1.2对数据块先描述再组织------消息队列诞生了
- 1.3和共享内存如出一辙的操作方法
-
- 1.3.1进程如何保证自己看到的是同一个消息队列
- [1.3.2 SystemV 标准下设计方式完全一致的接口](#1.3.2 SystemV 标准下设计方式完全一致的接口)
-
- 1.3.2.1创建与获取:**msgget**
- [1.3.2.2控制与释放:**msgctl** 与内核数据结构](#1.3.2.2控制与释放:msgctl 与内核数据结构)
- 二、消息队列的收发操作与实战细节
-
- [2.1 发送与接收函数接口](#2.1 发送与接收函数接口)
- [2.2 用户必须手动定义的数据结构](#2.2 用户必须手动定义的数据结构)
- [2.3 工程实践:在公共头文件中定义通信规范](#2.3 工程实践:在公共头文件中定义通信规范)
- [2.4 命令行管理工具:**ipcs** 与 **ipcrm**](#2.4 命令行管理工具:ipcs 与 ipcrm)
概要&序論
Hello,大家好我是此方 ,上一篇我们重点讲解了SystemV的三大通信手段的其之一------共享内存。本文将继续带大家了解第二者------消息队列 ,内容相对简单,我们直接开始吧。
一、什么是消息队列
1.1消息队列提供了一种有类型数据块传输方式
IPC通信的本质是让两个进程看到同一份资源。管道看到的是同一份管道文件,共享内存看到的是同一份内存资源,消息队列也没有绕开这个本质,他看到的是一个一个数据块。
先说结论:消息队列提供了一种"一个进程给另外一共进程发送有类型数据块的方式。" (对比管道使用的是字节流)
为什么是有类型?因为为了区分------什么数据块是我需要的。什么数据块是我发出来的 于是两个进程就约定好各自的类型,A进程的类型是1,B进程约定的类型是2,A发送1类型数据块,接收2类型数据块,B进程反之。
1.2对数据块先描述再组织------消息队列诞生了
数据块这么多,操作系统要不要管理他们?需要。于是先描述再组织。操作系统就使用结构体封装这些数据块,用指针把他们串联起来形成消息队列。

如上图,这些数据块是一个一个结构体,他们的内部包含了以下内容(伪代码)。
cpp
struct msg_queue
{
msg_queue* head; // 前驱指针
msg_queue* tail; // 后驱指针
long type; // 消息类型
char data[256]; // 消息内容
};
1.3和共享内存如出一辙的操作方法
1.3.1进程如何保证自己看到的是同一个消息队列
两个进程,如何保证自己看到的是同一个消息队列呢?SystemV标准下的三种手段采用了同样的套路:在外部约定一个Key。

1.3.2 SystemV 标准下设计方式完全一致的接口
正因为它们都遵循 SystemV 标准,消息队列的生命周期同样随内核(除非显式删除,否则哪怕进程退出了,消息队列依然存在)。在接口设计上,它与我们之前学习的共享内存几乎完全是一模一样的套路。
1.3.2.1创建与获取:msgget
想要让两个进程看到同一个消息队列,首先得用 ftok 函数通过相同的 pathname 和 proj_id 计算出一个 key 。接着,使用 msgget 系统调用来创建或获取消息队列:
c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
- 参数说明 :
- key :由 ftok 产生的唯一键值,用来在系统层面标识该消息队列资源。
- msgflg :权限与标志位。常用的有 IPC_CREAT (若不存在则创建,存在则获取)和 IPC_EXCL (与 IPC_CREAT 连用,若已存在则报错返回,确保创建出的是全新的队列)。
- 返回值 :成功时返回消息队列标识符(msqid) 。后续所有的增删查改操作,应用层全部使用这个 msqid ,而不再使用 key (这和共享内存中 shmid 与 key 的关系一模一样)。
1.3.2.2控制与释放:msgctl 与内核数据结构
操作系统为了管理消息队列,会在内核中维护一个专门的结构体 struct msqid_ds,它包含了该队列的权限、大小、当前消息数以及最后操作的进程 PID 等信息:
c
struct msqid_ds {
struct ipc_perm msg_perm; /* 拥有者与权限信息 */
time_t msg_stime; /* 上次发送消息的时间 */
time_t msg_rtime; /* 上次接收消息的时间 */
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 */
};
其中,struct ipc_perm 是 SystemV 家族的核心基类,里面存放着 key_t key 以及权限 mode 。(后面详细讲)
要控制或销毁这个队列,我们需要调用 msgctl:
c
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
- 常用命令(cmd) :
- IPC_STAT :将内核中该消息队列的 msqid_ds 状态拷贝到用户提供的 buf 结构体中,用于查看属性。
- IPC_RMID :立即删除该消息队列。一旦删除,所有消息清空,阻塞在该队列上的进程会被唤醒并返回错误。
二、消息队列的收发操作与实战细节
2.1 发送与接收函数接口
与共享内存"直接挂载到进程地址空间、像用本地内存一样读写"的机制不同,消息队列采用的是显式的 "收/发"模式。
c
// 发送消息
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// 接收消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
- msgp(消息缓冲区指针):指向一个用户自定义的结构体(详情见下文)。
- msgsz (消息正文大小):注意!这个大小仅仅指消息中用户正文数据的长度,不包括核心的 long mtype 所占用的空间!
- msgtyp (接收类型):
- 若传 0:代表先进先出,不分类型,直接读取队列中的第一条消息。
- 若大于 0 :代表只读取队列中第一条类型恰好等于 msgtyp 的消息。
- msgflg :控制阻塞行为。通常设为 0 (代表阻塞等待);若设为 IPC_NOWAIT,则在队列满(发送时)或队列空(接收时)的情况下不阻塞,直接报错返回。
2.2 用户必须手动定义的数据结构
虽然系统提供了收发接口,但内核要求用户必须在应用层手动创建一个符合规范的通信数据结构 。操作系统对这个结构体的名字没有强制要求(推荐叫 struct msgbuf ),但对其内部成员的顺序与类型有严格的硬性规定:
c
// 标准推荐模板
struct msgbuf {
long mtype; /* 消息类型,必须 > 0,用来区分身份 */
char mtext[1]; /* 消息正文内容,数组大小可以根据业务自由修改 */
};
核心细节:
- 数据类型必须是 long :结构体的第一个成员必须是 long mtype,这是内核用来筛选消息的凭证。
- 数据的正文大小 :我们在传入 msgsnd 或 msgrcv 的 msgsz 参数时,计算方式应该是 sizeof(struct msgbuf) - sizeof(long)。
2.3 工程实践:在公共头文件中定义通信规范
在实际的多进程开发中,Server 端和 Client 端都需要包含同一个公共头文件,在其中约定好 Key 的路径、各自的身份标签以及统一的缓冲区大小。
cpp
// comm.hpp 示范
#pragma once
#define PATHNAME "/tmp/sysv_msg_queue"
#define PROJ_ID 0x666
// 约定好各自发送消息的类型标签
#define SERVER_TYPE 1 // Server发给Client的消息类型
#define CLIENT_TYPE 2 // Client发给Server的消息类型
#define TEXT_SIZE 1000
// 双方共用的消息结构体
struct my_msgbuf {
long mtype;
char mtext[TEXT_SIZE];
};
2.4 命令行管理工具:ipcs 与 ipcrm
-
查看全部 SystemV IPC 资源:
bashipcs -
定向只查看消息队列:
bashipcs -q -
在命令行手动删除消息队列:
bashipcrm -q msqid
好的本期内容就到这里,如果对你有帮助,还不要忘记点赞三联支持。我是此方,我们下期再见。bye! Linux、C++、算法持续连载中,欢迎关注WeChat Official Account 【此方的技术栈】。