linux 消息队列机制
現(xiàn)在我們來討論第三種也是最后一種System V IPV工具:消息隊(duì)列。在許多方面看來,消息隊(duì)列類似于有名管道,但是卻沒有與打開與關(guān)閉管道的復(fù)雜關(guān)聯(lián)。然而,使用消息隊(duì)列并沒有解決我們使用有名管道所遇到的問題,例如管道上的阻塞。
?
消息隊(duì)列提供了一種在兩個(gè)不相關(guān)的進(jìn)程之間傳遞數(shù)據(jù)的簡(jiǎn)單高效的方法。與有名管道比較起來,消息隊(duì)列的優(yōu)點(diǎn)在獨(dú)立于發(fā)送與接收進(jìn)程,這減少了在打開與關(guān)閉有名管道之間同步的困難。
?
消息隊(duì)列提供了一種由一個(gè)進(jìn)程向另一個(gè)進(jìn)程發(fā)送塊數(shù)據(jù)的方法。另外,每一個(gè)數(shù)據(jù)塊被看作有一個(gè)類型,而接收進(jìn)程可以獨(dú)立接收具有不同類型的數(shù)據(jù)塊。消息隊(duì)列的好處在于我們幾乎可以完全避免同步問題,并且可以通過發(fā)送消息屏蔽有名管道的問題。更好的是,我們可以使用某些緊急方式發(fā)送消息。壞處在于,與管道類似,在每一個(gè)數(shù)據(jù)塊上有一個(gè)最大尺寸限制,同時(shí)在系統(tǒng)中所有消息隊(duì)列上的塊尺寸上也有一個(gè)最大尺寸限制。
?
盡管有這些限制,但是X/Open規(guī)范并沒有定義這些限制的具體值,除了指出超過這些尺寸是某些消息隊(duì)列功能失敗的原因。Linux系統(tǒng)有兩個(gè)定義,MSGMAX與MSGMNB,這分別定義單個(gè)消息與一個(gè)隊(duì)列的最大尺寸。這些宏定義在其他系統(tǒng)上也許并不相同,甚至也許就不存在。
?
消息隊(duì)列函數(shù)定義如下:
?
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
int msgget(key_t key, int msgflg);
int msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);
int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg);
?
與信息號(hào)和共享內(nèi)存一樣,頭文件sys/types.h與sys/ipc.h通常也是需要的。
?
msgget
?
我們可以使用msgget函數(shù)創(chuàng)建與訪問一個(gè)消息隊(duì)列:
?
int msgget(key_t key, int msgflg);
?
與其他IPC工具類似,程序必須提供一個(gè)指定一個(gè)特定消息隊(duì)列的key值。特殊值IPC_PRIVATE創(chuàng)建一個(gè)私有隊(duì)列,這在理論上只可以為當(dāng)前進(jìn)程所訪問。與信息量和共享內(nèi)存一樣,在某些Linux系統(tǒng)上,消息隊(duì)列并不是私有的。因?yàn)樗接嘘?duì)列用處較少,因而這并不是一個(gè)嚴(yán)重問題。與前面一樣,第二個(gè)參數(shù),msgflg,由9個(gè)權(quán)限標(biāo)記組成。要?jiǎng)?chuàng)建一個(gè)新的消息隊(duì)列,由IPC_CREAT特殊位必須與其他的權(quán)限位進(jìn)行或操作。設(shè)置IPC_CREAT標(biāo)記與指定一個(gè)已存在的消息隊(duì)列并不是錯(cuò)誤。如果消息隊(duì)列已經(jīng)存在,IPC_CREAT標(biāo)記只是簡(jiǎn)單的被忽略。
?
如果成功,msgget函數(shù)會(huì)返回一個(gè)正數(shù)作為隊(duì)列標(biāo)識(shí)符,如果失敗則會(huì)返回-1。
?
msgsnd
?
msgsnd函數(shù)允許我們將消息添加到消息隊(duì)列:
?
int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg);
?
消息結(jié)構(gòu)由兩種方式來限定。第一,他必須小于系統(tǒng)限制,第二,必須以long int開始,這在接收函數(shù)中會(huì)用作一個(gè)消息類型。當(dāng)我們?cè)谑褂孟r(shí),最好是以如下形式來定義我們的消息結(jié)構(gòu):
?
struct my_message {
????long int message_type;
????/* The data you wish to transfer */
}
?
因?yàn)?/span>message_type用于消息接收,所以我們不能簡(jiǎn)單的忽略他。我們必須定義我們自己的數(shù)據(jù)結(jié)構(gòu)來包含并對(duì)其進(jìn)行初始化,從而他可以包含一個(gè)可知的值。
?
第一個(gè)參數(shù),msgid,是由msgget函數(shù)所返回的消息隊(duì)列標(biāo)識(shí)符。
?
第二個(gè)參數(shù),msg_ptr,是一個(gè)指向要發(fā)送消息的指針,正如前面所描述的,這個(gè)消息必須以long int類型開始。
?
第三個(gè)參數(shù),msg_sz,是由msg_ptr所指向的消息的尺寸。這個(gè)尺寸必須不包含long int消息類型。
?
第四個(gè)參數(shù),msgflg,控制如果當(dāng)前消息隊(duì)列已滿或是達(dá)到了隊(duì)列消息的系統(tǒng)限制時(shí)如何處理。如果msgflg標(biāo)記設(shè)置了IPC_NOWAIT,函數(shù)就會(huì)立即返回而不發(fā)送消息,并且返回值為-1。如果msgflg標(biāo)記清除了IPC_NOWAIT標(biāo)記,發(fā)送進(jìn)程就會(huì)被掛起,等待隊(duì)列中有可用的空間。
?
如果成功,函數(shù)會(huì)返回0,如果失敗,則會(huì)返回-1。如果調(diào)用成功,系統(tǒng)就會(huì)復(fù)制一份消息數(shù)據(jù)并將其放入消息隊(duì)列中。
?
msgrcv
?
msgrcv函數(shù)由一個(gè)消息隊(duì)列中收取消息:
?
int msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);
?
第一個(gè)參數(shù),msqid,是由msgget函數(shù)所返回的消息隊(duì)列標(biāo)記符。
?
第二個(gè)參數(shù),msg_ptr,是一個(gè)指向?qū)⒁邮障⒌闹羔?#xff0c;正如在msgsnd函數(shù)中所描述的,這個(gè)消息必須以long int類型開始。
?
第三個(gè)參數(shù),msg_sz,是由msg_ptr所指向的消息的尺寸,并不包含long int消息類型。
?
第四個(gè)參數(shù),msgtype,是一個(gè)long int類型,允許一個(gè)接收優(yōu)先級(jí)形式的實(shí)現(xiàn)。如果msgtype的值為0,隊(duì)列中第一個(gè)可用的消息就會(huì)被接收。如果其值大于0,具有相同消息類型的第一個(gè)消息就會(huì)被接收。如果其值小于0,第一個(gè)具有相同類型或是小于msgtype絕對(duì)值的消息就會(huì)被接收。
?
這聽起來要比實(shí)際操作復(fù)雜得多。如果我們只是簡(jiǎn)單的希望以其發(fā)送的順序來接收消息,我們可以將msgtype設(shè)置為0。如果我們希望接收特殊消息類型的消息,我們可以將msgtype設(shè)置為等于這個(gè)值。如果我們希望接收消息類型為n或是小于n的值,我們可以將msgtype設(shè)置為-n。
?
第五個(gè)參數(shù),msgflg,控制當(dāng)沒有合適類型的消息正在等待被接收時(shí)如何處理。如果在msgflg中設(shè)置了IPC_NOWAIT位,調(diào)用就會(huì)立即返回,而返回值為-1。如果msgflg標(biāo)記中消除了IPC_NOWAIT位,進(jìn)程就會(huì)被掛起,等待一個(gè)合適類型的消息到來。
?
如果成功,msgrcv會(huì)返回放入接收緩沖區(qū)中的字節(jié)數(shù),消息會(huì)被拷貝到由msg_ptr所指向的用戶分配緩沖區(qū)中,而數(shù)據(jù)就會(huì)由消息隊(duì)列中刪除。如果失敗則會(huì)返回-1。
?
msgctl
?
最后一個(gè)消息隊(duì)列函數(shù)是msgctl,這與共享內(nèi)存中的控制函數(shù)十分類似。
?
int msgctl(int msqid, int command, struct msqid_ds *buf);
?
msqid_ds結(jié)構(gòu)至少包含下列成員:
?
struct msqid_ds {
????uid_t msg_perm.uid;
????uid_t msg_perm.gid
????mode_t msg_perm.mode;
}
?
第一個(gè)參數(shù),msqid,是由msgget函數(shù)所返回的標(biāo)記符。
?
第二個(gè)參數(shù),command,是要執(zhí)行的動(dòng)作。他可以取下面三個(gè)值:
?
命令 ???????描述
IPC_STAT ???設(shè)置msqid_ds結(jié)構(gòu)中的數(shù)據(jù)來反射與消息隊(duì)列相關(guān)聯(lián)的值。
IPC_SET ???????如果進(jìn)程有權(quán)限這樣做,這個(gè)命令會(huì)設(shè)置與msqid_ds數(shù)據(jù)結(jié)構(gòu)中所提供的消息隊(duì)列相關(guān)聯(lián)的值。
IPC_RMID ???刪除消息隊(duì)列。
?
如果成功則會(huì)返回0,如果失敗則會(huì)返回-1。當(dāng)進(jìn)程正在msgsnd或是msgrcv函數(shù)中等待時(shí)如果消息隊(duì)列被刪除,發(fā)送或接收函數(shù)就會(huì)失敗。
?
試驗(yàn)--消息隊(duì)列
?
現(xiàn)在我們已經(jīng)了解了消息隊(duì)列的定義,我們可以來看一下他們是如何實(shí)際工作的。與前面一樣,我們將會(huì)編寫兩個(gè)程序:msg1.c來接收,msg2.c來發(fā)送。我們會(huì)允許任意一個(gè)程序創(chuàng)建消息隊(duì)列,但是使用接收者在接收到最后一條消息后刪除消息隊(duì)列。
?
1 下面是接收程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
?
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
?
struct my_msg_st
{
????long int my_msg_type;
????char some_text[BUFSIZ];
};
?
int main()
{
????int running = 1;
????int msgid;
????struct my_msg_st some_data;
????long int msg_to_receive = 0;
?
2 首先,我們?cè)O(shè)置消息隊(duì)列:
?
????msgid = msgget((key_t)1234,0666|IPC_CREAT);
?
????if(msgid == -1)
????{
????????fprintf(stderr,"msgget failed with error: %d\n", errno);
????????exit(EXIT_FAILURE);
????}
?
3 然后,接收消息隊(duì)列中的消息直到遇到一個(gè)end消息。最后,消息隊(duì)列被刪除:
?
????while(running)
????{
????????if(msgrcv(msgid, (void *)&some_data, BUFSIZ, msg_to_receive, 0) == -1)
????????{
????????????fprintf(stderr, "msgrcv failed with errno: %d\n", errno);
????????????exit(EXIT_FAILURE);
????????}
?
????????printf("You wrote: %s", some_data.some_text);
????????if(strncmp(some_data.some_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);
}
?
4 發(fā)送程序與msg1.c類似。在main函數(shù)中,刪除msg_to_receive聲明,代之以buffer[BUFSIZ]。移除消息隊(duì)列刪除代碼,并且在running循環(huán)中做出如下更改。現(xiàn)在我們調(diào)用msgsnd來將輸入的文本發(fā)送到隊(duì)列中。
?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
?
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
?
#define MAX_TEXT 512
?
struct my_msg_st
{
????long int my_msg_type;
????char some_text[MAX_TEXT];
};
?
int main()
{
????int running = 1;
????struct my_msg_st some_data;
????int msgid;
????char buffer[BUFSIZ];
?
????msgid = msgget((key_t)1234, 0666|IPC_CREAT);
?
????if(msgid==-1)
????{
????????fprintf(stderr,"msgget failed with errno: %d\n", errno);
????????exit(EXIT_FAILURE);
????}
?
????while(running)
????{
????????printf("Enter some text: ");
????????fgets(buffer, BUFSIZ, stdin);
????????some_data.my_msg_type = 1;
????????strcpy(some_data.some_text, buffer);
?
????????if(msgsnd(msgid, (void *)&some_data, MAX_TEXT, 0)==-1)
????????{
????????????fprintf(stderr, "msgsnd failed\n");
????????????exit(EXIT_FAILURE);
????????}
?
????????if(strncmp(buffer, "end", 3) == 0)
????????{
????????????running = 0;
????????}
????}
?
????exit(EXIT_SUCCESS);
}
?
與管道中的例子不同,進(jìn)程并沒有必要提供自己的同步機(jī)制。這是消息隊(duì)列比起管道的一個(gè)巨大優(yōu)點(diǎn)。
?
假設(shè)消息隊(duì)列有空間,發(fā)送者可以創(chuàng)建隊(duì)列,在隊(duì)列中放入一些數(shù)據(jù),并且甚至可以在接收者啟動(dòng)之前退出。我們會(huì)首先運(yùn)行發(fā)送者。如下面的例子輸出:
?
$ ./msg2
Enter some text: hello
Enter some text: How are you today?
Enter some text: end
$ ./msg1
You wrote: hello
You wrote: How are you today?
You wrote: end
$
?
工作原理
?
發(fā)送者程序使用msgget創(chuàng)建一個(gè)消息隊(duì)列;然后使用msgsnd函數(shù)向隊(duì)列中添加消息。接收者使用msgget來獲得消息隊(duì)列標(biāo)識(shí)符,并且接收消息,直到接收到特殊消息end。然后他會(huì)使用msgctl刪除消息隊(duì)列進(jìn)行一些清理工作。
總結(jié)
以上是生活随笔為你收集整理的linux 消息队列机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 子宫输卵管造影多少钱
- 下一篇: Linux 开发路线