下面來說說如何用不用訊息佇列來進行進程間的通訊,訊息佇列與具名管道有很多相似之處。有關具名管道的更多內容可以參閱我的另一篇文章:Linux處理序間通訊——使用具名管道一、什麼是訊息佇列訊息佇列提供了一種從一個進程向另一個進程發送一個資料區塊的方法。 每個資料區塊都被認為含有一個類型,接收進程可以獨立地接收含有不同類型的資料結構。我們可以通過發送訊息來避免具名管道的同步和阻塞問題。但是訊息佇列與具名管道一樣,每個資料區塊都有一個最大長度的限制。Linux用宏MSGMAX和MSGMNB來限制一條訊息的最大長度和一個隊列的最大長度。二、在Linux中使用訊息佇列Linux提供了一系列訊息佇列的函數介面來讓我們方便地使用它來實現進程間的通訊。它的用法與其他兩個System V PIC機制,即訊號量和共用記憶體相似。1、msgget函數該函數用來建立和訪問一個訊息佇列。它的原型為:
int msgget(key_t, key, int msgflg);
與其他的IPC機制一樣,程式必須提供一個鍵來命名某個特定的訊息佇列。msgflg是一個許可權標誌,表示訊息佇列的存取權限,它與檔案的存取權限一樣。msgflg可以與IPC_CREAT做或操作,表示當key所命名的訊息佇列不存在時建立一個訊息佇列,如果key所命名的訊息佇列存在時,IPC_CREAT標誌會被忽略,而只返回一個標識符。它返回一個以key命名的訊息佇列的標識符(非零整數),失敗時返回-1.2、msgsnd函數該函數用來把訊息添加到訊息佇列中。它的原型為:
int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
msgid是由msgget函數返回的訊息佇列標識符。msg_ptr是一個指向準備發送訊息的指標,但是訊息的資料結構卻有一定的要求,指標msg_ptr所指向的訊息結構一定要是以一個長整型成員變數開始的結構體,接收函數將用這個成員來確定訊息的類型。所以訊息結構要定義成這樣:
struct my_message{ long int message_type; /* The data you wish to transfer*/};msg_sz是msg_ptr指向的訊息的長度,注意是訊息的長度,而不是整個結構體的長度,也就是說msg_sz是不包括長整型訊息類型成員變數的長度。msgflg用於控制當前訊息佇列滿或隊列訊息到達系統範圍的限制時將要發生的事情。如果調用成功,訊息資料的一分副本將被放到訊息佇列中,並返回0,失敗時返回-1.3、msgrcv函數該函數用來從一個訊息佇列擷取訊息,它的原型為
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);
msgid, msg_ptr, msg_st的作用也函數msgsnd函數的一樣。msgtype可以實現一種簡單的接收優先順序。如果msgtype為0,就擷取隊列中的第一個訊息。如果它的值大於零,將擷取具有相同訊息類型的第一個資訊。如果它小於零,就擷取類型等於或小於msgtype的絕對值的第一個訊息。msgflg用於控制當隊列中沒有相應類型的訊息可以接收時將發生的事情。調用成功時,該函數返回放到接收緩衝區中的位元組數,訊息被複製到由msg_ptr指向的使用者指派的緩衝區中,然後刪除訊息佇列中的對應訊息。失敗時返回-1.4、msgctl函數該函數用來控制訊息隊列,它與共用記憶體的shmctl函數相似,它的原型為:
int msgctl(int msgid, int command, struct msgid_ds *buf);
command是將要採取的動作,它可以取3個值, IPC_STAT:把msgid_ds結構中的資料設定為訊息佇列的當前關聯值,即用訊息佇列的當前關聯值覆蓋msgid_ds的值。 IPC_SET:如果進程有足夠的許可權,就把訊息列隊的當前關聯值設定為msgid_ds結構中給出的值 IPC_RMID:刪除訊息佇列buf是指向msgid_ds結構的指標,它指向訊息佇列模式和存取權限的結構。msgid_ds結構至少包括以下成員:
struct msgid_ds{ uid_t shm_perm.uid; uid_t shm_perm.gid; mode_t shm_perm.mode;};成功時返回0,失敗時返回-1.三、使用訊息佇列進行處理序間通訊馬不停蹄,介紹完訊息佇列的定義和可使用的介面之後,我們來看看它是怎麼讓進程進行通訊的。由於可以讓不相關的進程進行行通訊,所以我們在這裡將會編寫兩個程式,msgreceive和msgsned來表示接收和發送資訊。根據正常的情況,我們允許兩個程式都可以建立訊息,但只有接收者在接收完最後一個訊息之後,它才把它刪除。接收資訊的程式源檔案為msgreceive.c的原始碼為:
#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <string.h>#include <errno.h>#include <sys/msg.h>struct msg_st{long int msg_type;char text[BUFSIZ];};int main(){int running = 1;int msgid = -1;struct msg_st data;long int msgtype = 0; //注意1//建立訊息佇列msgid = msgget((key_t)1234, 0666 | IPC_CREAT);if(msgid == -1){fprintf(stderr, "msgget failed with error: %d\n", errno);exit(EXIT_FAILURE);}//從隊列中擷取訊息,直到遇到end訊息為止while(running){if(msgrcv(msgid, (void*)&data, BUFSIZ, msgtype, 0) == -1){fprintf(stderr, "msgrcv failed with errno: %d\n", errno);exit(EXIT_FAILURE);}printf("You wrote: %s\n",data.text);//遇到end結束if(strncmp(data.text, "end", 3) == 0)running = 0;}//刪除訊息佇列if(msgctl(msgid, IPC_RMID, 0) == -1){fprintf(stderr, "msgctl(IPC_RMID) failed\n");exit(EXIT_FAILURE);}exit(EXIT_SUCCESS);}發送資訊的程式的源檔案msgsend.c的原始碼為:
#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <string.h>#include <sys/msg.h>#include <errno.h>#define MAX_TEXT 512struct msg_st{long int msg_type;char text[MAX_TEXT];};int main(){int running = 1;struct msg_st data;char buffer[BUFSIZ];int msgid = -1;//建立訊息佇列msgid = msgget((key_t)1234, 0666 | IPC_CREAT);if(msgid == -1){fprintf(stderr, "msgget failed with error: %d\n", errno);exit(EXIT_FAILURE);}//向訊息佇列中寫訊息,直到寫入endwhile(running){//輸入資料printf("Enter some text: ");fgets(buffer, BUFSIZ, stdin);data.msg_type = 1; //注意2strcpy(data.text, buffer);//向隊列發送資料if(msgsnd(msgid, (void*)&data, MAX_TEXT, 0) == -1){fprintf(stderr, "msgsnd failed\n");exit(EXIT_FAILURE);}//輸入end結束輸入if(strncmp(buffer, "end", 3) == 0)running = 0;sleep(1);}exit(EXIT_SUCCESS);}運行結果如下:四、例子分析——訊息類型這裡主要說明一下訊息類型是怎麼一回事,注意msgreceive.c檔案main函數中定義的變數msgtype(注釋為注意1),它作為msgrcv函數的接收資訊型別參數的值,其值為0,表示擷取隊列中第一個可用的訊息。再來看看msgsend.c檔案中while迴圈中的語句data.msg_type = 1(注釋為注意2),它用來設定發送的資訊的資訊類型,即其發送的資訊的類型為1。所以程式msgreceive能夠接收到程式msgsend發送的資訊。如果把注意1,即msgreceive.c檔案main函數中的語句由long int msgtype = 0;改變為long int msgtype = 2;會發生什麼情況,msgreceive將不能接收到程式msgsend發送的資訊。因為在調用msgrcv函數時,如果msgtype(第四個參數)大於零,則將只擷取具有相同訊息類型的第一個訊息,修改後擷取的訊息類型為2,而msgsend發送的訊息類型為1,所以不能被msgreceive程式接收。重新編譯msgreceive.c檔案並再次執行,其結果如下:我們可以看到,msgreceive並沒有接收到資訊和輸出,而且當msgsend輸入end結束後,msgreceive也沒有結束,通過jobs命令我們可以看到它還在後台運行著。五、訊息佇列與具名管道的比較訊息佇列跟具名管道有不少的相同之處,通過與具名管道一樣,訊息佇列進行通訊的進程可以是不相關的進程,同時它們都是通過發送和接收的方式來傳遞資料的。在具名管道中,發送資料用write,接收資料用read,則在訊息佇列中,發送資料用msgsnd,接收資料用msgrcv。而且它們對每個資料都有一個最大長度的限制。與具名管道相比,訊息佇列的優勢在於,1、訊息佇列也可以獨立於發送和接收進程而存在,從而消除了在同步具名管道的開啟和關閉時可能產生的困難。2、同時通過發送訊息還可以避免具名管道的同步和阻塞問題,不需要由進程自己來提供同步方法。3、接收程式可以通過訊息類型有選擇地接收資料,而不是像具名管道中那樣,只能預設地接收。