一、消息队列的原理
实现原理:具有相同IPC命名空间(structipc_namespace)的进程能够同时访问IPC命名空间相同内存空间。
Key,标识ID,IPC对象
Key:整型数,用来索引(哈希表)IPC对象,检索失败创建IPC对象并返回标识ID,检索成功返回标识ID。
标识ID:整型数,用来索引(radix树)IPC对象,标识ID并不由Key产生,而是由其他信息(如序号)通过算法产生。
IPC对象:消息队列继承IPC对象,提供一些通用信息。
二、消息队列的特点
1.不受进程的生命周期影响。
2.可以实现多个进程同时进行写入与读出,可以实现并发处理。
3.可以通过消息优先级机制,实现对重要的消息优先处理。# 二、使用步骤
三、所用函数
//1.msgget函数
//创建一个新的消息队列(如果指定IPC_CREAT标志且队列尚不存在)或打开一个已存在的消息队列。
int msgget(key_t key, int msgflg);
//key:由ftok生成的键值,用于标识消息队列。
//msgflg:权限位和标志位的组合,如IPC_CREAT(如果队列不存在则创建)和访问权限(如0666)。
//2msgsnd函数
//功能:发送一个消息到消息队列。
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//msqid:由msgget返回的消息队列标识符。
//msgp:指向要发送的消息的指针,通常是一个结构体指针,如struct mymsgbuf *。
//msgsz:消息正文的字节数,不包括消息类型的字节。
//msgflg:可选标志,如IPC_NOWAIT(非阻塞发送)。
//结构体示例(常用的消息缓冲区结构体):
typedef struct {
long mtype; // 消息类型,长整型,用户自定义,当作优先级来使用
char mtext[100]; // 消息正文,最大长度由用户定义
} mymsgbuf;
//3.msgrcv函数
//功能:从消息队列接收一个消息。
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
//msqid:消息队列标识符。
//msgp:指向接收消息缓冲区的指针,通常是一个结构体指针。
//msgsz:缓冲区能接收的最大消息正文的字节数。
//msgtyp:要接收的消息类型,若为0,则接收任何类型的消息。
//msgflg:可选标志,如IPC_NOWAIT(非阻塞接收),MSG_NOERROR(即使消息过长也不截断)。
//4.msgctl函数
//功能:控制消息队列的各种属性,包括获取状态、修改权限和删除消息队列。
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
//msqid:消息队列的标识符。
//cmd:操作命令,可以是
IPC_STAT(获取队列状态到buf)
IPC_SET(设置队列属性)
IPC_RMID(删除消息队列)
//buf:指向struct msqid_ds结构体的指针,用于存储或修改消息队列的状态信息。
//结构体msqid_ds示例:
struct msqid_ds {
struct ipc_perm msg_perm; // 权限信息
__kernel_time_t msg_stime; // 最后一次消息发送时间
__kernel_time_t msg_rtime; // 最后一次消息接收时间
__kernel_time_t msg_ctime; // 最后一次改变队列时间(权限或状态)
unsigned long msg_cbytes; // 当前队列中消息的总字节数
unsigned long msg_qnum; // 队列中消息的数量
unsigned long msg_qbytes; // 队列的当前最大容量(字节)
pid_t msg_lspid; // 最后发送消息的进程ID
pid_t msg_lrpid; // 最后接收消息的进程ID
};
// ipc_perm结构体定义,嵌套在msqid_ds中
struct ipc_perm {
key_t __key; // 键值,用于标识IPC对象
uid_t uid; // 所有者的用户ID
gid_t gid; // 所有者的组ID
uid_t cuid; // 创建者的用户ID
gid_t cgid; // 创建者的组ID
unsigned short mode; // 权限位,如读、写等
unsigned short __seq; // 序列号,内核使用
unsigned long __pad1; // 填充字段
unsigned long __pad2; // 填充字段
};
其中msgget函数的返回值需要注意,如下表:
四、示例代码
发送端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MSGSIZE 100
typedef struct mymsgbuf {
long mtype; // 消息类型
char mtext[MSGSIZE]; // 消息正文
} mymsgbuf;
int main() {
int msqid; // 消息队列标识符
key_t key; // 键值
mymsgbuf message; // 消息缓冲区
// 使用ftok生成一个键值,基于项目目录下的一个文件路径和一个项目标识符
key = ftok("./sender.c", 'A');
if (key == (key_t)-1) {
perror("ftok");
exit(EXIT_FAILURE);
}
// 尝试创建或打开一个消息队列,如果不存在则创建(IPC_CREAT)
if ((msqid = msgget(key, IPC_CREAT | 0666)) == -1) {
perror("msgget");
exit(EXIT_FAILURE);
}
// 准备消息内容
message.mtype = 1; // 消息类型
strcpy(message.mtext, "Hello from Sender!");
// 发送消息到消息队列
if (msgsnd(msqid, &message, sizeof(message), 0) == -1) {
perror("msgsnd");
exit(EXIT_FAILURE);
}
printf("Message sent.\n");
// 可以在这里添加代码使用msgctl来操作消息队列,比如获取状态或删除消息队列
return 0;
}
接收端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MSGSIZE 100
typedef struct mymsgbuf {
long mtype; // 消息类型
char mtext[MSGSIZE]; // 消息正文
} mymsgbuf;
int main() {
int msqid; // 消息队列标识符
key_t key; // 键值
mymsgbuf message; // 消息缓冲区
// 使用相同的ftok调用来获取与发送进程相同的键值
key = ftok("./sender.c", 'A');
if (key == (key_t)-1) {
perror("ftok");
exit(EXIT_FAILURE);
}
// 打开已存在的消息队列
if ((msqid = msgget(key, 0)) == -1) {
perror("msgget");
exit(EXIT_FAILURE);
}
// 接收消息,这里我们假设只接收类型为1的消息
if (msgrcv(msqid, &message, MSGSIZE, 1, 0) == -1) {
perror("msgrcv");
exit(EXIT_FAILURE);
}
printf("Received message: %s\n", message.mtext);
// 如果不再需要消息队列,可以在这里使用msgctl删除它
// struct msqid_ds buf;
// msgctl(msqid, IPC_RMID, &buf);
return 0;
}
单独展示msgctl的用法
//1.IPC_STAT
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int main() {
int msqid; // 假设这是您已有的消息队列标识符
struct msqid_ds buf;
// 获取消息队列状态
if (msgctl(msqid, IPC_STAT, &buf) == -1) {
perror("msgctl IPC_STAT");
return 1;
}
printf("Message Queue Status:\n");
printf(" msg_perm.uid: %d\n", buf.msg_perm.uid);
printf(" msg_perm.gid: %d\n", buf.msg_perm.gid);
printf(" msg_perm.mode: %o\n", buf.msg_perm.mode);
printf(" msg_qnum: %ld\n", buf.msg_qnum); // 当前队列中的消息数量
printf(" msg_qbytes: %ld\n", buf.msg_qbytes); // 队列的最大容量
// 其他字段打印...
return 0;
}
//2.IPC_SET
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
int main() {
int msqid; // 假设这是您已有的消息队列标识符
struct msqid_ds buf;
// 首先获取当前状态
if (msgctl(msqid, IPC_STAT, &buf) == -1) {
perror("msgctl IPC_STAT");
return 1;
}
// 修改权限,例如,只允许所有者读写
buf.msg_perm.mode = S_IRUSR | S_IWUSR;
// 设置新的权限
if (msgctl(msqid, IPC_SET, &buf) == -1) {
perror("msgctl IPC_SET");
return 1;
}
printf("Message queue permissions set to owner read-write.\n");
return 0;
}
//3.IPC_RMID
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int main() {
int msqid; // 假设这是您已有的消息队列标识符
// 删除消息队列
if (msgctl(msqid, IPC_RMID, NULL) == -1) {
perror("msgctl IPC_RMID");
return 1;
}
printf("Message queue successfully deleted.\n");
return 0;
}