消息隊(duì)列
消息隊(duì)列提供了一種從一個(gè)進(jìn)程向另一個(gè)進(jìn)程發(fā)送一個(gè)數(shù)據(jù)塊的方法觉啊。 每個(gè)數(shù)據(jù)塊都被認(rèn)為含有一個(gè)類型折联,接收進(jìn)程可以獨(dú)立地接收含有不同類型的數(shù)據(jù)結(jié)構(gòu)急灭。我們可以通過(guò)發(fā)送消息來(lái)避免命名管道的同步和阻塞問(wèn)題。但是消息隊(duì)列與命名管道一樣,每個(gè)數(shù)據(jù)塊都有一個(gè)最大長(zhǎng)度的限制钠糊。
Linux用宏MSGMAX和MSGMNB來(lái)限制一條消息的最大長(zhǎng)度和一個(gè)隊(duì)列的最大長(zhǎng)度。
在Linux中使用消息隊(duì)列
1壹哺、msgget函數(shù)
該函數(shù)用來(lái)創(chuàng)建和訪問(wèn)一個(gè)消息隊(duì)列抄伍。它的原型為:
int msgget(key_t, key, int msgflg);
與其他的IPC機(jī)制一樣,程序必須提供一個(gè)鍵來(lái)命名某個(gè)特定的消息隊(duì)列斗躏。msgflg是一個(gè)權(quán)限標(biāo)志逝慧,表示消息隊(duì)列的訪問(wèn)權(quán)限,它與文件的訪問(wèn)權(quán)限一樣啄糙。msgflg可以與IPC_CREAT做或操作笛臣,表示當(dāng)key所命名的消息隊(duì)列不存在時(shí)創(chuàng)建一個(gè)消息隊(duì)列,如果key所命名的消息隊(duì)列存在時(shí)隧饼,IPC_CREAT標(biāo)志會(huì)被忽略沈堡,而只返回一個(gè)標(biāo)識(shí)符。
它返回一個(gè)以key命名的消息隊(duì)列的標(biāo)識(shí)符(非零整數(shù))燕雁,失敗時(shí)返回-1.
2诞丽、msgsnd函數(shù)
該函數(shù)用來(lái)把消息添加到消息隊(duì)列中鲸拥。它的原型為:
int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
msgid是由msgget函數(shù)返回的消息隊(duì)列標(biāo)識(shí)符。
msg_ptr是一個(gè)指向準(zhǔn)備發(fā)送消息的指針僧免,但是消息的數(shù)據(jù)結(jié)構(gòu)卻有一定的要求刑赶,指針msg_ptr所指向的消息結(jié)構(gòu)一定要是以一個(gè)長(zhǎng)整型成員變量開(kāi)始的結(jié)構(gòu)體,接收函數(shù)將用這個(gè)成員來(lái)確定消息的類型懂衩。所以消息結(jié)構(gòu)要定義成這樣:
struct my_message{
long int message_type;
/* The data you wish to transfer*/
};
msg_sz是msg_ptr指向的消息的長(zhǎng)度撞叨,注意是消息的長(zhǎng)度,而不是整個(gè)結(jié)構(gòu)體的長(zhǎng)度浊洞,也就是說(shuō)msg_sz是不包括長(zhǎng)整型消息類型成員變量的長(zhǎng)度牵敷。
msgflg用于控制當(dāng)前消息隊(duì)列滿或隊(duì)列消息到達(dá)系統(tǒng)范圍的限制時(shí)將要發(fā)生的事情。
如果調(diào)用成功法希,消息數(shù)據(jù)的一分副本將被放到消息隊(duì)列中枷餐,并返回0,失敗時(shí)返回-1.
3苫亦、msgrcv函數(shù)
該函數(shù)用來(lái)從一個(gè)消息隊(duì)列獲取消息毛肋,它的原型為
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);
msgid, msg_ptr, msg_st的作用也函數(shù)msgsnd函數(shù)的一樣。
msgtype可以實(shí)現(xiàn)一種簡(jiǎn)單的接收優(yōu)先級(jí)著觉。如果msgtype為0村生,就獲取隊(duì)列中的第一個(gè)消息。如果它的值大于零饼丘,將獲取具有相同消息類型的第一個(gè)信息。如果它小于零辽话,就獲取類型等于或小于msgtype的絕對(duì)值的第一個(gè)消息肄鸽。
msgflg用于控制當(dāng)隊(duì)列中沒(méi)有相應(yīng)類型的消息可以接收時(shí)將發(fā)生的事情。
調(diào)用成功時(shí)油啤,該函數(shù)返回放到接收緩存區(qū)中的字節(jié)數(shù)典徘,消息被復(fù)制到由msg_ptr指向的用戶分配的緩存區(qū)中,然后刪除消息隊(duì)列中的對(duì)應(yīng)消息益咬。失敗時(shí)返回-1.
4逮诲、msgctl函數(shù)
該函數(shù)用來(lái)控制消息隊(duì)列,它與共享內(nèi)存的shmctl函數(shù)相似幽告,它的原型為:
int msgctl(int msgid, int command, struct msgid_ds *buf);
command是將要采取的動(dòng)作梅鹦,它可以取3個(gè)值,
IPC_STAT:把msgid_ds結(jié)構(gòu)中的數(shù)據(jù)設(shè)置為消息隊(duì)列的當(dāng)前關(guān)聯(lián)值冗锁,即用消息隊(duì)列的當(dāng)前關(guān)聯(lián)值覆蓋msgid_ds的值齐唆。
IPC_SET:如果進(jìn)程有足夠的權(quán)限,就把消息列隊(duì)的當(dāng)前關(guān)聯(lián)值設(shè)置為msgid_ds結(jié)構(gòu)中給出的值
IPC_RMID:刪除消息隊(duì)列
buf是指向msgid_ds結(jié)構(gòu)的指針冻河,它指向消息隊(duì)列模式和訪問(wèn)權(quán)限的結(jié)構(gòu)箍邮。msgid_ds結(jié)構(gòu)至少包括以下成員:
struct msgid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
成功時(shí)返回0茉帅,失敗時(shí)返回-1.
三、使用消息隊(duì)列進(jìn)行進(jìn)程間通信
馬不停蹄锭弊,介紹完消息隊(duì)列的定義和可使用的接口之后堪澎,我們來(lái)看看它是怎么讓進(jìn)程進(jìn)行通信的。由于可以讓不相關(guān)的進(jìn)程進(jìn)行行通信味滞,所以我們?cè)谶@里將會(huì)編寫(xiě)兩個(gè)程序全封,msgreceive和msgsned來(lái)表示接收和發(fā)送信息。根據(jù)正常的情況桃犬,我們?cè)试S兩個(gè)程序都可以創(chuàng)建消息刹悴,但只有接收者在接收完最后一個(gè)消息之后,它才把它刪除攒暇。
接收信息的程序源文件為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
//建立消息隊(duì)列
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if(msgid == -1)
{
fprintf(stderr, "msgget failed with error: %d\n", errno);
exit(EXIT_FAILURE);
}
//從隊(duì)列中獲取消息土匀,直到遇到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結(jié)束
if(strncmp(data.text, "end", 3) == 0)
running = 0;
}
//刪除消息隊(duì)列
if(msgctl(msgid, IPC_RMID, 0) == -1)
{
fprintf(stderr, "msgctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
發(fā)送信息的程序的源文件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;
//建立消息隊(duì)列
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if(msgid == -1)
{
fprintf(stderr, "msgget failed with error: %d\n", errno);
exit(EXIT_FAILURE);
}
//向消息隊(duì)列中寫(xiě)消息,直到寫(xiě)入end
while(running)
{
//輸入數(shù)據(jù)
printf("Enter some text: ");
fgets(buffer, BUFSIZ, stdin);
data.msg_type = 1; //注意2
strcpy(data.text, buffer);
//向隊(duì)列發(fā)送數(shù)據(jù)
if(msgsnd(msgid, (void*)&data, MAX_TEXT, 0) == -1)
{
fprintf(stderr, "msgsnd failed\n");
exit(EXIT_FAILURE);
}
//輸入end結(jié)束輸入
if(strncmp(buffer, "end", 3) == 0)
running = 0;
sleep(1);
}
exit(EXIT_SUCCESS);
}
運(yùn)行結(jié)果如下:
例子分析——消息類型
這里主要說(shuō)明一下消息類型是怎么一回事形用,注意msgreceive.c文件main函數(shù)中定義的變量msgtype(注釋為注意1)就轧,它作為msgrcv函數(shù)的接收信息類型參數(shù)的值,其值為0田度,表示獲取隊(duì)列中第一個(gè)可用的消息妒御。再來(lái)看看msgsend.c文件中while循環(huán)中的語(yǔ)句data.msg_type = 1(注釋為注意2),它用來(lái)設(shè)置發(fā)送的信息的信息類型镇饺,即其發(fā)送的信息的類型為1乎莉。所以程序msgreceive能夠接收到程序msgsend發(fā)送的信息。
如果把注意1奸笤,即msgreceive.c文件main函數(shù)中的語(yǔ)句由long int msgtype = 0;改變?yōu)閘ong int msgtype = 2;會(huì)發(fā)生什么情況惋啃,msgreceive將不能接收到程序msgsend發(fā)送的信息。因?yàn)樵谡{(diào)用msgrcv函數(shù)時(shí)监右,如果msgtype(第四個(gè)參數(shù))大于零边灭,則將只獲取具有相同消息類型的第一個(gè)消息,修改后獲取的消息類型為2健盒,而msgsend發(fā)送的消息類型為1绒瘦,所以不能被msgreceive程序接收。重新編譯msgreceive.c文件并再次執(zhí)行扣癣,其結(jié)果如下:
![](file:///C:/Users/Administrator/AppData/Local/youdao/ynote/images/A1A9EF1F521A463D80835BF457097564/QQ%E6%88%AA%E5%9B%BE20130824162817.jpg)
我們可以看到惰帽,msgreceive并沒(méi)有接收到信息和輸出,而且當(dāng)msgsend輸入end結(jié)束后搏色,msgreceive也沒(méi)有結(jié)束善茎,通過(guò)jobs命令我們可以看到它還在后臺(tái)運(yùn)行著。
五频轿、消息隊(duì)列與命名管道的比較
消息隊(duì)列跟命名管道有不少的相同之處垂涯,通過(guò)與命名管道一樣烁焙,消息隊(duì)列進(jìn)行通信的進(jìn)程可以是不相關(guān)的進(jìn)程,同時(shí)它們都是通過(guò)發(fā)送和接收的方式來(lái)傳遞數(shù)據(jù)的耕赘。在命名管道中骄蝇,發(fā)送數(shù)據(jù)用write,接收數(shù)據(jù)用read操骡,則在消息隊(duì)列中九火,發(fā)送數(shù)據(jù)用msgsnd,接收數(shù)據(jù)用msgrcv册招。而且它們對(duì)每個(gè)數(shù)據(jù)都有一個(gè)最大長(zhǎng)度的限制岔激。
與命名管道相比,消息隊(duì)列的優(yōu)勢(shì)在于是掰,
1虑鼎、消息隊(duì)列也可以獨(dú)立于發(fā)送和接收進(jìn)程而存在,從而消除了在同步命名管道的打開(kāi)和關(guān)閉時(shí)可能產(chǎn)生的困難键痛。
2炫彩、同時(shí)通過(guò)發(fā)送消息還可以避免命名管道的同步和阻塞問(wèn)題,不需要由進(jìn)程自己來(lái)提供同步方法絮短。
3江兢、接收程序可以通過(guò)消息類型有選擇地接收數(shù)據(jù),而不是像命名管道中那樣丁频,只能默認(rèn)地接收杉允。