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_NOERROR
    • EAGAIN: 非阻塞模式下队列空
    • 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;
}