目錄
一、消息隊列的基本原理
1、基本概念
2、基本原理
3、消息類型的關鍵作用
4、重要特性總結
5、生命周期管理
6、典型應用場景
二、System V 消息隊列的內核數據結構
1、消息隊列的管理結構?msqid_ds(消息隊列標識符結構)
關鍵字段解析
2、權限控制結構?ipc_perm
權限控制字段解析
3、消息結構
4、消息隊列與共享內存的對比
5、關鍵系統限制
6、總結
三、消息隊列的創建:msgget()?函數
1、函數原型
2、參數解析
key?的生成方式
msgflg?的組成
1. 權限位(低 9 位)
2. 選項標志(高位)
3、返回值
4、典型使用場景
(1) 創建新隊列
(2) 獲取已有隊列
(3) 創建獨占隊列(檢測是否已存在)
5、與共享內存 (shmget) 的對比
6、注意事項
7、完整示例
輸出示例
說明
驗證
8、總結
四、?消息隊列的釋放與管理:msgctl()?函數
1、函數原型
2、參數解析
cmd?的常用選項
3、返回值
4、典型使用場景
(1) 刪除消息隊列(釋放資源)
(2) 查詢隊列狀態
(3) 修改隊列屬性
5、與共享內存 (shmctl) 的對比
6、注意事項
7、完整示例
分析:?
8、總結
五、?向消息隊列發送數據:msgsnd()?函數
1、函數原型
2、參數解析
消息結構體?msgbuf
msgflg?的常用選項
3、返回值
4、使用示例
(1) 定義消息結構
(2) 發送消息(阻塞模式)
(3) 發送消息(非阻塞模式)
5、關鍵注意事項
6、完整代碼示例
代碼邏輯分析
7、總結
六、從消息隊列獲取數據:msgrcv()?函數詳解
1、函數原型
2、參數解析
消息結構體?msgbuf
msgtyp?的接收規則
msgflg?的常用選項
3、返回值
4、使用示例
(1) 定義消息結構
(2) 接收特定類型的消息(阻塞模式)
(3) 接收任意消息(非阻塞模式)
(4) 處理長消息(自動截斷)
5、關鍵注意事項
6、完整代碼示例
關鍵點說明:
注意:
7、總結
一、消息隊列的基本原理
????????System V消息隊列是Unix/Linux系統中一種進程間通信(IPC)機制,允許不相關的進程通過消息隊列交換數據。
1、基本概念
System V消息隊列是一種內核維護的消息鏈表,具有以下特點:
-
消息隊列由唯一的標識符(隊列ID)標識
-
消息是類型化的,每個消息都有一個長整型的類型字段
-
消息可以按照類型順序讀取,而不一定是先進先出(后面會具體介紹)
-
消息隊列是持久的,會持續到系統重啟或顯式刪除
2、基本原理
????????消息隊列是System V IPC機制中的一種進程間通信方式,其本質是在內核空間維護的一個鏈表結構。這個鏈表中的每個節點都是一個獨立的數據塊,每個數據塊包含兩個主要部分:
-
消息類型:一個正整數,用于標識消息的類別
-
消息內容:實際傳輸的數據
通信雙方通過以下機制實現交互:
-
隊列共享:不同進程通過唯一的key訪問同一個消息隊列
-
寫入機制:發送方總是在隊列尾部添加新的消息
-
讀取機制:接收方可以按照特定規則從隊列中獲取消息(不一定先進先出)
3、消息類型的關鍵作用
消息類型決定了消息的歸屬和讀取方式:
-
發送方標識:通過指定不同類型的值來區分消息的接收者
-
選擇性接收:接收方可以指定只接收特定類型的消息
-
優先級控制:類型值也可以用于實現消息優先級(數值越小優先級越高)
4、重要特性總結
- 消息隊列提供了一個從一個進程向另一個進程發送數據塊的方法。
-
結構化通信:支持類型化數據塊(每個數據塊都被認為是有一個類型的,接收者進程接收的數據塊可以有不同的類型值)的傳輸,比管道等字節流通信更結構化
-
異步通信:發送和接收進程不需要同時存在
-
多路復用:單個隊列可支持多個進程間的多種消息交換
-
內核持久性:消息會一直保留在隊列中直到被讀取(和共享內存一樣,消息隊列的資源也必須自行刪除,否則不會自動清除,因為system V IPC資源的生命周期是隨內核的)
-
容量限制:受系統配置參數限制(如MSGMAX, MSGMNB等)
5、生命周期管理
與System V IPC的其他資源相同:
-
顯式刪除:必須通過
msgctl(..., IPC_RMID,...)
主動刪除 -
查看命令:使用
ipcs -q
查看現有消息隊列 -
清理命令:可通過
ipcrm -q
刪除指定的隊列
6、典型應用場景
-
生產者-消費者模型
-
多進程事件通知
-
跨進程的任務分發系統
-
需要保證消息邊界的通信場景
補充說明:
????????雖然POSIX也定義了消息隊列接口(mq_*系列函數),但System V消息隊列在現有系統中仍廣泛使用,兩者在實現和特性上有顯著差異。
二、System V 消息隊列的內核數據結構
1、消息隊列的管理結構?msqid_ds(
消息隊列標識符結構)
????????System V 消息隊列在內核中通過?msqid_ds
?結構體進行管理(內核為每個消息隊列維護一個msqid_ds
結構),該結構體存儲了消息隊列的所有關鍵信息,定義在?<linux/msg.h>
?中:
struct msqid_ds {struct ipc_perm msg_perm; // 權限控制結構struct msg *msg_first; // 指向隊列中的第一個消息(內核內部使用)struct msg *msg_last; // 指向隊列中的最后一個消息(內核內部使用)__kernel_time_t msg_stime; // 最后一次調用 `msgsnd()` 的時間(發送時間)__kernel_time_t msg_rtime; // 最后一次調用 `msgrcv()` 的時間(接收時間)__kernel_time_t msg_ctime; // 最后一次修改隊列的時間(如權限變更)unsigned long msg_lcbytes; // 保留字段(32位兼容)unsigned long msg_lqbytes; // 保留字段(32位兼容)unsigned short msg_cbytes; // 當前隊列中的總字節數unsigned short msg_qnum; // 當前隊列中的消息數量unsigned short msg_qbytes; // 隊列允許的最大字節數(`msg_qbytes ≤ MSGMNB`)__kernel_ipc_pid_t msg_lspid; // 最后一個執行 `msgsnd()` 的進程PID__kernel_ipc_pid_t msg_lrpid; // 最后一個執行 `msgrcv()` 的進程PID
};
關鍵字段解析
-
msg_perm
:權限控制結構,決定哪些進程可以訪問該消息隊列。 -
msg_first
?/?msg_last
:內核內部使用,指向消息鏈表(用戶層不可見)。 -
時間戳(
msg_stime
?/?msg_rtime
?/?msg_ctime
):記錄消息隊列的關鍵操作時間,可用于監控和調試。 -
msg_qnum
?和?msg_cbytes
:-
msg_qnum
:當前隊列中的消息數量。 -
msg_cbytes
:當前隊列占用的總字節數(所有消息大小之和)。
-
-
msg_qbytes
:隊列的最大容量(字節數),可通過?msgctl(IPC_SET)
?調整,但不得超過系統限制(MSGMNB
)。 -
msg_lspid
?/?msg_lrpid
:記錄最后一個發送(msgsnd
)和接收(msgrcv
)消息的進程PID,便于調試。
2、權限控制結構?ipc_perm
????????可以看到消息隊列數據結構的第一個成員是msg_perm
,它和shm_perm
是同一個類型的結構體變量。ipc_perm
?是 System V IPC 機制的通用權限控制結構,定義在?<linux/ipc.h>
?中,用于管理消息隊列、共享內存和信號量的訪問權限:
struct ipc_perm {__kernel_key_t key; // 創建 IPC 資源時指定的 key(`ftok()` 生成)__kernel_uid_t uid; // 所有者的用戶ID__kernel_gid_t gid; // 所有者的組ID__kernel_uid_t cuid; // 創建者的用戶ID__kernel_gid_t cgid; // 創建者的組ID__kernel_mode_t mode; // 訪問權限(如 0644)unsigned short seq; // 序列號(內核用于管理 IPC 標識符)
};
權限控制字段解析
-
key
:用于生成 IPC 標識符(msgget()
?的第一個參數),通常由?ftok()
?生成。 -
uid
?/?gid
:資源當前的所有者(可通過?msgctl(IPC_SET)
?修改)。 -
cuid
?/?cgid
:資源的創建者(不可修改)。 -
mode
:-
訪問權限位,格式類似文件權限(如?
0600
?表示僅所有者可讀寫)。 -
可通過?
msgctl(IPC_SET)
?修改。
-
3、消息結構
發送和接收消息時使用的結構:
struct msgbuf {long mtype; // 消息類型,必須>0char mtext[1]; // 消息數據(實際長度可變)
};
4、消息隊列與共享內存的對比
特性 | 消息隊列 (msqid_ds ) | 共享內存 (shmid_ds ) |
---|---|---|
數據結構 | 鏈表結構,存儲消息塊 | 線性內存區域 |
訪問方式 | 類型化消息(msgrcv ?按類型讀取) | 直接內存讀寫 |
同步需求 | 自帶消息邊界,無需額外同步 | 通常需要信號量同步 |
內核持久性 | 消息保留直到被讀取 | 內存段持久,直到顯式刪除 |
適用場景 | 結構化通信、異步通知 | 大數據量、高性能共享 |
5、關鍵系統限制
-
MSGMAX
:單個消息的最大長度(字節),可通過?sysctl kernel.msgmax
?查看。 -
MSGMNB
:單個消息隊列的最大容量(字節),可通過?sysctl kernel.msgmnb
?查看。 -
MSGMNI
:系統范圍內最大消息隊列數量,可通過?sysctl kernel.msgmni
?查看。
6、總結
-
System V 消息隊列通過?
msqid_ds
?結構管理,包含隊列狀態、時間戳、權限等信息。 -
ipc_perm
?控制訪問權限,確保安全性。 -
消息隊列適用于結構化、異步的進程間通信,但需注意手動釋放資源(
msgctl(IPC_RMID)
)。 -
與共享內存相比,消息隊列自帶消息邊界,但性能較低,適用于中小數據量通信。
三、消息隊列的創建:msgget()
?函數
1、函數原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgget(key_t key, int msgflg);
2、參數解析
參數 | 類型 | 說明 |
---|---|---|
key | key_t | 消息隊列的唯一鍵值,通常由?ftok() ?生成,或使用?IPC_PRIVATE ?創建私有隊列。 |
msgflg | int | 控制隊列創建的標志位,由權限位(如?0644 )和選項標志(如?IPC_CREAT )按位或(|)組合。 |
key
?的生成方式
-
ftok()
?生成(推薦)
通過文件路徑和項目ID生成唯一鍵值:key_t key = ftok("/path/to/file", 'A'); // 'A' 是項目ID(1~255)
-
IPC_PRIVATE
創建一個僅當前進程可訪問的私有隊列(通常用于父子進程間通信):
key_t key = IPC_PRIVATE;
msgflg
?的組成
????????在?System V IPC(如消息隊列、共享內存、信號量)中,msgflg
?參數是一個整型(int
),它由?權限位(低 9 位)?和?選項標志(高位)?組合而成,通過?按位或(|
)?運算來設置。
1. 權限位(低 9 位)
-
作用:控制消息隊列的訪問權限(類似 Linux 文件權限)。
-
格式:一個?9 位?的八進制數,結構如下:
位組 含義 示例 0700
所有者(User)權限 rwx
(讀/寫/執行)0070
組(Group)權限 rw-
(讀/寫)0007
其他用戶(Others)權限 r--
(只讀)示例:
-
0644
?→ 所有者可讀可寫(6
?=?110
),組和其他用戶只讀(4
?=?100
)。 -
0600
?→ 僅所有者可讀可寫,其他用戶無權限。
-
2. 選項標志(高位)
標志 | 作用 | 示例 |
---|---|---|
IPC_CREAT | 如果隊列不存在,則創建;否則直接返回現有隊列標識符。 | IPC_CREAT | 0644 |
IPC_EXCL | 與?IPC_CREAT ?聯用,若隊列已存在則返回錯誤(EEXIST )。 | IPC_CREAT | IPC_EXCL | 0644 |
IPC_NOWAIT | 非阻塞模式,若隊列滿/空時立即返回錯誤(ENOMSG )。 | IPC_CREAT | IPC_NOWAIT | 0644 |
3、返回值
返回值 | 說明 |
---|---|
成功 | 返回消息隊列的標識符(非負整數)(一個有效的消息隊列標識符(用戶層標識符),類似共享內存的shmid),用于后續操作(如?msgsnd /msgrcv )。 |
失敗 | 返回?-1 ,并設置?errno (如?EACCES ?權限不足、ENOENT ?隊列不存在)。 |
4、典型使用場景
(1) 創建新隊列
key_t key = ftok("/tmp/msgqueue", 65);
int msqid = msgget(key, IPC_CREAT | 0666);
if (msqid == -1)
{perror("msgget failed");exit(EXIT_FAILURE);
}
(2) 獲取已有隊列
int msqid = msgget(key, 0666); // 不指定 IPC_CREAT,若隊列不存在則失敗
(3) 創建獨占隊列(檢測是否已存在)
int msqid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);
if (msqid == -1 && errno == EEXIST)
{printf("Message queue already exists.\n");
}
5、與共享內存 (shmget
) 的對比
特性 | 消息隊列 (msgget ) | 共享內存 (shmget ) |
---|---|---|
鍵值生成 | 同樣使用?ftok() ?或?IPC_PRIVATE | 相同 |
權限控制 | 通過?msgflg ?的低9位設置 | 相同 |
創建標志 | IPC_CREAT /IPC_EXCL | 相同 |
資源類型 | 消息鏈表結構 | 線性內存段 |
6、注意事項
-
鍵值沖突:不同進程使用相同的?
key
?會訪問同一隊列,需確保?ftok()
?的參數唯一。 -
權限管理:若權限不足(如?
msgflg=0600
),其他進程無法訪問隊列。 -
資源泄漏:消息隊列不會自動釋放,需通過?
msgctl(msqid, IPC_RMID, NULL)
?手動刪除。 -
系統限制:隊列數量受?
MSGMNI
?限制,可通過?sysctl kernel.msgmni
?查看。
7、完整示例
#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>int main()
{key_t key = ftok("/tmp/msgqueue", 65);if (key == -1) {perror("ftok failed");exit(EXIT_FAILURE);}int msqid = msgget(key, IPC_CREAT | 0666);if (msqid == -1) {perror("msgget failed");exit(EXIT_FAILURE);}printf("Message queue created with ID: %d\n", msqid);return 0;
}
輸出示例
Message queue created with ID: 0
(實際?msqid?由內核動態分配)
說明
-
ftok()
?成功:-
文件?
/home/hmz/learn4/demo1.c
?存在且可讀。 -
key
?計算成功(基于文件的?inode
?和?proj_id=65
)。
-
-
msgget()
?成功:-
如果消息隊列?不存在,則創建新隊列,返回其標識符?
msqid
。 -
如果隊列?已存在,則直接返回其?
msqid
(不會報錯,因為未使用?IPC_EXCL
)。
-
驗證
運行?ipcs -q
?可查看新創建的消息隊列:(key
?由?ftok()
?生成,msqid
?由內核分配)
ipcs -q
8、總結
-
msgget()
?是創建/訪問消息隊列的入口函數,依賴?key
?和權限標志。 -
需合理管理隊列生命周期,避免資源泄漏。
-
適用于需要結構化通信的場景(如生產者-消費者模型)。
四、?消息隊列的釋放與管理:msgctl()
?函數
1、函數原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgctl(int msqid, int cmd, struct msqid_ds *buf);
2、參數解析
參數 | 類型 | 說明 |
---|---|---|
msqid | int | 消息隊列標識符,由?msgget() ?返回。 |
cmd | int | 控制命令,決定該操作的類型(如刪除、查詢或設置隊列屬性)。 |
buf | struct msqid_ds* | 用于存儲或修改隊列狀態信息的緩沖區(部分命令需傳入,部分命令可設為?NULL )。 |
說明一下:
????????msgctl函數的參數與釋放共享內存時使用的shmctl函數的三個參數相同,只不過msgctl函數的第三個參數傳入的是消息隊列的相關數據結構。?
cmd
?的常用選項
命令 | 作用 |
---|---|
IPC_RMID | 立即刪除消息隊列,并喚醒所有阻塞的讀寫進程(errno ?設為?EIDRM )。buf ?參數可設為?NULL 。 |
IPC_STAT | 獲取隊列的當前狀態信息(如權限、消息數量等),存儲到?buf ?指向的?msqid_ds ?結構體中。 |
IPC_SET | 修改隊列屬性(如權限?msg_perm.mode ?或最大容量?msg_qbytes ),需通過?buf ?傳入新值。 |
3、返回值
返回值 | 說明 |
---|---|
成功 | 返回?0 。 |
失敗 | 返回?-1 ,并設置?errno (如?EINVAL ?無效隊列ID、EPERM ?權限不足)。 |
4、典型使用場景
(1) 刪除消息隊列(釋放資源)
if (msgctl(msqid, IPC_RMID, NULL) == -1)
{perror("msgctl(IPC_RMID) failed");exit(EXIT_FAILURE);
}
printf("Message queue %d deleted.\n", msqid);
(2) 查詢隊列狀態
struct msqid_ds info;
if (msgctl(msqid, IPC_STAT, &info) == -1)
{perror("msgctl(IPC_STAT) failed");exit(EXIT_FAILURE);
}
printf("Queue stats: %d messages, %d bytes max.\n", info.msg_qnum, info.msg_qbytes);
(3) 修改隊列屬性
struct msqid_ds info;
msgctl(msqid, IPC_STAT, &info); // 先獲取當前狀態
info.msg_perm.mode = 0600; // 修改權限為僅所有者可讀寫
info.msg_qbytes = 8192; // 調整隊列最大容量為8KB
if (msgctl(msqid, IPC_SET, &info) == -1)
{perror("msgctl(IPC_SET) failed");
}
5、與共享內存 (shmctl
) 的對比
特性 | 消息隊列 (msgctl ) | 共享內存 (shmctl ) |
---|---|---|
刪除命令 | IPC_RMID | 相同 |
查詢命令 | IPC_STAT (返回?msqid_ds ?結構) | IPC_STAT (返回?shmid_ds ?結構) |
設置命令 | IPC_SET (修改權限或容量) | 相同 |
關鍵差異 | 管理消息鏈表結構 | 管理內存段屬性 |
6、注意事項
-
權限要求
-
刪除隊列(
IPC_RMID
)需具備所有者或超級用戶權限。 -
修改屬性(
IPC_SET
)需與原權限匹配。
-
-
資源釋放時機:即使所有進程退出,消息隊列仍會保留,必須顯式調用?
IPC_RMID
?刪除。 -
阻塞進程的處理:刪除隊列會喚醒所有阻塞在?
msgsnd
/msgrcv
?的進程,并使其返回錯誤(EIDRM
)。 -
內核限制:隊列刪除后,其標識符可能被后續新建的隊列復用,需避免誤操作。
7、完整示例
#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
#define PATHNAME "/home/hmz/learn4/demo2.c"int main()
{key_t key = ftok(PATHNAME, 65);int msqid = msgget(key, IPC_CREAT | 0666);if (msqid == -1) {perror("msgget failed");exit(EXIT_FAILURE);}// 刪除隊列if (msgctl(msqid, IPC_RMID, NULL) == -1) {perror("msgctl(IPC_RMID) failed");exit(EXIT_FAILURE);}printf("Message queue %d released.\n", msqid);return 0;
}
分析:?
-
ftok()函數:
-
使用給定的路徑名
/home/hmz/learn4/demo2.c
和項目ID65
生成一個唯一的鍵值(key)。 -
如果文件存在且可訪問,ftok()會成功返回一個key_t類型的鍵;否則返回-1。
-
-
msgget()函數:
-
使用生成的key創建一個新的消息隊列或訪問已存在的隊列。
-
IPC_CREAT | 0666
表示如果隊列不存在則創建,并設置權限為0666(所有用戶可讀寫)。 -
成功時返回消息隊列標識符(msqid),失敗時返回-1。
-
-
msgctl()函數:
-
使用
IPC_RMID
命令刪除消息隊列。 -
成功時返回0,失敗時返回-1。
-
-
可能的運行結果:
-
如果所有操作都成功,程序將輸出:Message queue [隊列ID] released.
其中[隊列ID]是系統分配的消息隊列標識符。
-
可能的錯誤情況:
a) 如果ftok()失敗(如路徑不存在),會輸出錯誤并退出
b) 如果msgget()失敗(如權限不足),會輸出"msgget failed"并退出
c) 如果msgctl()失敗,會輸出"msgctl(IPC_RMID) failed"并退出
-
-
程序特點:
-
這是一個"創建后立即刪除"的示例程序,實際使用中通常不會這樣操作。
-
程序沒有發送或接收任何實際消息,只是演示了消息隊列的創建和刪除。
-
退出時使用了EXIT_FAILURE宏(值為1)表示失敗退出。
-
注意:每次運行程序時,系統可能會分配不同的消息隊列標識符,所以輸出的數字可能會變化。
8、總結
-
msgctl()
?是管理消息隊列的核心函數,支持刪除、查詢和配置操作。 -
IPC_RMID
?是釋放資源的唯一方式,需主動調用以避免內存泄漏。 -
通過?
IPC_STAT
/IPC_SET
?可實現精細化的隊列監控與權限控制。 -
與共享內存的管理接口高度相似,但操作對象為消息鏈表而非內存段。
五、?向消息隊列發送數據:msgsnd()
?函數
1、函數原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
2、參數解析
參數 | 類型 | 說明 |
---|---|---|
msqid | int | 消息隊列標識符,由?msgget() ?返回。 |
msgp | const void* | 指向消息緩沖區的指針,必須符合?msgbuf ?結構。 |
msgsz | size_t | 消息數據的實際長度(字節數),不包括?mtype ?字段。 |
msgflg | int | 發送標志位,控制發送行為(如非阻塞模式)。 |
- 第一個參數msqid,表示消息隊列的用戶級標識符。
- 第二個參數msgp,表示待發送的數據塊。
- 第三個參數msgsz,表示所發送數據塊的大小
- 第四個參數msgflg,表示發送數據塊的方式,一般默認為0即可。
消息結構體?msgbuf
struct msgbuf {long mtype; // 消息類型(必須 > 0)char mtext[1]; // 消息數據(柔性數組,實際長度由用戶定義)
};
-
mtype
:消息類型,接收方可通過該字段篩選消息(值必須為正整數)。 -
mtext
:實際消息數據,定義時可擴展為任意長度(如?char mtext[100]
)。
msgflg
?的常用選項
標志 | 作用 |
---|---|
0 | 默認阻塞模式,若隊列已滿則阻塞,直到空間可用。 |
IPC_NOWAIT | 非阻塞模式,若隊列滿則立即返回?-1 ,并設置?errno ?為?EAGAIN 。 |
3、返回值
返回值 | 說明 |
---|---|
成功 | 返回?0 ,消息被添加到隊列尾部。 |
失敗 | 返回?-1 ,并設置?errno (如?EINVAL ?無效參數、EIDRM ?隊列被刪除、EACCES ?權限不足)。 |
4、使用示例
(1) 定義消息結構
#define MAX_MSG_SIZE 1024struct my_msg {long mtype;char mtext[MAX_MSG_SIZE];
};
(2) 發送消息(阻塞模式)
struct my_msg msg;
msg.mtype = 1; // 設置消息類型
strcpy(msg.mtext, "Hello, Message Queue!");if (msgsnd(msqid, &msg, strlen(msg.mtext) + 1, 0) == -1) {perror("msgsnd failed");exit(EXIT_FAILURE);
}
(3) 發送消息(非阻塞模式)
if (msgsnd(msqid, &msg, strlen(msg.mtext) + 1, IPC_NOWAIT) == -1) {if (errno == EAGAIN) {printf("Queue is full. Try later.\n");} else {perror("msgsnd failed");}
}
5、關鍵注意事項
-
消息大小限制
-
單條消息的長度(
msgsz
)不能超過系統限制?MSGMAX
(可通過?cat /proc/sys/kernel/msgmax
?查看)。 -
隊列總容量受?
MSGMNB
?限制(所有消息的?msgsz
?之和 ≤?msg_qbytes
)。
-
-
內存對齊問題:
msgbuf
?結構中的?mtext
?字段建議定義為柔性數組(C99)或足夠大的靜態數組,避免內存越界。 -
消息類型必須為正數:若?
mtype ≤ 0
,msgsnd()
?會返回?EINVAL
?錯誤。 -
資源競爭處理:多進程同時發送消息時,需通過額外同步機制(如信號量)避免數據混亂。
6、完整代碼示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>
#include <unistd.h>#define MAX_MSG_SIZE 1024
#define PATHNAME "/home/hmz/learn4/demo4.c"struct my_msg
{long mtype;char mtext[MAX_MSG_SIZE];
};int main()
{key_t key = ftok(PATHNAME, 65);int msqid = msgget(key, IPC_CREAT | 0666); // 隊列不存在時自動創建if (msqid == -1){perror("msgget failed");exit(EXIT_FAILURE);}struct my_msg msg;msg.mtype = 1; // 消息類型snprintf(msg.mtext, MAX_MSG_SIZE, "PID %d: Hello, World!", getpid());if (msgsnd(msqid, &msg, strlen(msg.mtext) + 1, 0) == -1){perror("msgsnd failed");exit(EXIT_FAILURE);}printf("Message sent to queue %d.\n", msqid);return 0;
}
代碼邏輯分析
-
生成鍵值 (
ftok
)-
使用文件路徑?
/home/hmz/learn4/demo4.c
?和項目 ID?65
?生成唯一的?key
。 -
如果文件不存在或不可訪問,
ftok
?會失敗,返回?-1
。
-
-
創建/獲取消息隊列 (
msgget
)-
IPC_CREAT | 0666
?表示:-
如果隊列不存在,則創建新隊列;
-
權限設置為?
0666
(所有用戶可讀寫)。
-
-
如果失敗(如權限不足),返回?
-1
。
-
-
構造并發送消息 (
msgsnd
)-
消息類型?
mtype = 1
(可用于接收端篩選消息)。 -
消息內容為字符串?
"PID [進程ID]: Hello, World!"
(例如?"PID 1234: Hello, World!"
)。 -
strlen(msg.mtext) + 1
?表示發送消息長度(包括終止符?\0
)。 -
如果隊列已滿或權限不足,
msgsnd
?會失敗。
-
7、總結
-
msgsnd()
?用于向消息隊列發送結構化數據,需通過?msgbuf
?指定類型和內容。 -
消息長度需明確指定(不包括?
mtype
),且不能超過系統限制。 -
支持阻塞和非阻塞模式,適用于不同實時性要求的場景。
-
務必檢查返回值,確保消息成功入隊。
六、從消息隊列獲取數據:msgrcv()
?函數詳解
1、函數原型
#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);
2、參數解析
參數 | 類型 | 說明 |
---|---|---|
msqid | int | 消息隊列標識符,由?msgget() ?返回。 |
msgp | void* | 輸出型參數,指向接收消息的緩沖區,需符合?msgbuf ?結構(見下文)。 |
msgsz | size_t | 緩沖區?mtext ?的最大容量(字節數),不包括?mtype ?字段。 |
msgtyp | long | 指定接收的消息類型,決定消息篩選規則(見下文)。 |
msgflg | int | 接收標志位,控制接收行為(如非阻塞模式、截斷長消息等)。 |
消息結構體?msgbuf
struct msgbuf {long mtype; // 消息類型(由發送方指定)char mtext[1]; // 消息數據(柔性數組,實際長度由用戶定義)
};
msgtyp
?的接收規則
msgtyp ?值 | 接收行為 |
---|---|
> 0 | 讀取隊列中?第一條類型等于?msgtyp ?的消息。 |
= 0 | 讀取隊列中的?第一條消息(無論類型)。 |
< 0 | 讀取隊列中?類型值 ≤ |msgtyp|?的最小類型的消息(如?-3 ?接收類型為?1 、2 ?或?3 ?的消息)。 |
msgflg
?的常用選項
標志 | 作用 |
---|---|
IPC_NOWAIT | 非阻塞模式,若隊列無匹配消息則立即返回?-1 ,并設置?errno ?為?ENOMSG 。 |
MSG_NOERROR | 若消息實際長度?> msgsz ,則截斷消息(不返回錯誤);未設置時,長消息會觸發?E2BIG ?錯誤。 |
MSG_EXCEPT | 當?msgtyp > 0 ?時,接收?類型不等于?msgtyp ?的第一條消息(Linux 特有擴展)。 |
3、返回值
返回值 | 說明 |
---|---|
成功 | 返回實際接收到的?mtext ?的字節數(不包括?mtype )。 |
失敗 | 返回?-1 ,并設置?errno (如?EINVAL ?無效參數、EIDRM ?隊列被刪除、EACCES ?權限不足)。 |
4、使用示例
(1) 定義消息結構
#define MAX_MSG_SIZE 1024struct my_msg {long mtype;char mtext[MAX_MSG_SIZE];
};
(2) 接收特定類型的消息(阻塞模式)
struct my_msg msg;
ssize_t bytes = msgrcv(msqid, &msg, MAX_MSG_SIZE, 1, 0); // 接收類型為1的消息
if (bytes == -1) {perror("msgrcv failed");exit(EXIT_FAILURE);
}
printf("Received: %s\n", msg.mtext);
(3) 接收任意消息(非阻塞模式)
ssize_t bytes = msgrcv(msqid, &msg, MAX_MSG_SIZE, 0, IPC_NOWAIT);
if (bytes == -1) {if (errno == ENOMSG) {printf("No message available.\n");} else {perror("msgrcv failed");}
}
(4) 處理長消息(自動截斷)
ssize_t bytes = msgrcv(msqid, &msg, 100, 0, MSG_NOERROR); // 最多接收100字節
if (bytes == -1) {perror("msgrcv failed");
} else if (bytes == 100) {printf("Message truncated to 100 bytes.\n");
}
5、關鍵注意事項
-
緩沖區大小
-
msgsz
?必須 ≥ 消息的實際長度(除非使用?MSG_NOERROR
)。 -
建議將?
mtext
?定義為足夠大的數組(如?char mtext[4096]
)。
-
-
消息類型篩選
-
使用?
msgtyp
?可實現優先級消息處理(如類型值越小優先級越高)。 -
msgtyp < 0
?時,內核會遍歷隊列尋找匹配的最小類型消息。
-
-
阻塞與非阻塞
-
默認阻塞模式下,若隊列無匹配消息,進程會休眠等待。
-
IPC_NOWAIT
?適用于需要輪詢的場景。
-
-
隊列刪除處理:若隊列在阻塞期間被刪除(
IPC_RMID
),msgrcv()
?會立即返回?-1
,并設置?errno
?為?EIDRM
。
6、完整代碼示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>
#include <unistd.h>#define MAX_MSG_SIZE 1024
#define PATHNAME "/home/hmz/learn4/demo4.c"struct my_msg
{long mtype;char mtext[MAX_MSG_SIZE];
};int main()
{key_t key = ftok(PATHNAME, 65);int msqid = msgget(key, 0666);if (msqid == -1){perror("msgget failed");exit(EXIT_FAILURE);}struct my_msg msg;if (msgrcv(msqid, &msg, MAX_MSG_SIZE, 1, 0) == -1) // 接收類型為1的消息{perror("msgrcv failed");exit(EXIT_FAILURE);}printf("Received message: %s\n", msg.mtext);// 可選:刪除消息隊列// if (msgctl(msqid, IPC_RMID, NULL) == -1) {// perror("msgctl failed");// exit(EXIT_FAILURE);// }return 0;
}
????????上面第五點的發送代碼,消息隊列中已經有了,下面是一個對應的接收端代碼。這個代碼會從消息隊列中讀取發送的消息:
關鍵點說明:
-
使用相同的
PATHNAME
和項目ID(65)生成相同的key -
使用
msgget
獲取相同的消息隊列ID(不需要IPC_CREAT標志) -
使用
msgrcv
接收類型為1的消息(與發送端設置的mtype匹配) -
接收的消息會存儲在msg結構體中,可以通過msg.mtext訪問消息內容
注意:
-
接收端運行時需要確保消息隊列已存在(即發送端已運行過)
-
如果要刪除消息隊列,可以取消注釋最后的msgctl代碼
-
如果發送端和接收端在不同的終端運行,確保工作目錄相同或PATHNAME路徑正確
7、總結
-
msgrcv()
?是消息隊列的核心接收函數,支持按類型篩選和多種接收模式。 -
通過?
msgtyp
?和?msgflg
?可靈活控制消息選擇策略(如優先級、非阻塞等)。 -
務必檢查返回值和錯誤碼,確保消息完整性和程序健壯性。
-
與?
msgsnd()
?配合使用,可實現進程間結構化通信(如任務分發、事件通知等)。