首页 > 运维 > linux运维 > 正文

【Linux】消息传递的艺术:探索Linux消息队列机制

看不見的法師
发布: 2025-06-19 15:36:33
原创
153人浏览过

本文讨论的是system v版本的消息队列。

  1. 前言

共享内存不涉及同步、互斥以及异步。System V是一种经典的UNIX进程间通信(IPC)机制,提供了一套API来支持进程之间的高效数据交换和同步。消息队列和信号量是其中的两个关键部分,它们各自解决了不同的通信和同步问题,但都基于System V的IPC框架。虽然System V IPC功能强大,但其接口较为复杂,现代系统中逐渐被POSIX IPC替代。

  1. 什么是消息队列

消息队列(Message Queue)是进程间通信(IPC)的一种方式,通过将消息存入内核维护的队列中,实现异步的进程数据传递。与管道不同,消息队列不仅允许不同大小的数据块传递,还支持消息的优先级排序,从而提供了更灵活的通信机制。

1.1 消息队列的特点

  • 异步通信:发送方和接收方不需要同时进行,消息会存储在队列中,直到接收方读取。
  • 持久性:消息队列由内核维护,即使发送方或者接收方意外退出,消息仍然保留在队列中。
  • 消息分类:每个消息都有一个消息类型,可以根据类型有选择地读取特定消息。
  • 容量限制:消息队列的大小是有限制的,需要合理地管理和清空,避免队列满导致堵塞。

1.2 消息队列的核心概念

  • 标识符:消息队列使用一个唯一的标识符(Queue ID)来区分。
  • 消息类型:每条消息包含一个正整数的类型,用户可以根据类型选择性地读取消息。
  • 结构:消息队列实际传输的数据内容,消息长度(消息的字节数),消息类型分类标识。

【Linux】消息传递的艺术:探索Linux消息队列机制

消息队列遍历消息时,存数据块还是取数据块,取决于数据块中的类型type。

1.3 消息队列的数据结构

我们使用man手册来查看,输入man msgctl。

【Linux】消息传递的艺术:探索Linux消息队列机制

消息队列的数据结构:

struct msqid_ds {
   struct ipc_perm msg_perm;   /* Ownership and permissions */
   time_t          msg_stime;  /* Time of last msgsnd(2) */
   time_t          msg_rtime;  /* Time of last msgrcv(2) */
   time_t          msg_ctime;  /* Time of creation or last modification by msgctl() */
   unsigned long   msg_cbytes; /* # of bytes in queue */
   msgqnum_t       msg_qnum;   /* # number of messages in queue */
   msglen_t        msg_qbytes; /* Maximum # of bytes in queue */
   pid_t           msg_lspid;  /* PID of last msgsnd(2) */
   pid_t           msg_lrpid;  /* PID of last msgrcv(2) */
};
登录后复制

我们要注意类型为struct ipc_perm这个类型,该类型在共享内存的数据结构中也出现过。下面是它的具体内容:

struct ipc_perm {
   key_t          __key;       /* Key supplied to msgget(2) */
   uid_t          uid;         /* Effective UID of owner */
   gid_t          gid;         /* Effective GID of owner */
   uid_t          cuid;        /* Effective UID of creator */
   gid_t          cgid;        /* Effective GID of creator */
   unsigned short mode;        /* Permissions */
   unsigned short __seq;       /* Sequence number */
};
登录后复制

1.4 消息队列的使用

如果你已经学习过共享内存,那么消息队列的使用也一定会得心应手的。

1.4.1 创建消息队列

使用msgget函数来创建消息队列。msgget是用于创建或访问System V消息队列的系统调用。它根据指定的键值创建或获取一个消息队列标识符,用于后续的消息操作。

#include <sys>
#include <sys>
#include <sys>
int msgget(key_t key, int msgflg);
登录后复制

使用方法和shmget高度相似。

参数说明:

  • key:一个唯一标识消息队列的值。常通过ftok函数生成,也可以直接使用整数值(不推荐)。
  • msgflg:用于指定消息队列的权限和行为。
    • 访问权限:权限掩码。
    • 行为控制:IPC_CREAT:如果队列不存在那么创建;IPC_EXCL:需要与IPC_CREAT结合使用,单独使用没有意义。表示如果队列已经存在,那么返回错误。

返回值:

  • 成功:返回消息队列的标识符msgid。
  • 失败:返回-1。

是不是和shmget相似,简直一模一样啊!如果你没有看过我的共享内存文章,推荐一看,会对你理解消息队列也是有帮助的哦~ 【Linux】「共享内存揭秘」:高效进程通信的终极指南-CSDN博客。

还是老样子,我们先创建一个共享区域:common.hpp。

#include <iostream>
#include <sys>
#include <sys>
#include <sys>
#include <assert.h>
#define PATHNAME "./"
#define PROJ_ID 0x3f
const mode_t mode = 0666;
key_t getKey(){
    /**
     * :return 返回key值
     * @return
     */
    key_t ret = ftok(PATHNAME,PROJ_ID);
    assert(ret!=-1);
    return ret;
}
int creatMsg(){
    /**:function 创建消息队列
     * :return 返回msgid
     * @return
     */
    int msgid = msgget(getKey(),IPC_CREAT|IPC_EXCL|mode);
    assert(msgid!=-1);
    return msgid;
}
登录后复制

然后创建一个消息队列。

#include "common.hpp"
int main(){
    //创建消息队列
    int msgid = creatMsg();
    return 0;
}
登录后复制

运行后,我们在命令行输入指令ipcs -q。

【Linux】消息传递的艺术:探索Linux消息队列机制

由于现在我们并没有使用消息队列,其used-bytes和消息数messages都是0。消息队列的生命周期也是跟随操作系统的,并不会因为进程的结束而结束。

1.4.2 释放消息队列

同共享内存一样,你可以通过指令或者程序中的函数来将消息队列释放。

释放指令:ipcrm -q msqid。

【Linux】消息传递的艺术:探索Linux消息队列机制

函数释放:msgctl。例行惯例,我们先在介绍msgctl。

#include <sys>
#include <sys>
#include <sys>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
登录后复制

参数说明:

  • msqid:消息队列的标识符(由msgget返回)。
  • cmd:指定要执行的操作:
    • IPC_STAT:获取消息队列的当前状态,并将其存储到buf中。
    • IPC_SET:设置消息队列的属性(使用buf中的数据)。
    • IPC_RMID:删除消息队列。
  • buf:指向struct msqid_ds的指针,表示消息队列的状态和属性。对于IPC_STAT和IPC_SET,需要使用此参数;对IPC_RMID则可以为NULL。

返回值:

  • 成功返回0,失败返回-1。

只是释放消息队列,函数的使用就是套路了。

msgctl(msqid,IPC_RMID,nullptr);
登录后复制

1.4.3 使用消息队列发送消息

为了将数据发送到消息队列,现在需要的函数为msgsnd。msgsnd是用于向System V消息队列发送消息的系统调用。它将一条消息放入消息队列中,以实现进程间通信。

#include <sys>
#include <sys>
#include <sys>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
登录后复制

参数说明:

  • msqid:消息队列的标识符,由msgget返回。
  • msgp:指向消息数据的指针。这个指针指向的结构必须包含一个long类型的成员(用于存储消息类型),后面跟随实际的数据。
  • msgsz:消息正文部分的大小(以字节为单位,不包括消息类型字段)。
  • msgflg:标志选项,用于控制消息发送行为:
    • IPC_NOWAIT:如果消息队列已满,则不阻塞,立即返回错误。默认情况下,如果队列满,调用会阻塞直到有空间可用。

返回值:

  • 成功:0。
  • 失败:-1。

关于参数2,表示的是待发送的数据块,是一个结构体,我们需要自己定义。

struct msgbuf {
   long mtype;       /* message type, must be > 0 */
   char mtext[1];    /* message data */
};
登录后复制

1.4.4 使用消息队列接收消息

为了接收消息,同样我们还需要一个函数msgrcv。msgrcv是一个用于从System V消息队列中接收消息的系统调用。通过它,进程可以从指定的消息队列中读取一条符合条件的消息。

#include <sys>
#include <sys>
#include <sys>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
登录后复制

参数说明:

  • msqid:消息队列的标识符,由msgget返回。用于标识从哪个队列中接收消息。
  • msgp:指向一个用户定义的消息缓冲区。该缓冲区的第一个字段必须是long类型的消息类型mtype,后续是消息正文。示例消息缓冲区定义:
struct msgbuf {
     long mtype;       // 消息类型
     char mtext[MSGSZ]; // 消息正文
};
登录后复制
  • msgsz:指定消息正文mtext的大小(以字节为单位)。如果接收到的消息大小超过该值,行为将根据msgflg参数决定。
  • msgtyp:指定要接收的消息类型:
    • >0:接收队列中类型等于msgtyp的第一条消息。
    • 0:接收队列中的第一条消息,不论类型。
  • msgflg:控制消息接收行为的标志,可选值包括:
    • IPC_NOWAIT:如果没有符合条件的消息,函数立即返回-1,而不是阻塞。
    • MSG_NOERROR:如果消息正文大小超过msgsz,多余部分将被截断,而不是导致错误。

common.hpp:

#include <iostream>
#include <sys>
#include <sys>
#include <sys>
#include <cstring>
#include <unistd.h>
#include <assert.h>
#define PATHNAME "./"
#define PROJ_ID 0x3f
#define MSGSZ 128
const mode_t mode = 0666;
key_t getKey(){
    /**
     * :return 返回key值
     * @return
     */
    key_t ret = ftok(PATHNAME,PROJ_ID);
    assert(ret!=-1);
    return ret;
}
int creatMsg(){
    /**:function 创建消息队列
     * :return 返回msgid
     * @return
     */
    int msgid = msgget(getKey(),IPC_CREAT|IPC_EXCL|mode);
    assert(msgid!=-1);
    return msgid;
}
int getMsg(){
    /**:function 获取消息队列
     * :return 返回msgid
     * @return
     */
    int msgid = msgget(getKey(),IPC_CREAT);
    assert(msgid!=-1);
    return msgid;
}
登录后复制

client.cc用于向server.cc发送消息。

/**
 * 用户端发送消息
 */
#include "common.hpp"
struct my_msgbuf{
    long mtype;
    char mtext[MSGSZ];//MSGSZ为新定义的标识常量:128
};
int main(){
    int msgid = creatMsg();
    struct my_msgbuf msg;
    msg.mtype = 1;//设置消息类型
    strcpy(msg.mtext,"hello,Message Queue!");
    //开始发送消息
    int n = msgsnd(msgid,&msg,strlen(msg.mtext)+1,0);
    assert(n!=-1);
    std::cout << "消息已发送:" << msg.mtext << std::endl;
    return 0;
}
登录后复制

server.cc用于接收client.cc消息。

/**
 * 服务端接收消息
 */
#include "common.hpp"
struct my_msgbuf{
    long mtype;
    char mtext[MSGSZ];//MSGSZ为新定义的标识常量:128
};
int main(){
    int msgid = getMsg();
    struct my_msgbuf msg;
    //开始接受信息
    ssize_t n = msgrcv(msgid,&msg,MSGSZ,1,0);
    assert(n!=-1);
    std::cout << "接收到消息:" << msg.mtext << std::endl;
    return 0;
}
登录后复制

执行效果:

【Linux】消息传递的艺术:探索Linux消息队列机制

  1. 总结

消息队列的大部分接口都与共享内存类似,如果你使用过更现代的POSIX IPC,可能会觉得System V已经落后,事实上也确实是如此,System V已经过于老旧,现在用的很少,对于此版本,不需要太过深入了解,在实际情况中遇到,查查文档就是了。

以上就是【Linux】消息传递的艺术:探索Linux消息队列机制的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号