在Linux内核中,System V消息队列和POSIX消息队列的实现原理是不同的。
System V消息队列是通过内核中的一个全局消息队列数组来实现的。每个消息队列都有一个唯一的标识符,可以通过该标识符在进程之间共享。进程可以使用系统调用msgget()来创建或获取一个消息队列,使用msgsnd()向消息队列中发送消息,使用msgrcv()从消息队列中接收消息,使用msgctl()来控制消息队列的属性。
POSIX消息队列是通过内核中的一个文件系统来实现的。每个消息队列都有一个唯一的名称,可以在文件系统中访问。进程可以使用系统调用mq_open()来创建或获取一个消息队列,使用mq_send()向消息队列中发送消息,使用mq_receive()从消息队列中接收消息,使用mq_unlink()来删除消息队列。
虽然System V消息队列和POSIX消息队列的实现原理不同,但它们都提供了类似的功能,可以用于进程之间的通信。
1、System V消息队列
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MSG_SIZE 128
struct msgbuf {
long mtype;
char mtext[MSG_SIZE];
};
int main()
{
key_t key;
int msgid;
struct msgbuf msg;
// 创建消息队列
key = ftok(".", 'a');
msgid = msgget(key, 0666 | IPC_CREAT);
if (msgid == -1) {
perror("msgget");
exit(1);
}
// 发送消息
msg.mtype = 1;
strcpy(msg.mtext, "Hello, world!");
if (msgsnd(msgid, &msg, strlen(msg.mtext)+1, 0) == -1) {
perror("msgsnd");
exit(1);
}
// 接收消息
if (msgrcv(msgid, &msg, MSG_SIZE, 0, 0) == -1) {
perror("msgrcv");
exit(1);
}
printf("Received message: %s\n", msg.mtext);
// 删除消息队列
if (msgctl(msgid, IPC_RMID, NULL) == -1) {
perror("msgctl");
exit(1);
}
return 0;
}
这里面设计四个函数:msgget、msgsnd、msgrcv、msgctl,从函数名其实可以看出来它们的作用。
(1)msgget的用法和之前说过的shmget几乎一致,根据key去分配一个消息队列。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
The msgget() system call returns the System V message queue identifier associated with the value of the key argument. A new message queue is created if key has the value IPC_PRIVATE or key isn't IPC_PRIVATE, no message queue with the given key key exists, and IPC_CREAT is specified in msgflg.
If msgflg specifies both IPC_CREAT and IPC_EXCL and a message queue already exists for key, then msgget() fails with errno set to EEXIST. (This is analogous to the effect of the combination O_CREAT | O_EXCL for open(2).)
(2)msgsnd和msgrcv是一套组合拳,
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
The msgsnd() and msgrcv() system calls are used, respectively, to send messages to, and receive messages from, a System V message queue. The calling process must have write permission on the message queue in order to send a message, and read permission to receive a message.
也因此,msgget的传入参数msgflg带上了0666。
另外关于IPC_NOWAIT这个flag,如果msgsnd和msgrcv没有指定的话,消息队列满了的话msgsnd会阻塞,消息队列空了的话msgrcv会阻塞。如果指定了该flag,那消息队列满了的话msgsnd会返回-1并将errno设置为EAGAIN,msgrcv会返回-1并将errno设置为E2BIG。
(3)msgctl通过不同的控制指令来实现相关功能,传入IPC_RMID则实现移除消息队列的功能。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
2、POSIX消息队列
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <string.h>
#define MAX_SIZE 1024
#define QUEUE_NAME "/my_queue"
int main() {
mqd_t mq;
struct mq_attr attr;
char buffer[MAX_SIZE + 1];
int msg_len, msg_prio;
// 打开或创建消息队列
mq = mq_open(QUEUE_NAME, O_CREAT | O_RDWR, 0666, NULL);
if (mq == (mqd_t) -1) {
perror("mq_open");
exit(1);
}
// 发送消息
if (mq_send(mq, "Hello, Message Queue!", strlen("Hello, Message Queue!"), 0) == -1) {
perror("mq_send");
exit(1);
}
printf("Message sent: Hello, Message Queue!\n");
// 接收消息
msg_len = mq_receive(mq, buffer, MAX_SIZE, &msg_prio);
if (msg_len == -1) {
perror("mq_receive");
exit(1);
}
buffer[msg_len] = '\0';
printf("Message received: %s\n", buffer);
// 关闭消息队列
if (mq_close(mq) == -1) {
perror("mq_close");
exit(1);
}
// 删除消息队列
if (mq_unlink(QUEUE_NAME) == -1) {
perror("mq_unlink");
exit(1);
}
return 0;
}