【进程与线程】System V IPC:消息队列(Message Queue)
System V 消息队列(Message Queue)
消息队列(Message Queue)是 System V IPC 机制中的一种 进程间通信(IPC) 方式,允许进程通过发送和接收格式化消息进行通信。
System V IPC:消息队列(Message Queue)
1> 先进先出的队列
因为消息队列允许根据消息类型非顺序读取:例如,可以指定接收特定类型的消息,
2> 消息队列的格式: 消息类型 + 消息正文
使用ipcs命令时显示的列:
key msqid owner perms used-bytes messages
已经使用的字节数 当前消息队列中未读的消息数
key:创建消息队列时使用的键值。
msqid:消息队列的标识符。
owner:拥有者。
perms:权限。
used-bytes:已使用的字节数。
messages:队列中的消息数量。
覆盖消息队列的基本操作:
创建或获取消息队列(msgget)
发送消息(msgsnd)
接收消息(msgrcv)
控制消息队列(msgctl)
消息队列是先进先出的队列,实际中消息队列允许根据消息类型进行读取,不完全是FIFO。消息的格式为:消息类型加消息正文 ——> 例如,在C语言中如何使用struct来定义消息,其中必须包含一个长整型的消息类型字段,然后是消息正文。
消息队列的特点:内核持久性,即使进程结束,消息队列仍然存在,除非显式删除;权限管理,类似文件权限,如何设置读写权限。消息队列的优点在于可以异步通信,数据有结构,缺点是需要内核操作,效率不如共享内存,且存在系统限制(如最大消息数、消息大小限制)。“先进先出的队列”:因为消息队列允许根据消息类型非顺序读取。例如,可以指定接收特定类型的消息,或者按类型优先级读取。因此,消息队列并不是严格的FIFO,但默认情况下可能是按发送顺序接收的,除非指定了不同的消息类型优先级。
一、消息队列的核心特性
- 消息结构
- 每条消息包含两个部分:
- 消息类型(
long mtype
):用于标识消息的优先级或类别。 - 消息正文(用户自定义数据):任意长度的数据字段。
- 消息类型(
- 消息结构示例:
- 每条消息包含两个部分:
struct msgbuf {
long mtype; // 消息类型(必须 > 0)
char mtext[100]; // 消息正文(长度可自定义)
};
- 先进先出(FIFO)与优先级读取
- 默认FIFO:消息按发送顺序排队。
- 按类型读取:接收时可指定消息类型,实现优先级或选择性读取。
- 内核持久性
- 消息队列由内核维护,生命周期独立于进程。
- 需显式删除(
msgctl(..., IPC_RMID)
),否则持续存在直至系统重启。
二、消息队列的管理与操作
- 创建或获取消息队列
使用 msgget()
创建或获取消息队列标识符:
#include <sys/msg.h>
key_t key = ftok("/tmp", 'A'); // 生成唯一键值
int msqid = msgget(key, IPC_CREAT | 0666);
key
:唯一标识消息队列的键值(通过ftok()
生成)。msgflg
:权限标志(如IPC_CREAT | 0666
)。
- 发送消息
使用 msgsnd()
向队列发送消息:
struct msgbuf msg;
msg.mtype = 1; // 消息类型
strcpy(msg.mtext, "Hello Message Queue");
msgsnd(msqid, &msg, sizeof(msg.mtext), 0); // 阻塞发送
msqid
:消息队列标识符。msgp
:指向消息结构体的指针。msgsz
:消息正文长度(不包含mtype
)。msgflg
:标志(如IPC_NOWAIT
非阻塞发送)。
- 接收消息
使用 msgrcv()
从队列接收消息:
struct msgbuf msg;
ssize_t bytes = msgrcv(msqid, &msg, sizeof(msg.mtext), 1, 0); // 接收类型为1的消息
printf("Received: %s\n", msg.mtext);
msgtyp
:指定接收的消息类型:=0
:接收队列中第一条消息。>0
:接收类型等于 msgtyp 的第一条消息。<0
:接收类型 ≤ |msgtyp| 的最小类型消息。
- 控制消息队列
使用 msgctl()
管理消息队列:
msgctl(msqid, IPC_RMID, NULL); // 删除消息队列
cmd
:控制命令(如IPC_STAT
获取状态,IPC_RMID
删除队列)。
三、消息队列的状态查看
通过 ipcs -q
命令查看系统中的消息队列信息:
ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x61005a3d 12345 user 666 1024 5
key
:消息队列的唯一键值。msqid
:消息队列的标识符。owner
:队列拥有者。perms
:权限(八进制格式,如 666 表示所有用户可读写)。used-bytes
:队列中所有消息占用的总字节数。messages
:队列中当前未读的消息数量。
四、消息队列的底层实现
- 内核数据结构
struct msg_queue
:内核为每个消息队列维护的结构体,包含消息链表、权限、统计信息等。- 消息存储:消息以链表形式存储,按类型和到达时间排序。
- 同步与阻塞机制
- 发送阻塞:若队列满(受系统限制
msgmnb
),发送进程阻塞或返回错误。 - 接收阻塞:若队列空或指定类型消息不存在,接收进程阻塞或返回错误。
- 发送阻塞:若队列满(受系统限制
- 系统限制
msgmax
:单条消息的最大长度(通过sysctl kernel.msgmax
查看)。msgmnb
:队列的最大字节数。msgmni
:系统允许的最大消息队列数。
代码示例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int main()
{
//1- 获得键值key
key_t key = ftok("/",0x3);
if(key == -1)
{
perror("ftok");
return -1;
}
//2- 利用key 创建/打开消息队列
//int msgget(key_t key, int msgflg);
int msgid = msgget(key,IPC_CREAT | 0666);
if(msgid == -1)
{
perror("msgget");
return -1;
}
printf("key = %#x,msgid = %d\n",key,msgid);
//查看消息队列的属性
struct msqid_ds msg;
int ret = msgctl(msgid,IPC_STAT,&msg);
if(ret == -1)
{
perror("msgctl");
return -1;
}
#if 0
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) 最后调用msgsnd函数的进程PID*/
pid_t msg_lrpid; /* PID of last msgrcv(2) 最后调用msgrcv函数的进程PID*/
};
#endif
printf("__msg_cbytes = %lu\n",msg.__msg_cbytes);
printf("msg_qnum = %ld\n",msg.msg_qnum);
printf("msg_qbytes = %lu\n",msg.msg_qbytes);
return 0;
}
五、代码示例:进程间通信
发送进程(sender.c)
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
struct msgbuf {
long mtype;
char mtext[100];
};
int main() {
key_t key = ftok("/tmp", 'A');
int msqid = msgget(key, IPC_CREAT | 0666);
struct msgbuf msg;
msg.mtype = 1;
strcpy(msg.mtext, "Hello from Sender!");
msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
printf("Sender sent: %s\n", msg.mtext);
return 0;
}
接收进程(receiver.c)
#include <sys/msg.h>
#include <stdio.h>
struct msgbuf {
long mtype;
char mtext[100];
};
int main() {
key_t key = ftok("/tmp", 'A');
int msqid = msgget(key, 0666);
struct msgbuf msg;
msgrcv(msqid, &msg, sizeof(msg.mtext), 1, 0);
printf("Receiver received: %s\n", msg.mtext);
msgctl(msqid, IPC_RMID, NULL); // 删除消息队列
return 0;
}
运行步骤:
- 编译并运行发送进程:
gcc sender.c -o sender
./sender
- 编译并运行接收进程:
gcc receiver.c -o receiver
./receiver
优点 | 缺点 |
---|---|
支持消息类型和优先级 | 内核操作,性能低于共享内存 |
数据有结构,易解析 | 系统限制可能影响扩展性 |
异步通信,无需实时同步 | 生命周期需显式管理 |
消息队列适用的场景:1. 结构化数据通信:需要按类型处理消息的场景(如任务分发)。2. 跨进程异步通知:生产者-消费者模型。3. 替代简单网络通信:同一主机内进程间的高效通信。
以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。
我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!