文章目录
- [📨 Linux System V 消息队列实战](#📨 Linux System V 消息队列实战)
-
- [一、消息队列核心概念 💡](#一、消息队列核心概念 💡)
-
- [1. 消息队列特点 🌟](#1. 消息队列特点 🌟)
- [2. 生命周期 🔄](#2. 生命周期 🔄)
- 二、项目概述
- 三、完整代码实现
-
- [1. 公共头文件 `common.hpp`](#1. 公共头文件
common.hpp
) - [2. 发送端 `sender.cpp`](#2. 发送端
sender.cpp
) - [3. 接收端 `receiver.cpp`](#3. 接收端
receiver.cpp
)
- [1. 公共头文件 `common.hpp`](#1. 公共头文件
- 三、编译与运行指南
-
- [1. 编译命令](#1. 编译命令)
- [2. 运行顺序](#2. 运行顺序)
- [四、代码分解与核心函数 🛠️](#四、代码分解与核心函数 🛠️)
- [五、常见问题与调试技巧 🚨](#五、常见问题与调试技巧 🚨)
-
- [1. 系统命令 💻](#1. 系统命令 💻)

📨 Linux System V 消息队列实战
一、消息队列核心概念 💡
1. 消息队列特点 🌟
- 📦 结构化数据 :消息包含类型标识(
mytype
)和正文(data
),支持分类处理 - ⏳ 异步通信:发送方和接收方无需同时在线
- 🔒 持久性:消息队列在内核中持久存在,直到显式删除
- 🔑 访问控制 :通过权限标志(如
0666
)管理读写权限
2. 生命周期 🔄
- 创建 → 发送/接收 → 销毁
- ❗若不主动销毁,队列会持续占用内核资源(通过
ipcs -q
可查看)
二、项目概述
本示例通过 System V 消息队列 实现跨进程通信,包含三个核心文件:
common.hpp
:消息队列公共配置sender.cpp
:消息生产者(发送端)receiver.cpp
:消息消费者(接收端)
三、完整代码实现
1. 公共头文件 common.hpp
cpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <cstring>
#include <cstdlib>
// 消息队列标识配置
const char* pathname = "/home"; // ftok路径参数(需真实存在)
const int proj_id = 666; // 项目ID(取值范围0-255)
// 消息结构体(必须包含long类型字段)
struct message {
long mytype; // 消息类型标识(必须 > 0)
char data[100]; // 消息正文(最大99字符)
};
// 错误码枚举
enum {
MSGGET_ERROR = 1,
};
// 通用队列创建/获取函数
int Msgqueue(int flag) {
key_t key = ftok(pathname, proj_id);
if (key < 0) {
perror("ftok failed");
exit(MSGGET_ERROR);
}
int msgid = msgget(key, flag);
if (msgid < 0) {
perror("msgget failed");
exit(MSGGET_ERROR);
}
return msgid;
}
// 创建新队列(服务端)
int CreateMsg() {
return Msgqueue(IPC_CREAT | IPC_EXCL | 0666);
}
// 获取已有队列(客户端)
int Getmsg() {
return Msgqueue(IPC_CREAT | 0666);
}
2. 发送端 sender.cpp
cpp
#include "common.hpp"
int main() {
// 创建消息队列
int msgid = CreateMsg();
std::cout << " 消息队列创建成功! ID: " << msgid << std::endl;
// 构造消息
message msg;
msg.mytype = 1; // 消息类型标识
snprintf(msg.data, sizeof(msg.data), "hello from sender");
// 发送消息(阻塞模式)
if (msgsnd(msgid, &msg, sizeof(msg.data), 0) < 0) {
perror(" 消息发送失败");
exit(1);
}
std::cout << " 消息已发送: " << msg.data << std::endl;
return 0;
}
3. 接收端 receiver.cpp
cpp
#include "common.hpp"
int main() {
// 获取消息队列
int msgid = Getmsg();
std::cout << " 连接到消息队列 ID: " << msgid << std::endl;
// 接收消息(阻塞等待类型为1的消息)
message msg;
if (msgrcv(msgid, &msg, sizeof(msg.data), 1, 0) < 0) {
perror(" 消息接收失败");
exit(1);
}
std::cout << " 收到消息: " << msg.data << std::endl;
// 销毁队列(生产环境慎用!)
if (msgctl(msgid, IPC_RMID, nullptr) < 0) {
perror(" 队列删除失败");
} else {
std::cout << "消息队列已销毁" << std::endl;
}
return 0;
}
三、编译与运行指南
1. 编译命令
bash
# 生成发送端可执行文件
g++ sender.cpp -o sender -std=c++11
# 生成接收端可执行文件
g++ receiver.cpp -o receiver -std=c++11
2. 运行顺序
bash
# 终端1:运行发送端(创建队列)
./sender
# 终端2:运行接收端(消费消息)
./receiver

四、代码分解与核心函数 🛠️
1. 公共头文件 common.hpp
📁
消息结构体定义
cpp
struct message {
long mytype; // 🔢 消息类型(必须 > 0)
char data[100]; // 📝 消息正文(最大长度可调整)
};
Key 生成与队列创建
cpp
key_t key = ftok(pathname, proj_id); // 🗝️ 生成唯一键值
int msgid = msgget(key, flag); // 🚪 创建/获取队列
ftok
参数 :- 📂
pathname
:任意存在的文件路径(本文使用/home
) - 🆔
proj_id
:项目标识符(确保不同应用使用不同值)
- 📂
封装函数
- 🆕
CreateMsg()
:创建新队列(IPC_CREAT | IPC_EXCL
确保唯一性) - 🔍
Getmsg()
:获取已有队列(若不存在则创建)
2. 发送端代码解析 📤
cpp
int main() {
int msgid = CreateMsg(); // 🏗️ 创建队列
message msg;
msg.mytype = 1; // 🏷️ 设置消息类型
snprintf(msg.data, sizeof(msg.data), "hello from sender\n");
// ✈️ 发送消息(阻塞模式)
msgsnd(msgid, &msg, sizeof(msg.data), 0);
std::cout << "Message sent: " << msg.data << std::endl;
return 0;
}
关键点 🔑
- 🎯 消息类型 :接收端通过
mytype
筛选消息 - 🚦 发送模式 :
- 🛑
0
:阻塞发送(队列满时等待) - 🚀
IPC_NOWAIT
:非阻塞发送(立即返回错误)
- 🛑
3. 接收端代码解析 📥
cpp
int main() {
int msgid = Getmsg(); // 🔍 获取队列
message msg;
// 📭 接收类型为1的消息(阻塞模式)
msgrcv(msgid, &msg, sizeof(msg.data), 1, 0);
std::cout << msg.data << std::endl;
msgctl(msgid, IPC_RMID, NULL); // 🗑️ 销毁队列
return 0;
}
关键点 🔍
- 🎯 消息过滤 :
msgrcv
的第4个参数指定接收的消息类型- 🎯
1
:仅接收类型为1的消息 - 🎲
0
:接收队列中第一条消息 - 🔍
-3
:接收类型 ≤3 的最小消息
- 🎯
- 🧹 资源释放 :
IPC_RMID
立即删除队列
五、常见问题与调试技巧 🚨
1. 系统命令 💻
bash
ipcs -q # 🔍 查看所有消息队列
ipcrm -q <msqid> # 🗑️ 手动删除队列