八、进程间通信-消息队列
相关链接:消息队列
一、概述
1、什么是消息队列
消息队列是进程间通信的一种,它是由操作系统维护的以字节序列为基本单位的间接通信机制,遵循先进先出的原则,它提供了一个进程向另一个进程发送一个带类型的数据块的方法。
2、特点:
-
消息队列是进程或线程间通讯的其中一种方式。遵循先进先出的原则,保证了时间的顺序性。拥有该消息队列读权限的进程可以从消息队列读出数据,拥有该消息队列写权限的进程可以向消息队列发送数据。
-
消息作为节点一个一个地存放在消息队列里,可把消息队列比作信箱,消息比作依次顺序存放的信件。地址比作消息类型,内容为消息。
-
支持双向传输,可以使用消息类型区分不同的消息。
-
任何不同的进程都可以进行通讯,不局限于父子进程的通讯。
-
不足之处是消息的读写涉及数据拷贝,比较花费时间
消息队列有两套机制:system v 和posix 前者移植性比较好,后者使用比较简单还可配合select机制使用、信号和线程通知。
3、消息队列和有名管道的区别
-
消息队列对每个数据都有一个最大长度的限制。
-
消息队列也可以独立于发送和接收进程而存在,在进程终止时,消息队列及其内容并不会被删除
-
管道只能承载无格式字节流,消息队列提供有格式的字节流,可以减少了开发人员的工作量
-
消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级,接收程序可以通过消息类型有选择地接收数据, 而不是像命名管道中那样,只能默认地接收。
-
消息队列可以实现消息的随机查询,消息不一定要以先进先出的顺序接收,也可以按消息的类型接收。
4、通过如下命令可以查看系统当前的IPC对象,没有使用的情况下可能为空
二、消息队列的用法
1、定义一个唯一key(ftok)
1 | key_t ftok( const char *pathname, int proj_id) //获取一个key :将一个路径名和项目id转换成一个System V IPC key |
(1)参数:
- pathname:一个合法路径(路径必须存在)。
- proj_id:一个整数(不为0)。
当使用相同的 proj_id 值时,命名同一文件的所有路径名的结果值相同。
(2)返回值
- 成功:返回合法键值
- 失败: -1.
2、构造消息对象(msgget)
1 | int msgget(key_t key, int msgflg) |
(1)参数
- key:消息队列的键值。
- msgflg:
- IPC_CREAT: 如果消息队列不存在,则创建。
- mode:访问权限。
(2)返回值
-
成功:消息队列的ID
- 失败:-1
3、发送特定类型消息(msgsnd)
1 | int msgsnd( int msqid, const void *msgp, size_t msgsz, int msgflg); |
(1)参数
- msqid:消息队列ID。
- msgp:消息缓存区。
1 2 3 4 5 | struct msgbuf { long mtype; //消息标识,必须大于0 char mtext[1]; //消息内容 } |
(2)返回值
- 成功:0
- 失败:-1
4、接收特定类型消息(msgrcv)
1 | ssize_t msgrcv( int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg) |
(1)参数
-
msqid:消息队列ID.
-
msgp:消息缓存区。
-
msgs:消息正文的字节数。
-
msgtyp:要接收消息的标识。
-
msgflag:IPC_NOWAIT 非阻塞读取,MSG_NOERROR:截断消息。
(2)返回值
- 成功:0.
- 失败:-1
5、删除消息队列(msgctl)
(1)函数原型:
设置或获取消息队列的相关属性
1 2 3 | #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> |
1 | int msgctl( int msgqid, int cmd, struct maqid_ds *buf) |
- msgqid:消息队列的ID
- cmd:
-
- IPC_STAT:获取消息队列的属性信息
-
- IPC_SET:设置消息队列的属性
-
- IPC_RMID:删除消息队列
- buf:相关结构体缓冲区
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 last change */ unsigned long __msg_cbytes; /* Current number of bytes in queue (nonstandard) */ msgqnum_t msg_qnum; /* Current number of messages in queue */ msglen_t msg_qbytes; /* Maximum number of bytes allowed in queue */ pid_t msg_lspid; /* PID of last msgsnd(2) */ pid_t msg_lrpid; /* PID of last msgrcv(2) */ }; |
三、实例
使用消息队列实现两个进程的消息收发。
1、 数据发送
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #define BUFFER_SIZE 512 struct message{ long msg_type; char msg_text[BUFFER_SIZE]; }; void main( void ) { int qid; key_t key; struct message msg; /*根据不同的路径和关键字产生标准的key*/ if ((key = ftok( "/tmp" ,11)) == -1) { printf ( "ftok" ); exit (1); } /*创建消息队列*/ if ((qid = msgget(key,IPC_CREAT|0666)) == -1) { printf ( "msgget" ); exit (1); } printf ( "Open queue %d\n" ,qid); while (1) { printf ( "Enter some message to the queue:" ); if (( fgets (msg.msg_text,BUFFER_SIZE,stdin))==NULL) { puts ( "no message" ); exit (1); } msg.msg_type = getpid(); /*添加消息到消息队列*/ if ((msgsnd(qid, &msg, strlen (msg.msg_text),0)) < 0) { printf ( "message posted" ); exit (1); } if ( strncmp (msg.msg_text, "quit" ,4) == 0) { break ; } } exit (0); } |
2、数据接收
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #define BUFFER_SIZE 512 struct message{ long msg_type; char msg_text[BUFFER_SIZE]; //消息buffer }; void main( void ) { int qid; key_t key; struct message msg; /*根据不同的路径和关键字产生标准的key*/ if ((key = ftok( "/tmp" ,11)) == -1) { printf ( "ftok" ); exit (1); } /*创建消息队列*/ if ((qid = msgget(key,IPC_CREAT|0666)) == -1) { printf ( "msgget" ); exit (1); } printf ( "Open queue %d\n" ,qid); do { memset (msg.msg_text,0, sizeof (msg.msg_text)); /*接收消息到消息队列*/ if ((msgrcv(qid, ( void *)&msg, BUFFER_SIZE ,0,0)) < 0) { printf ( "msgrcv" ); exit (1); } printf ( "The message form process %ld: %s" ,msg.msg_type,msg.msg_text); } while ( strncmp (msg.msg_text, "quit" ,4)); //接收到quit则退出数据接收 /*从系统内核中删除消息队列*/ if ((msgctl(qid,IPC_RMID,NULL)) < 0) { printf ( "msgctl" ); exit (1); } exit (0); } |
执行结果:
如果消息队列发送的数据长度大于接收设置的buffer大小,则消息队列会分两次进行接收。现在设置接收和发送的buffer都为10个字节,然后发送数据如下:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了