Linux程序間通訊訊息佇列
轉載自:https://blog.csdn.net/ljianhui/article/details/10287879
一、什麼是訊息佇列
訊息佇列提供了一種從一個程序向另一個程序傳送一個數據塊的方法。 每個資料塊都被認為含有一個型別,接收程序可以獨立地接收含有不同型別的資料結構。我們可以通過傳送訊息來避免命名管道的同步和阻塞問題。但是訊息佇列與命名管道一樣,每個資料塊都有一個最大長度的限制。
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 512
struct 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);
}
//向訊息佇列中寫訊息,直到寫入end
while(running)
{
//輸入資料
printf("Enter some text: ");
fgets(buffer, BUFSIZ, stdin);
data.msg_type = 1; //注意2
strcpy(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、接收程式可以通過訊息型別有選擇地接收資料,而不是像命名管道中那樣,只能預設地接收。