一、什么是消息隊列
消息隊列提供了一種從一個進程向另一個進程發送一個數據塊的方法。 每個數據塊都被認為含有一個類型,接收進程可以獨立地接收含有不同類型的數據結構。我們可以通過發送消息來避免命名管道的同步和阻塞問題。但是消息隊列與命名管道一樣,每個數據塊都有一個最大長度的限制。
Linux用宏MSGMAX和MSGMNB來限制一條消息的最大長度和一個隊列的最大長度。
二、在Linux中使用消息隊列
Linux提供了一系列消息隊列的函數接口來讓我們方便地使用它來實現進程間的通信。它的用法與其他兩個System V PIC機制,即信號量和共享內存相似。
1、msgget函數
該函數用來創建和訪問一個消息隊列。它的原型為:
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、接收程序可以通過消息類型有選擇地接收數據,而不是像命名管道中那樣,只能默認地接收。
