本文讨论的是system v版本的消息队列。
- 前言
共享内存不涉及同步、互斥以及异步。System V是一种经典的UNIX进程间通信(IPC)机制,提供了一套API来支持进程之间的高效数据交换和同步。消息队列和信号量是其中的两个关键部分,它们各自解决了不同的通信和同步问题,但都基于System V的IPC框架。虽然System V IPC功能强大,但其接口较为复杂,现代系统中逐渐被POSIX IPC替代。
- 什么是消息队列
消息队列(Message Queue)是进程间通信(IPC)的一种方式,通过将消息存入内核维护的队列中,实现异步的进程数据传递。与管道不同,消息队列不仅允许不同大小的数据块传递,还支持消息的优先级排序,从而提供了更灵活的通信机制。
1.1 消息队列的特点
- 异步通信:发送方和接收方不需要同时进行,消息会存储在队列中,直到接收方读取。
- 持久性:消息队列由内核维护,即使发送方或者接收方意外退出,消息仍然保留在队列中。
- 消息分类:每个消息都有一个消息类型,可以根据类型有选择地读取特定消息。
- 容量限制:消息队列的大小是有限制的,需要合理地管理和清空,避免队列满导致堵塞。
1.2 消息队列的核心概念
- 标识符:消息队列使用一个唯一的标识符(Queue ID)来区分。
- 消息类型:每条消息包含一个正整数的类型,用户可以根据类型选择性地读取消息。
- 结构:消息队列实际传输的数据内容,消息长度(消息的字节数),消息类型分类标识。

消息队列遍历消息时,存数据块还是取数据块,取决于数据块中的类型type。
1.3 消息队列的数据结构
我们使用man手册来查看,输入man msgctl。

消息队列的数据结构:
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#include #include 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#include #include #include #include #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。

由于现在我们并没有使用消息队列,其used-bytes和消息数messages都是0。消息队列的生命周期也是跟随操作系统的,并不会因为进程的结束而结束。
1.4.2 释放消息队列
同共享内存一样,你可以通过指令或者程序中的函数来将消息队列释放。
释放指令:ipcrm -q msqid。

函数释放:msgctl。例行惯例,我们先在介绍msgctl。
#include#include #include 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#include #include 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#include #include 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:接收队列中的第一条消息,不论类型。 :接收队列中类型绝对值小于等于|msgtyp|的第一条消息。
-
-
msgflg:控制消息接收行为的标志,可选值包括:-
IPC_NOWAIT:如果没有符合条件的消息,函数立即返回-1,而不是阻塞。 -
MSG_NOERROR:如果消息正文大小超过msgsz,多余部分将被截断,而不是导致错误。
-
common.hpp:
#include#include #include #include #include #include #include #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;
}执行效果:

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










