消息队列(Message Queue)
消息队列允许不同的进程通过发送和接收消息来交换数据,从而实现进程间的通信。消息队列在系统中对应一个由内核维护的内存空间,本质上是一个先进先出(FIFO)的数据结构。
具体来说,发送进程可以将消息添加到消息队列的尾部,而接收进程则可以从队列的头部获取消息。这种通信方式是异步的,也就是说发送进程和接收进程不需要同时在线或同步操作。发送进程可以在任何时间将消息发送到队列,而接收进程则可以在其方便的时候从队列中检索消息。
消息队列的特点:
- 独立于发送和接收进程:消息队列是一种独立于发送和接收进程的数据结构,它在内核中以队列的形式存在。
- 消息封装:消息队列中的数据是封装为消息的形式进行传输的,每个消息都有明确的边界。
- 固定或可变长度:消息队列支持固定长度或可变长度的消息,每个消息都是独立的数据单元。
- 竞争消费与多接收:消息队列默认是“竞争消费”模型,即消息一旦被接收,就会从队列中移除;也可以通过配置消息队列实现消息的“多接收”(multi-receive)或“复制”(copy)模式。在这种模式下,消息在被接收后仍然保留在队列中,其他进程也可以读取这个消息。这允许多个进程可以独立地处理同一份消息,常见于发布/订阅模型。
- 消息顺序:消息队列保证了消息的顺序性,即先发送的消息先被接收。
- 消息标识:每个消息都有一个与之关联的类型,接收消息时可以根据类型有选择地接收。
- 系统资源限制:系统对消息队列的数量和消息的大小有一定的限制。
- 复杂的控制:消息队列提供了复杂的控制机制,如消息类型、优先级和消息的非阻塞访问。
- 适用场景:适用于需要可靠消息传递、消息顺序重要、或需要异步通信的场景。它们也适用于分布式系统或复杂的应用程序,其中消息的顺序和类型很重要。
使用消息队列的关键函数:
mq_open
:打开或创建一个消息队列。
mqd_t mq_open(const char *name, int oflag);
消息队列的名称在整个系统中必须是唯一的。使用 mq_open
时,如果消息队列已经存在,则不会重复创建,但可以修改其属性。使用 O_CREAT
标志创建消息队列时,需要提供权限和初始属性。消息队列的属性包括消息队列的最大消息数、每个消息的最大的值等。
mq_close
:关闭一个打开的消息队列描述符。
int mq_close(mqd_t mqdes);
mq_unlink
:删除一个消息队列。
int mq_unlink(const char *name);
mq_send
:向消息队列发送一个消息。
int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio);
mq_receive
:从消息队列接收一个消息。
int mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio);
mq_getattr
:获取消息队列的属性。
int mq_getattr(mqd_t mqdes, struct mq_attr *__restrict attr);
mq_notify
:注册一个通知,当消息队列中有消息到达时触发。
int mq_notify(mqd_t mqdes, const struct sigevent *__restrict notification);
Linux消息队列的使用通常涉及几个关键步骤:
- 创建或打开队列 :使用
mq_open
函数打开一个现有的消息队列,或者创建一个新的消息队列。 - 发送消息 :使用
mq_send
函数向队列发送消息。 - 接收消息 :使用
mq_receive
函数从队列接收消息。 - 关闭队列 :使用
mq_close
函数关闭消息队列。 - 删除队列 (如果需要):使用
mq_unlink
函数删除消息队列。
此外,Linux消息队列在多种场景下都有广泛的应用,例如:
- 任务调度系统 :主控进程可以将任务信息发送到消息队列,工作者进程从队列中获取并执行任务。
- 实时数据处理 :数据生产者发送实时数据到消息队列,数据消费者从队列中获取并处理这些数据。
- 日志记录系统 :日志产生者发送日志信息到消息队列,日志消费者则从队列中获取并记录这些信息。
- 分布式计算 :在分布式环境中,节点之间可以通过消息队列来通信,实现任务的分发和结果的收集。
总的来说,Linux消息队列提供了一种灵活且高效的进程间通信机制,特别适用于需要在不同进程之间异步传递数据的场景。
消息队列实验:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <mqueue.h>
#define QUEUE_NAME "/my_message_queue"
#define MAX_MSG_SIZE 256
#define MSG_PRIORITY 1
mqd_t create_message_queue() {
mqd_t mq;
struct mq_attr attr;
attr.mq_flags = 0;
attr.mq_maxmsg = 10; // 队列中最大的消息数
attr.mq_msgsize = MAX_MSG_SIZE; // 消息的最大长度
attr.mq_curmsgs = 0;
mq = mq_open(QUEUE_NAME, O_CREAT | O_RDWR, 0644, &attr);
if (mq == (mqd_t) -1) {
perror("mq_open");
exit(1);
}
return mq;
}
void send_message(mqd_t mq, const char *msg) {
if (mq_send(mq, msg, strlen(msg) + 1, MSG_PRIORITY) == -1) {
perror("mq_send");
exit(1);
}
}
void receive_message(mqd_t mq) {
char buffer[MAX_MSG_SIZE];
ssize_t bytes_read;
unsigned int priority;
bytes_read = mq_receive(mq, buffer, MAX_MSG_SIZE, &priority);
if (bytes_read == -1) {
perror("mq_receive");
exit(1);
}
buffer[bytes_read] = '\0'; // 确保字符串正确终止
printf("Received message: %s\n", buffer);
}
int main() {
pid_t pid1, pid2;
mqd_t mq;
mq = create_message_queue();
pid1 = fork();
if (pid1 == -1) {
perror("fork");
exit(1);
} else if (pid1 == 0) {
// 子进程(发送者)
send_message(mq, "Hello from sender!");
exit(0);
}
pid2 = fork();
if (pid2 == -1) {
perror("fork");
exit(1);
} else if (pid2 == 0) {
// 另一个子进程(接收者)
sleep(1); // 等待发送者发送消息
receive_message(mq);
exit(0);
}
// 父进程等待子进程结束
waitpid(pid1, NULL, 0);
waitpid(pid2, NULL, 0);
while(1);
// 关闭并删除消息队列
mq_close(mq);
mq_unlink(QUEUE_NAME);
return 0;
}
实验结果: