linux 下消息队列

发布于:2025-03-18 ⋅ 阅读:(17) ⋅ 点赞:(0)

在这里插入图片描述


📨 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> # 🗑️ 手动删除队列