10. msgctl - 消息队列控制 链接到标题

函数介绍 链接到标题

msgctl系统调用用于控制System V消息队列,可以获取和设置消息队列的各种属性和状态。它是消息队列管理的重要工具,用于查询队列信息、修改权限、删除队列等。

函数原型 链接到标题

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

功能 链接到标题

对消息队列执行各种控制操作,包括获取状态、设置属性、删除队列等。

参数 链接到标题

  • int msqid: 消息队列的标识符
  • int cmd: 要执行的控制命令
    • IPC_STAT: 获取消息队列状态信息
    • IPC_SET: 设置消息队列属性
    • IPC_RMID: 删除消息队列
    • IPC_INFO: 获取系统消息队列限制信息
  • struct msqid_ds *buf: 指向msqid_ds结构体的指针(根据命令不同而不同)

返回值 链接到标题

  • 成功时返回0(IPC_STAT, IPC_SET, IPC_RMID)或正数(IPC_INFO)
  • 失败时返回-1,并设置errno

相似函数 链接到标题

  • msgget(): 获取消息队列标识符
  • msgsnd(): 发送消息到消息队列
  • msgrcv(): 从消息队列接收消息

示例代码 链接到标题

#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>

// 消息结构体
struct message {
    long msg_type;
    char msg_data[100];
};

int main() {
    key_t key;
    int msgid;
    struct msqid_ds queue_info;
    struct message msg;
    
    printf("=== Msgctl函数示例 ===\n");
    
    // 创建消息队列
    key = ftok(".", 'i');
    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);
    
    // 示例1: 获取消息队列状态信息(IPC_STAT)
    printf("\n示例1: 获取消息队列状态信息\n");
    if (msgctl(msgid, IPC_STAT, &queue_info) == -1) {
        perror("获取队列状态失败");
    } else {
        printf("  消息队列状态信息:\n");
        printf("    键值: %d\n", queue_info.msg_perm.__key);
        printf("    拥有者UID: %d\n", queue_info.msg_perm.uid);
        printf("    拥有者GID: %d\n", queue_info.msg_perm.gid);
        printf("    创建者UID: %d\n", queue_info.msg_perm.cuid);
        printf("    创建者GID: %d\n", queue_info.msg_perm.cgid);
        printf("    权限: %o\n", queue_info.msg_perm.mode);
        printf("    队列中的消息数: %ld\n", queue_info.msg_qnum);
        printf("    队列中字节数: %ld\n", queue_info.msg_cbytes);
        printf("    队列最大字节数: %ld\n", queue_info.msg_qbytes);
        printf("    最大消息大小: %ld\n", queue_info.msg_mbytes);
        printf("    最后发送时间: %ld\n", queue_info.msg_stime);
        printf("    最后接收时间: %ld\n", queue_info.msg_rtime);
        printf("    最后修改时间: %ld\n", queue_info.msg_ctime);
    }
    
    // 示例2: 发送一些消息用于测试
    printf("\n示例2: 发送测试消息\n");
    
    for (int i = 1; i <= 5; i++) {
        msg.msg_type = i;
        sprintf(msg.msg_data, "这是第%d条测试消息", i);
        
        if (msgsnd(msgid, &msg, sizeof(msg.msg_data), 0) == -1) {
            perror("发送测试消息失败");
        } else {
            printf("  发送消息%d: %s\n", i, msg.msg_data);
        }
    }
    
    // 再次获取状态信息,查看变化
    if (msgctl(msgid, IPC_STAT, &queue_info) == -1) {
        perror("再次获取队列状态失败");
    } else {
        printf("  发送消息后队列状态:\n");
        printf("    队列中的消息数: %ld\n", queue_info.msg_qnum);
        printf("    队列中字节数: %ld\n", queue_info.msg_cbytes);
    }
    
    // 示例3: 修改消息队列权限(IPC_SET)
    printf("\n示例3: 修改消息队列权限\n");
    
    // 先保存原始权限
    mode_t original_mode = queue_info.msg_perm.mode;
    printf("  原始权限: %o\n", original_mode);
    
    // 修改权限为只读
    queue_info.msg_perm.mode = 0444;
    if (msgctl(msgid, IPC_SET, &queue_info) == -1) {
        perror("修改权限失败");
    } else {
        printf("  成功修改权限为只读: %o\n", queue_info.msg_perm.mode);
    }
    
    // 验证权限修改效果
    struct msqid_ds new_info;
    if (msgctl(msgid, IPC_STAT, &new_info) == -1) {
        perror("验证权限修改失败");
    } else {
        printf("  验证权限: %o\n", new_info.msg_perm.mode);
    }
    
    // 恢复原始权限
    new_info.msg_perm.mode = original_mode;
    if (msgctl(msgid, IPC_SET, &new_info) == -1) {
        perror("恢复权限失败");
    } else {
        printf("  成功恢复原始权限: %o\n", new_info.msg_perm.mode);
    }
    
    // 示例4: 获取系统消息队列限制信息(IPC_INFO)
    printf("\n示例4: 获取系统消息队列限制信息\n");
    
    struct msginfo msg_info;
    if (msgctl(msgid, IPC_INFO, (struct msqid_ds *)&msg_info) == -1) {
        perror("获取系统信息失败");
    } else {
        printf("  系统消息队列限制信息:\n");
        printf("    最大消息队列数: %d\n", msg_info.msgpool);
        printf("    最大消息总数: %d\n", msg_info.msgmap);
        printf("    最大队列字节数: %d\n", msg_info.msgmax);
        printf("    最大消息字节数: %d\n", msg_info.msgmnb);
        printf("    最大消息段数: %d\n", msg_info.msgmni);
        printf("    最大消息大小: %d\n", msg_info.msgssz);
        printf("    消息队列数: %d\n", msg_info.msgtql);
        printf("    版本: %d\n", msg_info.msgseg);
    }
    
    // 示例5: 错误处理演示
    printf("\n示例5: 错误处理演示\n");
    
    // 尝试操作不存在的消息队列
    if (msgctl(999999, IPC_STAT, &queue_info) == -1) {
        printf("  操作不存在的队列: %s\n", strerror(errno));
    }
    
    // 尝试使用无效命令
    if (msgctl(msgid, 999, &queue_info) == -1) {
        printf("  使用无效命令: %s\n", strerror(errno));
    }
    
    // 示例6: 删除消息队列(IPC_RMID)
    printf("\n示例6: 删除消息队列\n");
    
    // 在删除前先获取一次状态
    if (msgctl(msgid, IPC_STAT, &queue_info) == -1) {
        perror("删除前获取状态失败");
    } else {
        printf("  删除前队列状态正常\n");
    }
    
    // 删除消息队列
    if (msgctl(msgid, IPC_RMID, NULL) == -1) {
        perror("删除消息队列失败");
    } else {
        printf("  成功删除消息队列 %d\n", msgid);
    }
    
    // 验证删除结果
    if (msgctl(msgid, IPC_STAT, &queue_info) == -1) {
        printf("  验证: 消息队列已不存在 - %s\n", strerror(errno));
    }
    
    // 示例7: 创建多个队列并管理
    printf("\n示例7: 管理多个消息队列\n");
    
    int msgids[3];
    key_t keys[3];
    
    // 创建3个消息队列
    for (int i = 0; i < 3; i++) {
        keys[i] = ftok(".", 'j' + i);
        if (keys[i] == -1) {
            perror("生成键值失败");
            continue;
        }
        
        // 清理可能存在的同名队列
        int temp_id = msgget(keys[i], 0666);
        if (temp_id != -1) {
            msgctl(temp_id, IPC_RMID, NULL);
        }
        
        msgids[i] = msgget(keys[i], 0666 | IPC_CREAT);
        if (msgids[i] == -1) {
            perror("创建消息队列失败");
        } else {
            printf("  创建消息队列%d,标识符: %d\n", i+1, msgids[i]);
        }
    }
    
    // 获取每个队列的信息
    for (int i = 0; i < 3; i++) {
        if (msgids[i] != -1) {
            struct msqid_ds info;
            if (msgctl(msgids[i], IPC_STAT, &info) == -1) {
                perror("获取队列信息失败");
            } else {
                printf("  队列%d信息 - 键值: %d, 消息数: %ld\n", 
                       i+1, info.msg_perm.__key, info.msg_qnum);
            }
        }
    }
    
    // 删除所有队列
    printf("  删除所有队列...\n");
    for (int i = 0; i < 3; i++) {
        if (msgids[i] != -1) {
            if (msgctl(msgids[i], IPC_RMID, NULL) == -1) {
                perror("删除队列失败");
            } else {
                printf("  成功删除队列%d\n", i+1);
            }
        }
    }
    
    return 0;
}