【linux】系統程式設計-2-訊息佇列

李柱明發表於2020-12-29


前言

  • 知識點
    • 訊息佇列、訊號量共享記憶體 被統稱為 system-V IPC
      • 以上都是“持續性”資源,即它們被建立之後, 不會因為程式的退出而消失

4. 訊息佇列

4.1 概念

  • 訊息佇列
    • 訊息佇列提供了一種從一個程式向另一個程式傳送一個資料塊的方法

4.2 對比

  • 訊息佇列與訊號之間的對比
    • 訊號承載的資訊量少,而訊息佇列可以承載大量自定義的資料
  • 訊息佇列與管道之間的對比
    • 相同
      • 程式間通訊都可以是不相關的程式
      • 都可以獨立於傳送和接收程式而存在
      • 在程式終止時,其內容並不會被刪除
    • 不同
      • 在命名管道中,傳送資料用 write(),接收資料用 read(), 則在訊息佇列中,傳送資料用 msgsnd(),接收資料用 msgrcv() ,訊息佇列對每個資料都有一個最大長度的限制
      • 管道只能承載無格式位元組流,訊息佇列提供有格式的位元組流
      • 訊息佇列是面向記錄的,其中的訊息具有特定的格式以及特定的優先順序,接收程式可以通過訊息型別有選擇地接收資料, 而不是像命名管道中那樣,只能預設地接收
      • 訊息佇列可以實現訊息的隨機查詢,訊息不一定要以先進先出的順序接收,也可以按訊息的型別接收

4.3 函式及使用流程

  • 使用流程:
    1. 使用 msgget() 來建立或開啟訊息佇列
    2. 使用 msgsnd() 來傳送訊息到文末
    3. 使用 msgrcv() 來接收訊息,可指定某一條訊息
    4. 使用 msgctl() 來控制訊息(具體往下看

4.3.1 msgget()

  • 使用 msgget() 來建立或開啟訊息佇列
  • 通過命令 man 瞭解更多
  • 函式原型:int msgget(key_t key, int msgflg);
    • key:訊息佇列的關鍵字值,多個程式可以通過它訪問同一個訊息佇列
    • msgflg:表示建立的訊息佇列的模式標誌引數,主要有IPC_CREAT,IPC_EXCL和許可權mode,如:
      • IPC_CREAT:沒有關鍵字 key 的訊息佇列就新建一個,有就直接開啟
      • IPC_CREAT | IPC_EXCL:訊息佇列不存在,則新建一個,如果訊息佇列存在,則報錯
      • IPC_CREAT | 0666:(注:訊息佇列不在意執行許可權)
    • 返回
      • 成功:返回訊息佇列標識值
      • 失敗:返回-1
        • 返回的錯誤碼,即是在變數 error
          • EACCES:訊息佇列存在,但程式沒有訪問許可權
          • EEXIST:msgflg 同時指定了 IPC_CREAT和IPC_EXCL,但是訊息佇列已經存在
          • ENOENT:訊息佇列不存在,且沒有指定 IPC_CREAT 標誌
          • ENOMEM:記憶體不足
          • ENOSPC:訊息佇列個數達到系統的限制

4.3.2 msgsng()

  • 使用 msgsnd() 來傳送訊息到文末
  • 通過命令 man 瞭解更多
  • 函式原型:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
    • msqid:訊息佇列識別符號
    • msgp:傳送給佇列的訊息。msgp可以是任何型別的結構體,但第一個欄位必須為long型別, 即表明此傳送訊息的型別,msgrcv()函式則根據此接收訊息
      /*msgp定義的參照格式*/
      struct s_msg{
      long type;  /* 必須大於0,訊息型別 */
      char mtext[1];  /* 訊息正文,可以是其他任何型別 */
      } msgp;
      
    • msgsz:要傳送訊息的大小,不包含訊息型別佔用的4個位元組
    • msgflg:
      • 0:當訊息佇列滿時,msgsnd() 函式將會阻塞,直到訊息能寫進訊息佇列
      • IPC_NOWAIT:當訊息佇列已滿的時候,msgsnd() 函式不等待立即返回
      • IPC_NOERROR:若傳送的訊息大於size位元組,則把該訊息截斷,截斷部分將被丟棄,且不通知傳送程式
    • 返回
      • 成功:返回 0
      • 失敗:返回 -1,錯誤原因存在於變數 error
        • EAGAIN:引數 msgflg 設為 IPC_NOWAIT,而訊息佇列已滿
        • EIDRM:識別符號為 msqid 的訊息佇列已被刪除
        • EACCESS:無許可權寫入訊息佇列
        • EFAULT:引數 msgp 指向無效的記憶體地址
        • EINTR:佇列已滿而處於等待情況下被訊號中斷
        • EINVAL:無效的引數msqid、msgsz或引數訊息型別type小於0
  • 解除阻塞的三個條件:
    1. 訊息佇列變為未滿
    2. 訊息佇列被刪除
    3. 呼叫 msgsnd() 的程式被訊號中斷

4.3.3 msgrcv()

  • 使用 msgrcv() 來傳送訊息到文末

  • 通過命令 man 瞭解更多

  • 函式原型:ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

    • msqid:訊息佇列識別符號
    • msgp:存放訊息的結構體,結構體型別要與msgsnd()函式傳送的型別相同
    • msgsz:要接收訊息的大小,不包含訊息型別佔用的4個位元組
    • msgtyp:
      • > 0:表示接收型別等於msgtyp的第一個訊息
      • = 0:表示接收第一個訊息
      • < 0:表示接收型別等於或者小於msgtyp絕對值的第一個訊息
    • msgflg:
      • 0:阻塞式接收訊息,沒有該型別的訊息 msgrcv 函式一直阻塞等待
      • IPC_NOWAIT:若在訊息佇列中並沒有相應型別的訊息可以接收,則函式立即返回,此時錯誤碼為ENOMSG
      • IPC_EXCEPT:與 msgtype 配合使用返回佇列中第一個型別不為 msgtype 的訊息
      • IPC_NOERROR:如果佇列中滿足條件的訊息內容大於所請求的 size 位元組,則把該訊息截斷,截斷部分將被丟棄
    • 返回:
      • 成功:返回接收到的訊息長度
      • 失敗:返回 -1,錯誤原因存在於變數 error 中:
        • E2BIG:訊息資料長度大於msgsz而msgflag沒有設定IPC_NOERROR
        • EIDRM:訊息佇列已被刪除
        • EACCESS:無許可權讀取該訊息佇列
        • EFAULT:引數msgp指向無效的記憶體地址
        • ENOMSG:引數msgflg設為IPC_NOWAIT,而訊息佇列中無訊息可讀
        • EINTR:等待讀取佇列內的訊息情況下被訊號中斷
  • 解除阻塞的三個條件:

    1. 訊息佇列中已有符合條件的訊息
    2. 訊息佇列被刪除
    3. 呼叫 msgrcv() 的程式被訊號中斷

4.3.4 msgctl()

  • 使用 msgctl() 來設定或者獲取訊息佇列的相關屬性等等
  • 通過命令 man 瞭解更多
  • 函式原型:int msgctl(int msqid, int cmd, struct msqid_ds *buf);
    • msqid:訊息佇列識別符號
    • cmd:操作命令
      • IPC_STAT:獲取該 MSG 資訊並存到結構體 msqid_ds 型別的 buf 中
      • IPC_SET:設定訊息佇列的屬性,屬性為 msqid_ds(msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes)
      • IPC_RMID:立即刪除該 MSG,並且喚醒所有阻塞在該 MSG上的程式,同時忽略第三個引數
      • IPC_INFO:獲得關於當前系統中 MSG 的限制值資訊
      • MSG_INFO:獲得關於當前系統中 MSG 的相關資源消耗資訊
      • MSG_STAT:同 IPC_STAT,但 msgid 為該訊息佇列在核心中記錄所有訊息佇列資訊的陣列的下標, 因此通過迭代所有的下標可以獲得系統中所有訊息佇列的相關資訊
    • buf:相關資訊結構體緩衝區
    • 返回值
      • 成功:返回 0
      • 失敗:返回 -1,錯誤原因存在 error 中:
        • EACCESS:引數cmd為IPC_STAT,卻無許可權讀取該訊息佇列
        • EFAULT:引數buf指向無效的記憶體地址
        • EIDRM:識別符號為msqid的訊息佇列已被刪除
        • EINVAL:無效的引數cmd或msqid
        • EPERM:引數cmd為IPC_SET或IPC_RMID,卻無執行許可權

4.4 例程

  • 傳送程式
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define BUFFER_SIZE 512
/*定義訊息結構體*/
struct message{
    long msg_type;
    char msg_text[BUFFER_SIZE];
};
int main()
{
    int qid;
    struct message msg;

    /*1. 建立訊息佇列*/    
    if ((qid = msgget((key_t)1234, IPC_CREAT|0666)) == -1)
    {
        perror("msgget\n");
        exit(1);
    }

    /*列印識別符號*/
    printf("Open queue %d\n",qid);

    while(1)
    {
        printf("Enter some message to the queue:");
        if ((fgets(msg.msg_text, BUFFER_SIZE, stdin)) == NULL)
        {
            printf("\nGet message end.\n");
            exit(1);
        }
        /*賦值訊息型別*/
        msg.msg_type = getpid();        
        /*2. 新增訊息到訊息佇列*/        
        if ((msgsnd(qid, &msg, strlen(msg.msg_text), 0)) < 0)
        {
            perror("\nSend message error.\n");
            exit(1);
        }
        else
        {
            printf("Send message.\n");
        }        
        if (strncmp(msg.msg_text, "quit", 4) == 0)
        {
            printf("\nQuit get message.\n");
            break;
        }
    }
    exit(0);
}
  • 接收程式
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define BUFFER_SIZE 512
/*定義訊息結構體,與傳送程式中的一樣*/
struct message{
    long msg_type;
    char msg_text[BUFFER_SIZE];
};
int main()
{
    int qid;
    struct message msg;

    /*建立訊息佇列,鍵值和傳送程式的一樣*/    
    if ((qid = msgget((key_t)1234, IPC_CREAT|0666)) == -1)    
    {
        perror("msgget");
        exit(1);
    }
    /*列印識別符號*/
    printf("Open queue %d\n", qid);
    do
    {
        /*讀取訊息佇列*/
        memset(msg.msg_text, 0, BUFFER_SIZE);
        if (msgrcv(qid, (void*)&msg, BUFFER_SIZE, 0, 0) < 0)        
        {
            perror("msgrcv");
            exit(1);
        }
        printf("The message from process %ld : %s", msg.msg_type, msg.msg_text);
    } while(strncmp(msg.msg_text, "quit", 4));

    /*從系統核心中刪除訊息佇列*/    
    if ((msgctl(qid, IPC_RMID, NULL)) < 0)    
    {
        perror("msgctl");
        exit(1);
    }
    else
    {
        printf("Delete msg qid: %d.\n", qid);
    }
    exit(0);
}

參考:

* 野火

相關文章