首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

消息隊列實戰:參數化進程角色與同步協議的完整實作

     編輯:關於C語言

實作架構設計

採用 單一程式碼、參數決定角色 的模式:

  • -w:發送者 (Writer) — 向隊列寫入訊息
  • -r:接收者 (Reader) — 從隊列讀取訊息

關鍵實作步驟

  1. 定義訊息結構體 (首欄位 long mtype)
  2. 使用固定 key (111) 建立/獲取隊列
  3. 發送端:填充數據 → msgsnd 阻塞發送
  4. 接收端:msgrcv 阻塞接收 → 顯示數據
  5. 清理:接收端最後刪除隊列 (IPC_RMID)

核心程式碼邏輯

#define MSG_KEY 111
#define MAX_TEXT 100

struct msgbuf {
    long mtype;
    char mtext[MAX_TEXT];
};

int main(int argc, char *argv[]) {
    int msqid = msgget(MSG_KEY, 0666 | IPC_CREAT);
    struct msgbuf msg;
    
    if (argc < 2) { printf(\"Usage: %s -w|-r\\n\", argv[0]); return 1; }
    
    if (strcmp(argv[1], \"-w\") == 0) {
        // 發送者
        msg.mtype = 1;
        strcpy(msg.mtext, \"hello message queue\");
        msgsnd(msqid, &msg, strlen(msg.mtext)+1, 0);
        printf(\"write data into message.count:%ld,data:%s\\n\", 
               strlen(msg.mtext)+1, msg.mtext);
    } else if (strcmp(argv[1], \"-r\") == 0) {
        // 接收者
        msgrcv(msqid, &msg, MAX_TEXT, 0, 0);  // 類型 0 = 任意
        printf(\"the data of message .count:%ld,data:%s\\n\", 
               strlen(msg.mtext)+1, msg.mtext);
        msgctl(msqid, IPC_RMID, NULL);  // 清理
    }
    return 0;
}

執行順序與死鎖避免

關鍵約束:必須先運行發送者 (-w),再運行接收者 (-r)。

$ ./msg -w
write data into message.count:3,data:hello message queue

$ ./msg -r
the data of message .count:3,data:hello message queue

原因:本範例未實現同步機制,若先啟動接收者,會阻塞在 msgrcv,導致死鎖 (無發送者喚醒)。

生產級改進方案

1. 非阻塞 + 輪詢避免死鎖

// 接收端:非阻塞嘗試
while (1) {
    int ret = msgrcv(msqid, &msg, MAX_TEXT, 0, IPC_NOWAIT);
    if (ret != -1) {
        printf(\"received: %s\\n\", msg.mtext);
        break;
    }
    if (errno == ENOMSG) {
        sleep(1);  // 等待發送者
        continue;
    }
    perror(\"msgrcv\");
    break;
}

2. 信號量同步雙向通信

// 建立兩個信號量:sem_send (空槽)、sem_recv (滿槽)
// 發送者:P(sem_send) → 寫入 → V(sem_recv)
// 接收者:P(sem_recv) → 讀取 → V(sem_send)
// 實現生產者-消費者模型,彈性控制緩衝區大小

3. 多類型訊息路由

// 定義協議
#define MSG_CMD  1   // 控制命令
#define MSG_DATA 2   // 業務數據
#define MSG_ACK  3   // 確認回覆

// 接收端根據 mtype 分發處理
switch (msg.mtype) {
    case MSG_CMD: handle_command(&msg); break;
    case MSG_DATA: handle_data(&msg); break;
    case MSG_ACK: handle_ack(&msg); break;
}

系統限制與調優

限制常數典型預設值查看/修改方式
MSGMNI16/32/192 (視系統)/proc/sys/kernel/msgmni
MSGMAX8192 bytes/proc/sys/kernel/msgmax
MSGMNB16384 bytes/proc/sys/kernel/msgmnb
# 臨時調大 (需 root)
echo 65536 > /proc/sys/kernel/msgmax
echo 65536 > /proc/sys/kernel/msgmnb

# 永久生效 (/etc/sysctl.conf)
kernel.msgmax = 65536
kernel.msgmnb = 65536
kernel.msgmni = 1024

除錯技巧

  • ipcs -q:列出所有消息隊列
  • ipcrm -q msqid:手動刪除殘留隊列
  • strace -e trace=msgget,msgsnd,msgrcv,msgctl ./prog:追蹤 IPC 呼叫
  • 檢查 errno:EACCES(權限)、EEXIST(已存在)、EIDRM(已刪除)、ENOMSG(無訊息)

現代替代方案對比

方案優勢劣勢適用場景
POSIX MQ (mq_*)非阻塞通知、優先級、標準化需鏈接 -lrt、部分舊系統不支援新專案、跨平台需求
UNIX Domain Socket雙向、傳遞 fd、高效能API 較複雜高效能本地通信
System V MQ廣泛支援、持久化、類型過濾複製開銷、系統限制、API 老舊維護舊代碼、簡單異步通信
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved