消息佇列

為什麼已經擁有了共用記憶體時需要消息佇列呢? 這將是多種原因,讓我們將其分解為多個點來簡化 -

  • 據瞭解,一旦消息被一個進程接收到,它將不再可用於任何其他進程。 而在共用記憶體中,數據可供多個進程訪問。
  • 如果想使用小資訊格式進行通信。
  • 當多個進程同時進行通信時,共用記憶體數據需要同步保護。
  • 使用共用記憶體的寫入和讀取頻率很高,那麼實現功能將會非常複雜。 在這種情況下不值得使用。
  • 如果所有的進程不需要訪問共用記憶體,但是很少的進程只需要它,那麼用消息佇列實現會更好。
  • 如果想要與不同的數據包進行通信,比如進程A正在發送消息類型1給進程B,消息類型10給進程C,消息類型20給進程D。在這種情況下,用消息佇列實現是比較簡單的。 為了將給定的消息類型簡化為1,10,20,它可以是0+ve-ve,如下所述。
  • 當然,消息佇列的順序是FIFO(先進先出)。 插入到佇列中的第一條消息是第一條要檢索的消息。

使用共用記憶體或消息佇列取決於應用程式的需要以及如何有效地使用它。

使用消息佇列的通信可以通過以下方式進行 -

  • 通過一個進程寫入共用記憶體,並由另一個進程讀取共用記憶體。 正如我們所知道的,讀取也可以用多個進程來完成。
  • 通過一個進程用不同的數據包寫入共用記憶體,並通過多個進程,即按照消息類型讀出。

    看到消息佇列中的某些資訊後,現在是檢查支持消息佇列的系統調用(System V)的時候了。

要使用消息佇列執行通信,請執行以下步驟 -

第1步 - 創建一個消息佇列或連接到一個已經存在的消息佇列(msgget())
第2步 - 寫入消息佇列(msgsnd())
第3步 - 從消息佇列中讀取(msgrcv())
第4步 - 對消息佇列(msgctl())執行控制操作

現在,讓我們看看上述調用的語法和某些資訊。

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

int msgget(key_t key, int msgflg)

這個系統調用創建或分配一個System V消息佇列。需要傳遞以下參數 -

  • 第一個參數key用於識別消息佇列。key可以是任意值,也可以是來自庫函數ftok()的值。
  • 第二個參數shmflg指定所需的消息佇列標誌,例如IPC_CREAT(如果不存在則創建消息佇列)或IPC_EXCL(與IPC_CREAT一起用於創建消息佇列,如果消息佇列已經存在,則調用失敗)。 還需要傳遞許可權。

注 - 有關許可權的詳細資訊,請參閱前面幾節。

這個調用會在成功時返回一個有效的消息佇列識別字(用於進一步調用消息佇列),在失敗的情況下返回-1。 要知道失敗的原因,請檢查errno變數或perror()函數。

關於這個調用的各種錯誤是EACCESS(許可權被拒絕),EEXIST(佇列已經存在不能創建),ENOENT(佇列不存在),ENOMEM(沒有足夠的記憶體來創建佇列)等

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

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

此系統調用將消息發送/附加到消息佇列(System V)中。 需要傳遞以下參數 -

  • 第一個參數msgid識別消息佇列,即消息佇列識別字。 msgget()成功時收到識別字的值
  • 第二個參數msgp是發送給調用者的消息的指針,定義在以下形式的結構中 -

    struct msgbuf {
     long mtype;
     char mtext[1];
    };
    

    變數mtype用於與不同的消息類型進行通信,在msgrcv()調用中詳細解釋。 變數mtext是一個數組或其他大小由msgsz(正值)指定的結構。 如果沒有提到mtext字段,則將其視為0大小消息,這是允許的。

  • 第三個參數msgsz是消息的大小(消息應該以空字元結尾)

  • 第四個參數msgflg表示某些標誌,例如IPC_NOWAIT(當在佇列中找不到消息時立即返回)或MSG_NOERROR(截斷消息文本,如果超過msgsz位元組)

這個調用在成功時將返回0,在失敗的情況下為-1。 要知道失敗的原因,請檢查errno變數或perror()函數。

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

int msgrcv(int msgid, const void *msgp, size_t msgsz, long msgtype, int msgflg)

該系統調用從消息佇列(系統V)中檢索消息。 需要傳遞以下參數 -

  • 第一個參數msgid識別消息佇列,即消息佇列識別字。 msgget()成功時收到識別字的值
  • 第二個參數msgp是從調用者接收的消息的指針。 它在以下形式的結構中被定義 -
    struct msgbuf {
     long mtype;
     char mtext[1];
    };
    
    變數mtype用於與不同的消息類型進行通信。 變數mtext是一個數組或其他大小由msgsz(正值)指定的結構。 如果沒有提到mtext字段,則將其視為0大小消息,這是允許的。
  • 第三個參數msgsz是收到的消息的大小(消息應該以空字元結尾)
  • 第四個參數msgtype表示消息的類型 -
    • 如果msgtype0 - 讀取佇列中的第一個收到的消息。
    • 如果msgtype+ve - 讀取類型為msgtype的佇列中的第一條消息(如果msgtype10,則只讀取類型10的第一條消息,即使其他類型可能位於佇列中的開頭)
    • 如果msgtype-ve - 讀取小於或等於消息類型的絕對值的最小類型的第一個消息(例如,如果msgtype-5,則它讀取類型小於5的第一個消息,即消息類型從15)
  • 第五個參數msgflg表示某些標誌,例如IPC_NOWAIT(當佇列中沒有消息時立即返回,或MSG_NOERROR(如果超過了msgsz位元組則截斷消息文本)

這個調用將返回成功時在mtext數組中實際接收的位元組數,在失敗的情況下返回-1。 要知道失敗的原因,請檢查errno變數或perror()函數。

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

int msgctl(int msgid, int cmd, struct msqid_ds *buf)

這個系統調用執行消息佇列(系統V)的控制操作。 需要傳遞以下參數 -

  • 第一個參數msgid識別消息佇列,即消息佇列識別字。 msgget()成功時收到識別字的值
  • 第二個參數cmd是對消息佇列執行所需控制操作的命令。 cmd的有效值是 -
    • IPC_STAT - 將struct msqid_ds的每個成員的當前值的資訊複製到由buf指向的傳遞結構中。 該命令需要消息佇列的讀取許可權。
    • IPC_SET - 設置結構buf指向的用戶ID,所有者的組ID,許可權等。
    • IPC_RMID - 立即刪除消息佇列。
    • IPC_INFO - 返回有關buf指向的結構中的消息佇列限制和參數的資訊,該結構的類型為struct msginfo
    • MSG_INFO - 返回一個msginfo結構,其中包含有關消息佇列消耗的系統資源的資訊。
  • 第三個參數buf是一個指向名為struct msqid_ds的消息佇列結構的指針。 這個結構的值將被用於任一集或者按照cmd得到。

這個調用將根據傳遞的命令返回值。 IPC_INFO和MSG_INFO或MSG_STAT的成功返回消息佇列的索引或識別字,其他操作返回0,失敗時返回-1。 要知道失敗的原因,請檢查errno變數或perror()函數。

上面已經看到有關消息佇列的基本資訊和系統調用,現在是時候來看看程式代碼了。

讓我們看看這個程式實現的描述 -

第1步 - 創建兩個進程,一個用於發送到消息佇列(msgq_send.c),另一個用於從消息佇列(msgq_recv.c)
第2步 - 使用ftok()函數創建鍵(Key)。 為此,最初創建檔msgq.txt以獲取唯一的鍵。
第3步 - 發送過程執行以下操作。

  • 讀取用戶輸入的字串
  • 刪除新行,如果存在
  • 發送到消息佇列
  • 重複這個過程直到輸入結束(CTRL + D)
  • 一旦收到輸入結束,發送消息“end”來表示進程結束。

第4步 - 在接收過程中,執行以下操作。

  • 從佇列中讀取消息
  • 顯示輸出
  • 如果收到的消息是“end”,則結束該過程並退出。

為了簡化,我們沒有使用這個示例的消息類型。 另外,一個進程正在寫入佇列,另一個進程正在從佇列中讀取。 這可以根據需要進行擴展,即理想情況下一個進程將寫入佇列中,多個進程從佇列中讀取。

現在,讓我們看看一下進程(消息發送到佇列) - 檔:msgq_send.c

/* Filename: msgq_send.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define PERMS 0644
struct my_msgbuf {
   long mtype;
   char mtext[200];
};

int main(void) {
   struct my_msgbuf buf;
   int msqid;
   int len;
   key_t key;
   system("touch msgq.txt");

   if ((key = ftok("msgq.txt", 'B')) == -1) {
      perror("ftok");
      exit(1);
   }

   if ((msqid = msgget(key, PERMS | IPC_CREAT)) == -1) {
      perror("msgget");
      exit(1);
   }
   printf("message queue: ready to send messages.\n");
   printf("Enter lines of text, ^D to quit:\n");
   buf.mtype = 1; /* we don't really care in this case */

   while(fgets(buf.mtext, sizeof buf.mtext, stdin) != NULL) {
      len = strlen(buf.mtext);
      /* remove newline at end, if it exists */
      if (buf.mtext[len-1] == '\n') buf.mtext[len-1] = '\0';
      if (msgsnd(msqid, &buf, len+1, 0) == -1) /* +1 for '\0' */
      perror("msgsnd");
   }
   strcpy(buf.mtext, "end");
   len = strlen(buf.mtext);
   if (msgsnd(msqid, &buf, len+1, 0) == -1) /* +1 for '\0' */
   perror("msgsnd");

   if (msgctl(msqid, IPC_RMID, NULL) == -1) {
      perror("msgctl");
      exit(1);
   }
   printf("message queue: done sending messages.\n");
   return 0;
}

執行上面示例代碼,得到以下輸出結果 -

message queue: ready to send messages.
Enter lines of text, ^D to quit:
this is line 1
this is line 2
message queue: done sending messages.

以下是來自消息接收過程的代碼(從佇列中檢索消息) - 檔:msgq_recv.c -

/* Filename: msgq_recv.c */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define PERMS 0644
struct my_msgbuf {
   long mtype;
   char mtext[200];
};

int main(void) {
   struct my_msgbuf buf;
   int msqid;
   int toend;
   key_t key;

   if ((key = ftok("msgq.txt", 'B')) == -1) {
      perror("ftok");
      exit(1);
   }

   if ((msqid = msgget(key, PERMS)) == -1) { /* connect to the queue */
      perror("msgget");
      exit(1);
   }
   printf("message queue: ready to receive messages.\n");

   for(;;) { /* normally receiving never ends but just to make conclusion
             /* this program ends wuth string of end */
      if (msgrcv(msqid, &buf, sizeof(buf.mtext), 0, 0) == -1) {
         perror("msgrcv");
         exit(1);
      }
      printf("recvd: \"%s\"\n", buf.mtext);
      toend = strcmp(buf.mtext,"end");
      if (toend == 0)
      break;
   }
   printf("message queue: done receiving messages.\n");
   system("rm msgq.txt");
   return 0;
}

執行上面示例代碼,得到以下輸出結果 -

message queue: ready to receive messages.
recvd: "this is line 1"
recvd: "this is line 2"
recvd: "end"
message queue: done receiving messages.

上一篇: 共用記憶體 下一篇: 信號量