9. msgrcv - 从消息队列接收消息 見出しへのリンク
函数介绍 見出しへのリンク
msgrcv
系统调用用于从System V消息队列中接收消息。它允许进程根据消息类型选择性地接收消息,是进程间通信的重要接收机制。
函数原型 見出しへのリンク
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
功能 見出しへのリンク
从指定的消息队列中接收消息。
参数 見出しへのリンク
int msqid
: 消息队列的标识符void *msgp
: 指向接收消息的缓冲区的指针size_t msgsz
: 接收缓冲区中消息正文的最大字节数long msgtyp
: 消息类型选择器= 0
: 接收队列中第一条消息> 0
: 接收指定类型的第一条消息,或最小类型的第一条消息(根据标志)< 0
: 接收类型小于等于|msgtyp|的最大类型的第一条消息
int msgflg
: 控制标志0
: 阻塞模式,如果队列空则等待IPC_NOWAIT
: 非阻塞模式,如果队列空则立即返回错误MSG_NOERROR
: 允许截断过长的消息MSG_EXCEPT
: 排除指定类型的消息(与msgtyp>0配合使用)
返回值 見出しへのリンク
- 成功时返回实际接收到的消息正文的字节数
- 失败时返回-1,并设置errno:
E2BIG
: 消息太大且未设置MSG_NOERROREAGAIN
: 非阻塞模式下队列空EACCES
: 没有读权限EIDRM
: 消息队列已被删除EINTR
: 系统调用被信号中断EINVAL
: 参数无效ENOMSG
: 没有符合条件的消息(非阻塞模式)
相似函数 見出しへのリンク
msgsnd()
: 向消息队列发送消息msgget()
: 获取消息队列标识符msgctl()
: 控制消息队列
示例代码 見出しへのリンク
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#include <sys/wait.h>
// 消息结构体
struct message {
long msg_type; // 消息类型
char msg_data[100]; // 消息内容
};
// 学生信息结构体
struct student_info {
long msg_type;
int student_id;
char name[20];
float score;
};
int main() {
key_t key;
int msgid;
struct message msg;
struct student_info student_msg;
printf("=== Msgrcv函数示例 ===\n");
// 创建消息队列
key = ftok(".", 'h');
if (key == -1) {
perror("ftok失败");
exit(EXIT_FAILURE);
}
// 先清理可能存在的同名消息队列
msgid = msgget(key, 0666);
if (msgid != -1) {
msgctl(msgid, IPC_RMID, NULL);
}
// 创建消息队列
msgid = msgget(key, 0666 | IPC_CREAT | IPC_EXCL);
if (msgid == -1) {
perror("创建消息队列失败");
exit(EXIT_FAILURE);
}
printf("成功创建消息队列,标识符: %d\n", msgid);
// 先发送一些测试消息
printf("\n准备测试消息...\n");
// 发送不同类型的消息
struct message test_msgs[] = {
{1, "这是类型1的消息"},
{2, "这是类型2的消息"},
{3, "这是类型3的消息"},
{1, "这是另一个类型1的消息"},
{5, "这是类型5的消息"}
};
for (int i = 0; i < 5; i++) {
if (msgsnd(msgid, &test_msgs[i], sizeof(test_msgs[i].msg_data), 0) == -1) {
perror("发送测试消息失败");
} else {
printf(" 发送消息: 类型%ld - %s\n", test_msgs[i].msg_type, test_msgs[i].msg_data);
}
}
// 示例1: 接收第一条消息(msgtyp=0)
printf("\n示例1: 接收第一条消息\n");
ssize_t bytes_received = msgrcv(msgid, &msg, sizeof(msg.msg_data), 0, 0);
if (bytes_received == -1) {
perror("接收消息失败");
} else {
printf(" 成功接收消息:\n");
printf(" 类型: %ld\n", msg.msg_type);
printf(" 内容: %s\n", msg.msg_data);
printf(" 字节数: %zd\n", bytes_received);
}
// 示例2: 接收指定类型的消息
printf("\n示例2: 接收指定类型的消息\n");
bytes_received = msgrcv(msgid, &msg, sizeof(msg.msg_data), 2, 0);
if (bytes_received == -1) {
perror("接收类型2消息失败");
} else {
printf(" 成功接收类型2消息:\n");
printf(" 类型: %ld\n", msg.msg_type);
printf(" 内容: %s\n", msg.msg_data);
}
// 示例3: 接收小于等于指定类型的最大类型消息
printf("\n示例3: 接收小于等于指定类型的最大类型消息\n");
bytes_received = msgrcv(msgid, &msg, sizeof(msg.msg_data), -4, 0);
if (bytes_received == -1) {
perror("接收消息失败");
} else {
printf(" 成功接收类型<=4的最大类型消息:\n");
printf(" 类型: %ld\n", msg.msg_type);
printf(" 内容: %s\n", msg.msg_data);
}
// 示例4: 使用MSG_EXCEPT标志
printf("\n示例4: 使用MSG_EXCEPT标志排除指定类型\n");
bytes_received = msgrcv(msgid, &msg, sizeof(msg.msg_data), 1, MSG_EXCEPT);
if (bytes_received == -1) {
perror("接收排除类型1的消息失败");
} else {
printf(" 成功接收非类型1的消息:\n");
printf(" 类型: %ld\n", msg.msg_type);
printf(" 内容: %s\n", msg.msg_data);
}
// 示例5: 接收结构化数据
printf("\n示例5: 接收结构化数据\n");
// 先发送一条学生信息消息
struct student_info send_student = {6, 1002, "李四", 88.5};
if (msgsnd(msgid, &send_student, sizeof(struct student_info) - sizeof(long), 0) == -1) {
perror("发送学生信息失败");
} else {
printf(" 发送学生信息: 学号%d, 姓名%s, 成绩%.1f\n",
send_student.student_id, send_student.name, send_student.score);
}
// 接收学生信息消息
bytes_received = msgrcv(msgid, &student_msg, sizeof(struct student_info) - sizeof(long), 6, 0);
if (bytes_received == -1) {
perror("接收学生信息失败");
} else {
printf(" 成功接收学生信息:\n");
printf(" 学号: %d\n", student_msg.student_id);
printf(" 姓名: %s\n", student_msg.name);
printf(" 成绩: %.1f\n", student_msg.score);
printf(" 字节数: %zd\n", bytes_received);
}
// 示例6: 非阻塞接收模式
printf("\n示例6: 非阻塞接收模式\n");
// 尝试从空队列中非阻塞接收
bytes_received = msgrcv(msgid, &msg, sizeof(msg.msg_data), 0, IPC_NOWAIT);
if (bytes_received == -1) {
if (errno == ENOMSG) {
printf(" 非阻塞接收: 队列为空,立即返回\n");
} else {
perror(" 非阻塞接收失败");
}
} else {
printf(" 非阻塞接收成功\n");
}
// 示例7: 处理大消息和截断
printf("\n示例7: 处理大消息和截断\n");
// 发送一条长消息
struct message long_msg;
long_msg.msg_type = 7;
memset(long_msg.msg_data, 'A', sizeof(long_msg.msg_data) - 1);
long_msg.msg_data[sizeof(long_msg.msg_data) - 1] = '\0';
if (msgsnd(msgid, &long_msg, sizeof(long_msg.msg_data), 0) == -1) {
perror("发送长消息失败");
} else {
printf(" 发送长消息成功\n");
}
// 尝试用小缓冲区接收(会失败)
char small_buffer[50];
struct {
long msg_type;
char msg_data[50];
} small_msg;
bytes_received = msgrcv(msgid, &small_msg, sizeof(small_msg.msg_data), 7, 0);
if (bytes_received == -1) {
if (errno == E2BIG) {
printf(" 接收失败: 消息太大\n");
}
}
// 使用MSG_NOERROR标志允许截断
bytes_received = msgrcv(msgid, &small_msg, sizeof(small_msg.msg_data), 7, MSG_NOERROR);
if (bytes_received == -1) {
perror("使用MSG_NOERROR接收失败");
} else {
printf(" 使用MSG_NOERROR成功接收(可能被截断):\n");
printf(" 接收字节数: %zd\n", bytes_received);
printf(" 内容: %.20s...\n", small_msg.msg_data);
}
// 示例8: 多进程接收消息演示
printf("\n示例8: 多进程接收消息演示\n");
// 发送几条消息供测试
struct message multi_msgs[] = {
{10, "发给进程A的消息"},
{20, "发给进程B的消息"},
{30, "发给任意进程的消息"}
};
for (int i = 0; i < 3; i++) {
if (msgsnd(msgid, &multi_msgs[i], sizeof(multi_msgs[i].msg_data), 0) == -1) {
perror("发送多进程测试消息失败");
}
}
pid_t pid = fork();
if (pid == -1) {
perror("fork失败");
} else if (pid == 0) {
// 子进程 - 接收类型20的消息
printf("子进程 %d 等待接收类型20的消息...\n", getpid());
bytes_received = msgrcv(msgid, &msg, sizeof(msg.msg_data), 20, 0);
if (bytes_received != -1) {
printf("子进程 %d 接收到消息: %s\n", getpid(), msg.msg_data);
}
exit(EXIT_SUCCESS);
} else {
// 父进程 - 接收类型10的消息
sleep(1); // 让子进程先等待
printf("父进程 %d 等待接收类型10的消息...\n", getpid());
bytes_received = msgrcv(msgid, &msg, sizeof(msg.msg_data), 10, 0);
if (bytes_received != -1) {
printf("父进程 %d 接收到消息: %s\n", getpid(), msg.msg_data);
}
// 父进程再接收剩余消息
sleep(1);
bytes_received = msgrcv(msgid, &msg, sizeof(msg.msg_data), 0, IPC_NOWAIT);
if (bytes_received != -1) {
printf("父进程 %d 接收到剩余消息: %s\n", getpid(), msg.msg_data);
}
// 等待子进程结束
wait(NULL);
}
// 示例9: 错误处理演示
printf("\n示例9: 错误处理演示\n");
// 尝试从不存在的消息队列接收消息
bytes_received = msgrcv(999999, &msg, sizeof(msg.msg_data), 0, 0);
if (bytes_received == -1) {
printf(" 从不存在的队列接收消息: %s\n", strerror(errno));
}
// 清理资源
printf("\n清理资源...\n");
if (msgctl(msgid, IPC_RMID, NULL) == -1) {
perror("删除消息队列失败");
} else {
printf("成功删除消息队列\n");
}
return 0;
}