(一)什麼是消息隊列
消息隊列提供了一個進程給另外一個進程發送數據塊的方法,每個數據塊都可以被認爲是由自己的類型的,接受者接受的數據塊可以有不同的類型;不像管道和命名管道那樣,必須以先進先出的方式接收數據。
同管道類似的是,他有一個不足的地方就是每個消息的最大長度是有上限的(MSGMAX),每個仙子隊列的總的字節數(MSGMNB),系統上消息隊列的總數上限(MSGMNI),可以用cat/proc/sys/kernel/msgmax查看具體的數據。
(二)消息隊列的使用
linux消息隊列的API都定義在sys/msg.h頭文件中,其中包括四個系統調用:msgget、msgsnd、msgrcv、msgctl;
2.1msgget系統調用
用來創建一個消息隊列,或者獲取一個已存在的消息隊列,定義如下:
#include <sys/msg.h>
int msgget(key_t, key, int msgflg);
【參數 key】:鍵值,用來標識一個全局唯一的消息隊列。
【參數 msgflg】:一個權限標誌,表示消息隊列的訪問權限,它與文件的訪問權限一樣。msgflg可以與IPC_CREAT做或操作,表示當key所命名的消息隊列不存在時創建一個消息隊列,如果key所命名的消息隊列存時,IPC_CREAT標誌會被忽略,而只返回一個標識符。
【函數返回值】成功返回一個以key命名的消息隊列的標識符(非零整數),失敗時返回-1.
在shmget用於創建內存,與之關聯的內核數據結構shmid_ds將被創建並初始化,shmid_ds結構體的定義和初始化參照《linux高性能服務器編程》263頁瞭解。
2.2msgsnd系統調用
用來將一條信息添加到消息隊列中,定義如下:
#include <sys/msg.h>
int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
【參數 msgid】:是由msgget系統調用返回的消息隊列標識符。
【參數 msg_ptr】:指向一個準備發送消息的指針,消息必須被定義爲如下:
struct my_message
{
long int message_type; /* 消息類型,必須是一個正整數 */
char mtext[512]; /* 消息數據 */
};
【參數 msg_sz】:是指消息的數據部分(mtext)的長度,這個長度可以爲0,代表沒有數據。
【參數 msgflg】:控制msgsnd的行爲。它通常僅支持IPC_NOWAIT標誌,即以非阻塞的方式發送消息。默認情況下,發送消息時如果消息隊列滿了,則msgsnd將會阻塞。若IPC_NOWAIT標誌被指定,則msgsnd將會立即返回並設置werrno爲EAGAIN.具體情況參照《linux高性能服務器編程》264頁。
【函數返回值】:如果調用成功,消息數據的一分副本將被放到消息隊列中,並返回0,失敗時返回-1並設置errno.成功後回修改內核數據結構msqid_ds的部分字段。
2.3msgrcv系統調用
用來從消息隊列中獲取消息,定義如下:
#include <sys/msg.h>
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);
【參數 msgtype】:指定接受哪一種類型的消息。可以實現一種簡單的接收優先級。如果msgtype爲0,就獲取隊列中的第一個消息。如果它的值大於零,將獲取具有相同消息類型的第一個信息。如果它小於零,就獲取類型等於或小於msgtype的絕對值的第一個消息。
【參數 msgflg】:用於控制當隊列中沒有相應類型的消息可以接收時將發生的事情。
【函數返回值】:調用成功時,該函數返回放到接收緩存區中的字節數,消息被複制到由msg_ptr指向的用戶分配的緩存區中,然後刪除消息隊列中的對應消息。失敗時返回-1.
參數msgid, msg_ptr, msg_st的作用和函數msgsnd的參數意義相同一。
2.4msgctl系統調用
用來控制消息隊列的某些屬性,定義如下:
#include <sys/msg.h>
int msgctl(int msgid, int command, struct msgid_ds *buf);
【參數 id】:同上。
【參數 command】:command是將要採取的動作,它可以取6個值,這裏列舉常用的3個:
(1)IPC_STAT:把msgid_ds結構中的數據設置爲消息隊列的當前關聯值,即用消息隊列的當前關聯值覆蓋msgid_ds的值。
(2)IPC_SET:如果進程有足夠的權限,就把消息列隊的當前關聯值設置爲msgid_ds結構中給出的值
(3)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來表示接收和發送信息。根據正常的情況,我們允許兩個程序都可以創建消息,但只有接收者在接收完最後一個消息之後,它才把它刪除。
接受數據源代碼:recv.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);
}
發送數據源代碼:write.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);
}
(四)消息隊列與命名管道的比較
消息隊列跟命名管道有不少的相同之處,通過與命名管道一樣,消息隊列進行通信的進程可以是不相關的進程,同時它們都是通過發送和接收的方式來傳遞數據的。在命名管道中,發送數據用write,接收數據用read,則在消息隊列中,發送數據用msgsnd,接收數據用msgrcv。而且它們對每個數據都有一個最大長度的限制。
與命名管道相比,消息隊列的優勢在於:
1、消息隊列也可以獨立於發送和接收進程而存在,從而消除了在同步命名管道的打開和關閉時可能產生的困難。
2、同時通過發送消息還可以避免命名管道的同步和阻塞問題,不需要由進程自己來提供同步方法。
3、接收程序可以通過消息類型有選擇地接收數據,而不是像命名管道中那樣,只能默認地接收。
參考文章: