1. 程式人生 > >System V IPC 之消息隊列

System V IPC 之消息隊列

指南 oid 消息 如果 bye time_t 信號 lag 一個

消息隊列和共享內存、信號量一樣,同屬 System V IPC 通信機制。消息隊列是一系列連續排列的消息,保存在內核中,通過消息隊列的引用標識符來訪問。使用消息隊列的好處是對每個消息指定了特定消息類型,接收消息的進程可以請求接收下一條消息,也可以請求接收下一條特定類型的消息。

相關數據結構

與其他兩個 System V IPC 通信機制一樣,消息隊列也有一個與之對應的結構,該結構的定義如下:

struct msqid_ds
{
    struct ipc_perm msq_perm;
    struct msg *msg_first;
    struct msg *msg_last;
    
ulong msg_ctypes; ulong msg_qnum; ulong msg_qbytes; pid_t msg_lspid; pid_t msg_lrpid; time_t msg_stime; time_t msg_rtime; time_t msg_ctime; }

該結構中各個字段的說明如下。
msg_perm:對應於該消息隊列的 ipc_perm 結構指針。
msg_first:msg 結構指針,msg 結構用於表示一個消息,此指針指向消息隊列中的第一個消息。
msg_last:msg 結構指針,指向消息隊列中的最後一個消息。


msg_ctypes:記錄消息隊列中當前的總字節數。
msg_qnum:記錄消息隊列中當前的總消息數。
msg_qbytes:記錄消息隊列中最大可容納的字節數。
msg_lspid:最近一個執行 msgsnd 函數的進程的 PID。
msg_lrpid:最近一個執行 msgrcv 函數的進程的 PID。
msg_stime:最近一次執行 msgsnd 函數的時間。
msg_rtime:最近一次執行 msgrcv 函數的時間。
msg_ctime:最近一次改變該消息隊列的時間。
消息隊列所傳遞的消息由兩部分組成,即消息的類型及所傳遞的數據。一般用一個結構體來表示。通常消息類型用一個正的長整數表示,而數據則根據需要設定。比如設定一個傳遞 1024 個字節長度的字符串數據的消息如下:

struct msgbuf
{
    long msgtype;
    char msgtext[1024];
}

傳遞消息時將所傳遞的數據內容寫入 msgtext 中,然後把這個結構體發送到消息隊列中即可。

消息隊列相關的函數

消息隊列的創建與打開
要使用消息隊列,首先要創建一個消息隊列,創建消息隊列的函數聲明如下:

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

int msgget(key_t key, int msgflg);

函數 msgget 用於創建或打開一個消息隊列。其中,參數 key 表示所創建或打開的消息隊列的鍵。參數 msgflg 表示調用函數的操作類型,也可用於設置消息隊列的訪問權限,兩者通過邏輯或表示。調用函數 msgget 所執行的具體操作由參數 key 和 flag 決定。相應約定與 shmget 函數類似。
函數調用成功時,返回值為消息隊列的引用標識符。調用失敗時,返回值為 -1。
當調用 msgget 函數創建一個消息隊列時,它相應的 msqid_ds 結構被初始化。Ipc_perm 中各個字段被設置為相應的值,其中 msg_qnum、msg_lspid、msg_lrpid、msg_stime 和 msg_rt 都被設置為 0,msg_qtypes 被設置為系統限制值,msg_ctime 被設置為當前時間。

向消息隊列中發送消息
接下來我們介紹如何向一個消息隊列中發送消息,發送消息的函數聲明如下:

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

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

此函數的作用是向一個消息隊列中發送消息。該消息將被添加到消息隊列的末尾。參數 msqid 是消息隊列的引用標識符。參數 msgp 是一個 void 指針,指向要發送的消息。參數 msgsz 是以字節數標識的消息數據的長度。參數 msgflg 用於指定消息隊列充滿時的處理方法。當消息隊列充滿時,如果設置了 IPC_NOWAIT 位,就立即出錯返回,否則發送消息的進程被阻塞,直至消息隊列中有空間或該消息隊列被刪除時,函數返回。
msgsnd 函數調用成功時,返回值為 0,調用失敗時,返回值為 -1。

從消息隊列中接收消息
進程要從消息隊列中接收消息時,需要調用 msgrcv 函數,其聲明如下:

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

此函數用於從指定的消息隊列中接收消息。參數 msqid 是消息隊列的引用標識符。參數 msgp 是一個 void 指針,接收到的消息將被存放在 msgp 所指向的緩沖區。參數 msgsz 是以字節數表示的要接收的消息的長度。當消息的實際長度大於這個值時,將根據 msgflg 的設置做出相應的處理。參數 msgtyp 用於表示要接收的消息的類型,其取值和含義如下:
msgtyp=0 接收消息隊列中的第一條消息
msgtyp>0 接收消息隊列中類型值等於 msgtyp 的第一條消息
msgtyp<0 接收消息隊列中類型值小於等於 msgtyp 的絕對值的所有消息類型值最小的消息中的第一條消息
參數 msgflg 用於設定與接收消息相關的信息。
IPC_NOWAIT:指定 msgtyp 無效時的處理方法。當 msgtyp 無效時,如果 IPC_NOWAIT 被設置,則立即出錯返回,否則接收消息的進程將被阻塞,直至 msgtyp 有效或該消息隊列被刪除。
MSG_NOERROR:用於設置消息長度大於 msgsz 時的處理方法。當消息長度大於 msgsz 時,如果 MSG_NOERROR 位被設置,則接收該消息,超出部分被截斷,函數正確返回,否則不接收該消息而將其保留在消息隊列中,出錯返回。
函數 msgrcv 調用成功時,返回值為以字節數表示的接收到的消息數據的長度,調用失敗時,返回值為 -1。

消息隊列的控制
對消息隊列的具體控制操作是通過函數 msgctl 來實現的,其聲明如下:

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

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

其中,參數 msqid 為消息隊列的引用標識符。參數 cmd 表示調用該函數希望執行的操作,其取值和相關說明如下。
IPC_RMID:刪除消息隊列。此命令是立即執行的,如果還有進程對此消息隊列進行操作,則出錯返回。只有有效用戶 ID 和消息隊列的所有者 ID 或創建者 ID 相同的用戶進程,以及超級用戶進程可以執行這一操作。
IPC_SET:按參數 buf 指向的結構中的值設置該消息隊列對應的 msqid_ds 結構。只有有效用戶 ID 和消息隊列的所有者 ID 或創建者 ID 相同的用戶進程,以及超級用戶進程可以執行這一操作。
IPC_STAT:獲得該消息隊列的 msqid_ds 結構,保存於 buf 指向的緩沖區。

應用消息隊列的 demo

下面是一個通過消息隊列進行進程間通信的 demo:

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

int main(void)
{
    int msgid;
    int status;
    char str1[ ]={"test message:hello!"};
    char str2[ ]={"test message:godbye!"};
    struct msgbuf
    {
        long msgtype;
        char msgtext[1024];
    }sndmsg, rcvmsg;

    if((msgid=msgget(IPC_PRIVATE,0666))==-1)
    {
        printf("msgget error!\n");
        exit(254);
    }
    sndmsg.msgtype = 111;
    sprintf(sndmsg.msgtext,"%s", str1);
    if(msgsnd(msgid,(struct msgbuf *)&sndmsg,sizeof(str1)+1,0)==-1)
    {
        printf("msgsnd error!\n");
        exit(254);
    }
    sndmsg.msgtype = 222;
    sprintf(sndmsg.msgtext, "%s", str2);
    if(msgsnd(msgid,(struct msgbuf *)&sndmsg,sizeof(str2)+1,0)==-1)
    {
        printf("msgsnd error!\n");
        exit(254);
    }
    if((status=msgrcv(msgid,(struct msgbuf *)&rcvmsg,80,222,IPC_NOWAIT))==-1)
    {
        printf("msg rcv error!\n");
        exit(254);
    }

    printf("The received message: %s.\n", rcvmsg.msgtext);
    // 下面的代碼會刪除消息隊列,這裏把它註釋掉是為了使用 ipcs 命令進行觀察
    // msgctl(msgid, IPC_RMID,0);
    exit(0);
}

簡單起見,該程序自己完成了消息的發送和接收。由於我們指定了接收消息的類型,所以只有第二條消息會被接收。
把程序代碼保存到文件 msgqueue.c 文件中,並編譯:

$ gcc -Wall msgqueue.c -o msgqueue_demo

然後運行程序:

$ sudo ./msgqueue_demo

技術分享圖片

接收者只收到了類型為 222 的消息。
由於我們註釋了程序中刪除消息隊列的代碼,所以我們還可以通過 ipcs 命令來查看程序中創建的消息隊列:

$ ipcs -q

技術分享圖片

總結

本文以一個極簡的 demo 介紹並演示了 IPC 消息隊列的基本概念和用法,對於了解 IPC 消息隊列我想這些已經足夠了。

參考:
《Linux 環境下 C 編程指南》

System V IPC 之消息隊列