十五、進程間通訊--消息隊列

一、消息隊列的概念

1.定義:

消息隊列是消息的鏈表,存放在內核中並由消息隊列標識符表示。消息隊列提供了一種從一個進程向另一個進程發送一個數據塊的方法,這個數據塊由消息類型和數據等信息組成。消息隊列遵循先進先出的策略,但因爲存在消息類型,有優先級,故消息隊列類似於一個優先級隊列

內核爲每個消息隊列維護了一個數據結構msqid_ds,用於標識消息隊列,成員有:

struct msqid_ds{
   struct ipc_perm  msg_perm; //權限結構體,規定了權限和所有者
   time_t msg_stime;           //消息隊列最後添加消息的時間
   time_t msg_rtime;           //最後從對隊列中獲取消息的時間
   time_t msg_ctime;           //最後一次改變時間
   unsigned long _msg_cbytes;  //當前輸入的字節數
   msgqnum_t  msg_qnum;        //隊列中消息的數量
   msglen_t msg_qbytes;        //隊列中允許最大字節數
   pid_t msg_lspid;            //最後一次添加的消息
   pid_t msg_lrpid;            //最後一次獲取的消息
   ……
};

通過msg_perm標識一個消息隊列,通過msqid_ds表示一個消息隊列,根據msg_first,msg_last成員維護一個先進先出的msg鏈表隊列(固定類型),每次發送消息時,將其構造成msg結構對象,即自己定義的消息結構體,裏面可以包含消息類型,內容等信息,並添加到msqid_ds成員維護的鏈表隊列中,如下圖所示:
在這裏插入圖片描述
消息隊列和管道類似,在消息長度和數目上都有一定的限制,具體的系統限制如下表所示:

在這裏插入圖片描述
導出的:表示這種限制來源於其他限制,如最大消息數是根據最大隊列數和隊列允許的最大數據量決定的。
可以使用:
cat /proc/sysy/kernel/msgmax 查看具體的數據

2.特點:


  • 消息隊列可以實現消息的隨機讀取,消息的類型不固定時,就不一定以先進先出的次序讀取,可以根據類型的優先級進行讀取。
  • 消息允許一個或多個進程向它寫入或者讀取信息。
  • 與無名管道,有名管道一樣,從消息隊列中讀出消息,消息隊列中對應的數據都會被刪除。
  • 每個消息隊列都有消息隊列標識符,在msg_perm中存儲,是唯一的標識符。
  • 消息隊列是消息的鏈表,存放在內存中,由內核維護,生命週期隨內核,即只有內核重啓或人工刪除消息隊列時,該消息隊列纔會被刪除,若不人工刪除消息隊列,消息隊列會一直存在於系統中。
  • 消息隊列可以雙向通信
  • 克服了管道只能承載無格式字節流的缺點,不會出現數據粘包的現象,因爲消息之間有間隔

二、消息隊列相關函數

利用消息隊列進行進程間銅通訊的方式,我們可以類比爲:A需要發送信息給B,但是因爲距離原因當面給A,那麼就需要A先把信息給XX地址SS驛站,驛站將其存儲,存儲箱編號爲YYYYY,B要取出這個信息,必須到XX地址的SS驛站取出編號爲YYYYY的箱子,打開纔可以獲取。

在消息隊列中,鍵(key)值相當於XX地址消息隊列標識符相當於SS驛站消息類型相當於YYYYY編號的箱子

同一個鍵(key)值可以保證是同一個消息隊列,同一個消息隊列標識符才能保證不同進程可以相互通信,同一個消息類型才能保證某個進程取出的是對方的信息。

1.鍵值
System V提供的進程間通信機制需要一個key值,通過key值就可以在系統內獲得一個唯一的消息隊列標識符, key值可以是人爲指定的數字,強轉爲key_t類型即可,也可以通過ftok 函數獲得。

  • 人爲指定
(key_t)1234;//1234就爲key值,在創建獲取消息隊列函數的第一個參數可以這樣寫
  • 通過函數獲得key值
# include<sys/types.h>
# include<sys/ipc.h>

key_t ftok(const char* pathname,int proj_id);
             成功返回key值,失敗返回-1

參數爲:路徑名;項目ID,非0整數,只有低8位有效。

2. 打開/創建隊列 msgget函數

調用的第一個函數,功能是打開一個現存隊列或者創建一個新隊列,如果多個進程想通過同一個消息隊列完成數據通信,則每個進程使用key值創建或者獲取同一個消息隊列的內核對象ID。key就類似一個標識符。函數原型如下:

# include<sys/msg.h>
int msgget(key_t key,int flag);
           成功則返回消息隊列ID,出錯返回-1

參數:

  • key對應IPC的鍵值,我們可以使用兩種方法來獲得。
  • msgflg:標識函數的行爲及消息隊列的權限,取值如下:
取值 含義
IPC_CREATE 創建消息隊列
IPC_EXCL 檢測消息隊列是否存在
位或權限位 可以設置消息隊列的訪問權限,和其他兩個參數可以或表示,取值和open函數的open_t一樣,一般爲0664

當創建一個新的隊列時,系統會初始化msqid_ds結構的下列成員:

  • 對ipc_perm結構體賦值(該結構體中的mode成員按msgget函數的msgflg參數中的對應權限位設置。
  • msg_qnum、msg_lspid、msg_lrpid、msg_stime、msg_rtime都設置爲0。
  • msg_ctime設置爲當前時間。
  • msg_qbytes設置爲系統限制值。

3.隊列設置函數msgctl

msgctl函數對隊列執行多種操作如修改消息隊列的屬性,刪除消息隊列等,它和另外兩個與信號量和共享存儲函數(semctl,shmctl)都類似於垃圾桶函數,函數原型爲:

# include<sys/msg.h>
int msgctl(int msqid,int cmd,struct msqid_ds* buf);
                 成功返回0,失敗返回-1

參數:

  • msqid:消息標識符,即由msgget返回的消息隊列表示碼

  • cmd,有三個可選的值:
    (1)IPC_STAT:把msqid_ds結構體中個元素的當前值存入到由buf指向的結構體中。
    (2)IPC_SET:把msqid結構體中的元素設置爲buf中的對應值。
    (3)IPC_RMID:從系統中刪除該消息隊列以及仍在該隊列中的所有數據,這種刪除立即生效。

  • buff:msqid_ds數據類型的地址,用於存放或更改消息隊列的屬性。

4.增加數據msgsnd函數

將新消息添加到消息隊列中,函數原型爲:

# include<sys/msg.h>

int msgsnd(int msqid,const void* ptr,size_t nbytes ,int flag)
               成功返回0,出錯返回-1

函數返回成功後,與消息隊列對應的msqid_ds結構得到更新,表明調用的進程ID,調用的時間及隊列中新增的消息。
參數

  • msqid:消息標識符

  • ptr參數:指向一個長整型數,包含了正的整型消息類型,在其後緊跟着消息數據,若nbytes爲0,則無消息數據。若發送的最長消息是512字節,則可定義下列結構:結構體名稱,正文成員自行定義,不固定。

    struct msmesg{
        long  mtype;    //消息類型
        char mtext[512];//實際消息數據,可以設置小於512的大小
        ……              //可以有多個消息正文成員
    };
    

    ptr參數實際指向一個msmesg結構體的指針,注意,這個結構體需要自己聲明在頭文件中,系統並不提供。

  • nbytes:如果ptr指針指向的結構體有兩個以上的成員,則此參數對應ptr指向的結構體中mtext成員大小,表示消息內容的大小。

  • flag:
    (1)默認填0。如果msgsnd函數阻塞,則進程阻塞直到下列情況出現爲止:有空間可以容納要發送的消息,返回EINTR;從系統中刪除此隊列,或捕捉到一個信號,並從信號處理程序返回,返回EIDRM標識符被刪除。

    (2)IPC_NOWAIT:類似文件IO的非阻塞IO標誌,若消息隊列已滿,或者隊列中的消息總數等於系統限制值,或隊列中的字節總數等於系統限制值,則指定IPC_NOWAIT使得msgsnd函數阻塞立即出錯返回EAGAIN

5.獲取消息msgrcv函數

從標識符爲msqid的消息隊列中接收一個消息,一旦接收消息成功,則消息在消息隊列中被刪除。函數原型爲:

# include<sys/msg.h>
ssize_t msgrcv(int msqid,void* ptr,size_t nbytes,long type,int flag)
            成功返回消息的數據部分的長度,出錯返回-1

msgrcv成功執行後,內核更新與該消息隊列相關聯的msqid_ds結構,表明調用的進程ID,調用時間,並指示隊列中的消息減少了1個。
參數:

  • msqid:消息隊列標識符。
  • ptr:和msgsnd函數一樣,ptr指向一個存放消息結構體的地址。
  • nbytes:數據緩衝區長度。若返回的消息長度大於nytes,而且在flag中設置了MSG_NOERROR位,則該消息會被截斷,系統不會通知我們,若沒有設置這一標誌,則出錯返回E2BIG,消息仍留在隊列中。
  • type:執行從消息隊列中取出哪一類型的消息:
    (1)type==0:返回隊列中的第一個消息。
    (2)type>0:返回隊列中消息類型爲type的第一個消息。這時以非先進先出的次序讀消息。
    (3)type<0:返回隊列中消息類型值小於等於type絕對值的消息,如果這種消息有若干個,則取類型值最小的消息。
  • flag參數:
    (1)默認爲0:msgrcv出現阻塞,直到出現下列情況才終止:有了指定類型的消息,從系統中刪除此隊列,返回-1;或捕捉到一個信號並從信號處理程序返回,返回-1。
    (2)IPC_NOWAIT:函數阻塞會立即返回-1。
    (3)MSG_NOERROR:若返回的消息長度大於nytes,則該消息會被截斷,系統不會通知。

我們畫出存在消息類型時的消息隊列圖:
在這裏插入圖片描述
可以看到有兩個消息類型1,2;2的優先級>1的優先級,那麼此時的隊列爲優先級隊列。msgrcv一次只能獲取一條消息,數據之間存在間隔,故不會出現粘連數據的現象。
如果我們要取2類型的數據,因爲2的優先級>1,所以無論前面有多少個1,都無所謂,都可以取出2,但是如果c取1類型的數據,那麼會先取到a進程指向的數據,因爲是一種先進先出的結構,所以會先拿到最開始的1。


6.兩個命令:

ipcs -q  //顯示消息隊列類型
ipcrm -q mspid(消息隊列的id)  //刪除消息隊列類型

三、實例

題目: 利用消息隊列實現連個進程間的通信,A進程給B進程發送指定消息“hello",B進程接收後將其打印,定義結構中的:消息類型爲100,字節大小爲128;

將存儲消息的結構體在頭文件中定義:

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


typedef struct msgdata //定義消息結構體
{
    long mytype;//消息類型
    char madata[128];//消息內容
}MsgData;

A.c

# include"./msg.h" //引入頭文件

int main()
{
    MsgData data;//定義結構體
    memset(&data,0,sizeof(data)); //初始化
    data.mytype=100;
    strcpy(data.madata,"hello");

    int msqid=msgget((key_t)1234,IPC_CREAT|0664);//創建消息隊列
    assert(msqid!=-1);

    msgsnd(msqid,&data,strlen(data.madata),0);//將消息添加到消息隊列中

    exit(0);
    
}

B.c

# include"./msg.h"

int main()
{
    int msqid=msgget((key_t)1234,IPC_CREAT|0664);//必須以相同的key值創建和打開消息隊列
    assert(msqid!=-1);

    MsgData data;
    memset(&data,0,sizeof(data));

    msgrcv(msqid,&data,127,100,0);//從消息隊列中取出消息類型爲100的消息

    printf("read:%s\n",data.madata);//打印消息內容
    exit(0);
    
}

運行測試結果:

1.先運行B,不運行A:
在這裏插入圖片描述
如果先運行B會阻塞,因爲此時沒有消息發送。
在這裏插入圖片描述運行A進程會立馬將消息發送,B打印消息數據。

2.執行A3次之後,再執行B:

在這裏插入圖片描述
如果多次執行A,如執行3次,那麼就會一直往消息隊列中填充數據,消息隊列中由3個內容爲hello的消息。這時開始運行B,則執行一次,讀取一次,而不是一次性讀取3次的數據,執行三次將消息數據讀完,讀完後再執行就會阻塞。

3.消息隊列的建立刪除

如果A創建消息隊列,發送信息後,再利用ipsc -q msqid刪除消息隊列,此時再執行B,B會阻塞,因爲沒有消息,但是B會創建出一個新的消息隊列,只不過消息隊列中沒有信息。如下圖所示:
在這裏插入圖片描述

加油哦!💪。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章