Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學
您现在的位置: Windows教程網 >> Windows教程 >  >> Linux 系統教學 >> Linux 教學

Linux IPC 詳解:System V 消息隊列編程指南

編輯:Linux 教學

前言

System V IPC 三大件(消息隊列、信號量、共享內存)是 Unix 經典進程間通信機制,Linux 完整支持。消息隊列以「消息」為單位、支持類型過濾、內核持久化,適合結構化數據交換、命令分發等場景。本文系統講解 msgget/msgsnd/msgrcv/msgctl 四大核心調用、數據結構設計、同步模式、常見陷阱及完整雙向通信範例。

一、核心概念

  • 消息隊列標識符 (msqid):內核分配的整數句柄,類似文件描述符。
  • 鍵值:key_t,通常由 ftok(pathname, proj_id) 生成,用於協調無關進程訪問同一隊列。
  • 消息類型:long mtype(必須 > 0),接收時可指定類型實現優先級/分類讀取。
  • 持久性:隊列隨內核存續,進程退出不銷毀,需顯式 msgctl(IPC_RMID) 或重啟機器清除。
  • 限制:單消息最大 MSGMAX(默認 8KB)、隊列最大字節 MSGMNB(默認 16KB)、系統最大隊列數 MSGMNI,可通過 /proc/sys/kernel/msg* 調整。

二、四大系統調用詳解

1. msgget() —— 獲取/創建隊列

#include <sys/msg.h>
int msgget(key_t key, int msgflg);
msgflg 組合行為
IPC_CREAT不存在則創建,存在則返回現有隊列
IPC_CREAT | IPC_EXCL不存在則創建,存在則報錯 EEXIST
0666 | IPC_CREAT創建時設置權限(受 umask 影響)
單獨 IPC_EXCL無意義

返回:成功得 msqid,失敗 -1 設 errno。

2. msgsnd() —— 發送消息

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
  • msgp 指向用戶定義結構,首字段必須是 long mtype。
  • msgsz = sizeof(struct) - sizeof(long)(不含類型字段)。
  • msgflg:
    • 0:隊列滿時阻塞。
    • IPC_NOWAIT:隊列滿立即返回 EAGAIN。

3. msgrcv() —— 接收消息

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
  • msgtyp:
    • > 0:讀取第一個類型等於該值的消息。
    • == 0:讀取隊列第一條消息(不論類型)。
    • < 0:讀取類型值 ≤ |msgtyp| 的第一條消息(實現優先級)。
  • msgflg:
    • 0:無匹配消息時阻塞。
    • IPC_NOWAIT:立即返回 ENOMSG。
    • MSG_NOERROR:消息長度超過 msgsz 時截斷而非報錯。
  • 成功返回實際複製字節數(不含 mtype),隊列中該消息被移除。

4. msgctl() —— 控制隊列

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
cmd功能
IPC_STAT讀取隊列內部 msqid_ds 到 buf
IPC_SET從 buf 設置 msg_perm.uid/gid/mode
IPC_RMID立即刪除隊列,喚醒所有阻塞進程
IPC_INFO獲取系統級限制信息 (Linux 擴展)

struct msqid_ds 關鍵字段:

struct msqid_ds {
    struct ipc_perm msg_perm; // 權限、uid/gid
    time_t msg_stime;         // 最後 msgsnd 時間
    time_t msg_rtime;         // 最後 msgrcv 時間
    time_t msg_ctime;         // 最後 msgctl 時間
    unsigned long msg_cbytes; // 當前隊列字節數
    msgqnum_t msg_qnum;       // 當前消息數
    msglen_t msg_qbytes;      // 隊列最大字節數 (可修改)
    pid_t msg_lspid;          // 最後發送進程 PID
    pid_t msg_lrpid;          // 最後接收進程 PID
};

三、消息結構體設計模式

// 基礎模板
struct mymsg {
    long mtype;       // 必須首字段,>0
    char mtext[256];  // 承載數據
};

// 進階:多類型聯合體
struct msg_payload {
    long mtype;
    union {
        struct { int cmd; int arg1; arg2; } ctrl;
        struct { char data[1024]; } bulk;
        struct { int fd; } fd_pass; // 配合 SCM_RIGHTS 傳遞文件描述符
    } payload;
};

四、完整範例:雙向命令/響應通信

server.c(服務端)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <signal.h>

#define KEY_PATH "/tmp/msgqueue_key"
#define PROJ_ID 0x4D
#define MSG_TYPE_CMD 1
#define MSG_TYPE_RSP 2

struct msgbuf {
    long mtype;
    char text[512];
};

int msqid;

void cleanup(int sig) {
    msgctl(msqid, IPC_RMID, NULL);
    printf("\nServer shutdown, queue removed.\n");
    exit(0);
}

int main() {
    key_t key = ftok(KEY_PATH, PROJ_ID);
    if (key == -1) { perror("ftok"); exit(1); }

    msqid = msgget(key, 0666 | IPC_CREAT);
    if (msqid == -1) { perror("msgget"); exit(1); }

    signal(SIGINT, cleanup);
    signal(SIGTERM, cleanup);

    printf("Server started, msqid=%d, waiting for commands...\n", msqid);

    struct msgbuf msg;
    while (1) {
        // 阻塞讀取命令
        ssize_t n = msgrcv(msqid, &msg, sizeof(msg.text), MSG_TYPE_CMD, 0);
        if (n == -1) {
            if (errno == EINTR) continue; // 信號中斷
            perror("msgrcv");
            break;
        }
        msg.text[n] = '\0';
        printf("Received command: %s\n", msg.text);

        // 處理並回覆
        snprintf(msg.text, sizeof(msg.text), "RSP: %s", msg.text);
        msg.mtype = MSG_TYPE_RSP;
        if (msgsnd(msqid, &msg, strlen(msg.text)+1, 0) == -1) {
            perror("msgsnd rsp");
        }
    }
    cleanup(0);
    return 0;
}

client.c(客戶端)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <unistd.h>

#define KEY_PATH "/tmp/msgqueue_key"
#define PROJ_ID 0x4D
#define MSG_TYPE_CMD 1
#define MSG_TYPE_RSP 2

struct msgbuf {
    long mtype;
    char text[512];
};

int main() {
    key_t key = ftok(KEY_PATH, PROJ_ID);
    if (key == -1) { perror("ftok"); exit(1); }

    int msqid = msgget(key, 0666); // 不創建,僅連接
    if (msqid == -1) { perror("msgget"); exit(1); }

    struct msgbuf msg;
    char input[512];

    printf("Client connected. Type commands (empty to quit):\n");
    while (fgets(input, sizeof(input), stdin)) {
        size_t len = strlen(input);
        if (len <= 1) break;
        input[len-1] = '\0'; // 去換行

        msg.mtype = MSG_TYPE_CMD;
        strncpy(msg.text, input, sizeof(msg.text)-1);
        msg.text[sizeof(msg.text)-1] = '\0';

        if (msgsnd(msqid, &msg, strlen(msg.text)+1, 0) == -1) {
            perror("msgsnd");
            break;
        }

        // 等待響應
        ssize_t n = msgrcv(msqid, &msg, sizeof(msg.text), MSG_TYPE_RSP, 0);
        if (n == -1) { perror("msgrcv"); break; }
        msg.text[n] = '\0';
        printf("Response: %s\n", msg.text);
    }
    return 0;
}

編譯測試

gcc server.c -o server
gcc client.c -o client
# 終端1
./server
# 終端2
./client
# 輸入命令,觀察雙向交互

五、進階技巧與陷阱

主題要點
非阻塞輪詢msgrcv(..., IPC_NOWAIT) + ENOMSG 判斷;或用 select/poll 監聽 /dev/mqueue (Linux 3.5+ 支持),但 System V msgqueue 不直接支持 fd 輪詢。
消息優先級發送時用不同 mtype,接收用負 msgtyp 讀取 ≤ 某值的優先消息。
大消息分片單消息受限 MSGMAX,大數據需應用層分片重組,或改用共享內存+消息隊列通知。
權限問題創建時 0666 & ~umask,不同用戶需同組或 msgctl(IPC_SET) 修改權限。
僵屍隊列清理進程異常退出未調用 IPC_RMID,隊列永久駐留。建議:啟動腳本用 ipcs -q 檢查、ipcrm -q msqid 清理;或守護進程定期掃描無引用隊列。
系統限制調整sysctl -w kernel.msgmax=65536 kernel.msgmnb=65536 kernel.msgmni=32000,持久化寫入 /etc/sysctl.d/99-msgqueue.conf。

六、System V vs POSIX 消息隊列

特性System V (msgget)POSIX (mq_open)
API 風格整數 msqid、key_t文件描述符 mqd_t、路徑名 /name
優先級靠 mtype 模擬原生支持 mq_send 優先級參數
通知機制無(需輪詢/信號)mq_notify 支持信號/線程回調
超時收發無mq_timedsend/receive
持久性內核持久,重啟消失同,或掛載 mqueuefs 可見
推薦場景老代碼維護、跨 Unix 移植新項目、需優先級/通知/超時

結語

System V 消息隊列雖 API 古老,但穩定、跨平台、內核持久化特性使其在嵌入式、老牌 Unix 遷移、簡單命令分發場景仍具生命力。掌握 ftok/msgget/msgsnd/msgrcv/msgctl 全鏈路、消息結構設計、權限與限制調整、僵屍隊列治理,即可靈活應用。新項目優先考慮 POSIX mqueue 或更高級中間件,但在維護既有系統時,本文提供的完整範例與最佳實踐將助你游刃有餘。

Copyright © Windows教程網 All Rights Reserved