文章目录
- 📨 Linux System V 消息队列实战
- 一、消息队列核心概念 💡
- 1. 消息队列特点 🌟
- 2. 生命周期 🔄
- 二、项目概述
- 三、完整代码实现
- 1. 公共头文件 `common.hpp`
- 2. 发送端 `sender.cpp`
- 3. 接收端 `receiver.cpp`
- 三、编译与运行指南
- 1. 编译命令
- 2. 运行顺序
- 四、代码分解与核心函数 🛠️
- 1. 公共头文件 `common.hpp` 📁
- 消息结构体定义
- Key 生成与队列创建
- 封装函数
- 2. 发送端代码解析 📤
- 关键点 🔑
- 3. 接收端代码解析 📥
- 关键点 🔍
- 五、常见问题与调试技巧 🚨
- 1. 系统命令 💻
📨 Linux System V 消息队列实战
一、消息队列核心概念 💡
1. 消息队列特点 🌟
- 📦 结构化数据:消息包含类型标识(
mytype
)和正文(data
),支持分类处理 - ⏳ 异步通信:发送方和接收方无需同时在线
- 🔒 持久性:消息队列在内核中持久存在,直到显式删除
- 🔑 访问控制:通过权限标志(如
0666
)管理读写权限
2. 生命周期 🔄
- 创建 → 发送/接收 → 销毁
- ❗若不主动销毁,队列会持续占用内核资源(通过
ipcs -q
可查看)
二、项目概述
本示例通过 System V 消息队列 实现跨进程通信,包含三个核心文件:
common.hpp
:消息队列公共配置sender.cpp
:消息生产者(发送端)receiver.cpp
:消息消费者(接收端)
三、完整代码实现
1. 公共头文件 common.hpp
#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
#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
#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. 编译命令
# 生成发送端可执行文件
g++ sender.cpp -o sender -std=c++11# 生成接收端可执行文件
g++ receiver.cpp -o receiver -std=c++11
2. 运行顺序
# 终端1:运行发送端(创建队列)
./sender# 终端2:运行接收端(消费消息)
./receiver
四、代码分解与核心函数 🛠️
1. 公共头文件 common.hpp
📁
消息结构体定义
struct message {long mytype; // 🔢 消息类型(必须 > 0)char data[100]; // 📝 消息正文(最大长度可调整)
};
Key 生成与队列创建
key_t key = ftok(pathname, proj_id); // 🗝️ 生成唯一键值
int msgid = msgget(key, flag); // 🚪 创建/获取队列
ftok
参数:- 📂
pathname
:任意存在的文件路径(本文使用/home
) - 🆔
proj_id
:项目标识符(确保不同应用使用不同值)
- 📂
封装函数
- 🆕
CreateMsg()
:创建新队列(IPC_CREAT | IPC_EXCL
确保唯一性) - 🔍
Getmsg()
:获取已有队列(若不存在则创建)
2. 发送端代码解析 📤
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. 接收端代码解析 📥
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. 系统命令 💻
ipcs -q # 🔍 查看所有消息队列
ipcrm -q <msqid> # 🗑️ 手动删除队列