Linux进程间通讯之消息队列
首先有個大體的概念:http://www.xefan.com/archives/83703.html
頭文件: #include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
函數: key_t ftok(const char *filename, int proj_id);
通過文件名和項目號獲得System V IPC鍵值(用于創建消息隊列、共享內存所用)
proj_id:項目號,不為0即可
返回:成功則返回鍵值,失敗則返回-1
函數: int msgget(key_t key, int msgflg);
key:鍵值,當為IPC_PRIVATE時新建一塊共享內存;
shmflg:標志。
IPC_CREAT:內存不存在則新建,否則打開;
IPC_EXCL:只有在內存不存在時才創建,否則出錯。
返回:成功則返回標識符,出錯返回-1
函數: int msgsnd(int msgid, const void *msgp, size_t sz, int flg);
向消息隊列發送消息
msgid:通過msgget獲取
msgp:指向消息內容的指針
sz:消息內容的大小
flg:處理方式;如為IPC_NOWAIT時表示空間不足時不會阻塞
返回:成功則返回0,失敗返回-1
函數: int msgrcv(int msgid, void *msgp, size_t sz, long type, int flg);
從消息隊列讀取消息
msgid:通過msgget獲取
msgp:指向消息內容的指針
sz:消息內容的大小
type:指定接收的消息類型;若為0則隊列第一條消息將被讀取,而不管類型;若大于0則隊列中同類型的消息將被讀取,如在flg中設了MSG_RXCEPT位將讀取指定類型的其他消息;若小于0讀取絕對值小于type的消息。
flg:處理方式;
返回:成功返回收到消息長度,錯誤返回-1
函數: int msgctl(int msgid, int cmd, struct msgid_ds *buf);
msgid:通過msgget獲取
cmd:控制命令,如下:
IPC_STAT:獲取消息隊列狀態
IPC_SET:改變消息隊列狀態
IPC_RMID:刪除消息隊列
buf:結構體指針,用于存放消息隊列狀態
返回:成功返回與cmd相關的正數,錯誤返回-1
注意:消息隊列一旦創建就會一直存在系統中,直到手動刪除或者重啟系統??梢允褂胕pcs -q命令來查看系統存在的消息隊列。
/****************************************** 使用消息隊列進行進程通信——寫進程* 該進程用于創建信號量* 龍昌博客:http://www.xefan.com*****************************************/ #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <stdio.h>typedef struct _msg_buf{long type; //消息類型char buf[100]; //消息內容 } msg_buf;int main() {int key, qid;msg_buf buf;key = ftok("tmp", 10);qid = msgget(key, IPC_CREAT);printf("key: %d\nqid: %d\n", key, qid);buf.type = 10;while (1){fgets(buf.buf, 100, stdin);if (msgsnd(qid, (void *)&buf, 100, 0) < 0){perror("msgsnd");exit(-1);}}return 0; }/****************************************** 使用消息隊列進行進程通信——讀進程* 該進程用于創建信號量* 龍昌博客:http://www.xefan.com*****************************************/ #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <stdio.h>typedef struct _msg_buf{long type; //消息類型char buf[100]; //消息內容 } msg_buf;int main() {int key, qid;msg_buf buf;key = ftok("tmp", 10);qid = msgget(key, IPC_CREAT);printf("key: %d\nqid: %d\n", key, qid);while (1){if (msgrcv(qid, (void *)&buf, 100, 0, 0) < 0){perror("msgrcv");exit(-1);}printf("type:%d\nget:%s\n", buf.type, buf.buf);}return 0; }
先運行寫進程再運行讀進程。
轉自:http://blog.csdn.net/liranke/article/details/5608686
很詳細
1.?基本概念
消息隊列的最佳定義是:內核地址空間中的內部鏈表。消息可以順序地發送到隊列中,
并以幾種不同的方式從隊列中獲取。當然,每個消息隊列都是由?IPC標識符所唯一標識的。
2.?內部和用戶數據結構
要完成理解象系統?V IPC這樣復雜的問題,關鍵是要徹底熟悉內核的幾個內部數據結構。
甚至對那些最基本的操作來說,直接訪問這些結構中的某幾個結構也是必要的,而其他的結
構則停留在一個更低的級別上。
3.?消息緩沖區
我們要介紹的第一個結構是?msgbuf結構。這個特殊的數據結構可以認為是消息數據的模
板。雖然定義這種類型的數據結構是程序員的職責,但是讀者絕對必須知道實際上存在
msgbuf類型的結構。它是在在?linux/msg.h中定義的,有2個成員:
? mtype—它是消息類型,以正數來表示。這個數必須為一個正數!
? mtext—它就是消息數據。
?
4.?內核msg結構
內核把消息隊列中的每個消息都存放在?msg結構的框架中。該結構是在?linux/msg.h中定義
的,如下是其成員的描述:
? msg_next—這是一個指針,指向消息隊列中的下一個消息。在內核尋址空間中,它們
是當作一個鏈表存儲的。
? msg_type—這是消息類型,它的值是在用戶結構?msgbuf中賦予的。
? msg_spot—這是一個指針,指向消息體的開始處。
? msg_ts—這是消息文本(消息體)的長度。
??內核?msgid_ds結構—IPC對象分為三類,每一類都有一個內部數據結構,該數據結構
是由內核維護的。對于消息隊列而言,它的內部數據結構是???????????????msqid_ds結構。對于系統上
創建的每個消息隊列,內核均為其創建、存儲和維護該結構的一個實例。該結構在
linux/msg.h中定義,如下所示:
struct msqid_ds{
??? struct ipc_perm ??? msg_perm;??
??? msgqnum_t ? msg_qnum;??????
??? msglen_t??? msg_qbytes; ???
??? pid_t?????? msg_lspid;?????
??? pid_t ????? msg_lrpid;????? ???
??? time_t????? msg_stime;?????
??? time_t????? msg_rtime;?????
??? time_t????? msg_ctime;?????
??? ...
???
??? ...
};
?
?
?
在不同的系統中,此結構會有不同的新成員,這里只列出最少擁有的關鍵成員。其中,msg_qbytes成員以及msg_qnum成員在不同的系統也會有不同的上限值,這里就不逐一介紹了,詳細內容請參閱相關系統手冊。
? msg_perm—它是?ipc_perm結構的一個實例,?ipc_perm結構是在?linux/ipc.h中定義的。
該成員存放的是消息隊列的許可權限信息,其中包括訪問許可信息,以及隊列的創建者
的有關信息?(如uid等等)。
? msg_first—鏈接到隊列中的第一個消息?(列表頭部?)。
? msg_last—鏈接到隊列中的最后一個消息?(列表尾部)。
? msg_stime—發送到隊列的最后一個消息的時間戳?(time_t)。
? msg_rtime—從隊列中獲取的最后一個消息的時間戳。
? msg_ctime—對隊列進行最后一次變動的時間戳。
?
?
5.?內核ipc_perm結構
內核把IPC對象的許可權限信息存放在?ipc_perm類型的結構中。例如在前面描述的某個消
息隊列的內部結構中,?msg_perm成員就是?ipc_perm類型的,它的定義是在文件?linux/ipc.h中,
?
以上所有的成員都具有相當的自擴展性。對象的創建者以及所有者?(它們可能會有不同?)的
有關信息,以及對象的?IPC關鍵字都是存放在該結構中的。八進制形式的訪問模式也是存放在
這里的,它是以一種無符號短整型的形式存儲的。最后,時間片使用序列編號存放在最后面,
每次通過系統調用關閉?IPC對象(摧毀)時,這個值將被增加一,至多可以增加到能駐留在系統
中的IPC對象的最大數目。用戶需要關心這個值嗎?答案是“不”。
有關這個問題,在?Richard Stevens所著的《?Unix Network Programming?》一書的第125頁
中作了精辟的討論。該書還介紹了?ipc_perm結構的存在和行為在安全性方面的原因。
?
6.?創建消息隊列:
(1)?msgget簡介:為了創建一個新的消息隊列,或者訪問一個現有的隊列,可以使用系統調用?msgget ( )。
msgget ( )的第一個變元是關鍵字的值?(在我們的例子中該值是調用?ftok ( )的返回值)。這個
關鍵字的值將被拿來與內核中其他消息隊列的現有關鍵字值相比較。比較之后,打開或者訪
問操作依賴于msgflg變元的內容。
? IPC_CREAT—如果在內核中不存在該隊列,則創建它。
? IPC_EXCL—當與IPC_CREAT一起使用時,如果隊列早已存在則將出錯。
如果只使用了?IPC_CREAT, msgget ( )或者返回新創建消息隊列的消息隊列標識符,或者
會返回現有的具有同一個關鍵字值的隊列的標識符。如果同時使用了
I P C _ E X C L?和IPC_CREAT,那么將可能會有兩個結果?;蛘邉摻ㄒ粋€新的隊列,或者如果該隊列存在,則
調用將出錯,并返回-1。IPC_EXCL本身是沒有什么用處的,但在與IPC_CREAT組合使用時,
它可以用于保證沒有一個現存的隊列為了訪問而被打開。
有個可選的八進制許可模式,它是與掩碼進行?OR操作以后得到的。這是因為從功能上講,
每個IPC對象的訪問許可權限與?Unix文件系統的文件許可權限是相似的!
?
(2)msgget舉例:
下面實例演示了使用msgget函數創建一個隊列,函數中參數falgs指定為IPC_CREAT|0666,說明新建一個權限為0666的消息隊列,其中組用戶、當前用戶以及其他用戶擁有讀寫的權限。并在程序的最后使用shell命令ipcs –q來查看系統IPC的狀態。
(1)在vi編輯器中編輯該程序如下:
程序清單14-12? create_msg.c msgget函數
#include <sys/msg.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <stdlib.h>
int main ( void )
{
??? int???? qid;
??? key_t?????? key;
???
??? key = 113;
??? qid=msgget( key, IPC_CREAT | 0666 );?????? /*創建一個消息隊列*/
??? if ( qid < 0 ) {??????????????????????????? /*?創建一個消息隊列失敗?*/
??????? perror ( "msgget" );
??????? exit (1) ;
??? }
???
??? printf ("created queue id : %d /n", qid );? /*?輸出消息隊列的ID */
???
??? system( "ipcs -q" );??????????????????????? /*查看系統IPC的狀態*/
??? exit ( 0 );
}
(2)在shell中編譯該程序如下:
$gcc create_msg.c–o create_msg
(3)在shell中運行該程序如下:
$./ create_msg
created queue id : 0
------ Message Queues --------
key??????? msqid????? owner????? perms????? used-bytes?? messages
0x0000af40 623430?? root?? ??? 666??????? 0??????????? 0
0x0000007b 0????????? root?? ????666??????? 0??????????? 0
在程序中使用了系統命令ipcs,命令參數-q說明只查看消息隊列的狀態。注意在輸出消息中,key段標明的是IPC的key值,msqid為該隊列的ID值,perms為執行權限。同樣,隊列的執行權限像其他IPC對象一樣沒有執行權限。函數msgctl可以在隊列上做多種操作,函數原型如下:
#include <sys/msg.h>
int msgctl( int msqid, int cmd , struct msqid_ds *buf );
參數msqid為指定的要操作的隊列,cmd參數指定所要進行的操作,其中有些操作需要buf參數。cmd參數的詳細取值及操作如表14-9所示。
表14-9? cmd參數詳解
| cmd | 操????作 |
| IPC_STAT | 取隊列的msqid_ds結構,將它存放在buf所指向的結構中(需要buf參數) |
| IPC_SET | 使用buf所指向結構中的值對當前隊列的相關結構成員賦值,其中包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_perm.cuid。該命令只能由具有以下條件的進程執行:進程有效用戶ID等于msg_perm.cuid或msg_perm.uid超級用戶進程。其中只有超級用戶才可以增加隊列的msg_qbytes的值 |
| IPC_RMID | 刪除隊列,并清除隊列中的所有消息。此操作會影響后續進程對這個隊列的相關操作。該命令只能由具有以下條件的進程執行。進程有效用戶ID等于msg_perm.cuid或msg_perm.uid,超級用戶進程 |
下面實例演示了調用msgctl函數操作隊列,程序中先讀取命令行參數,如沒有,則打印命令提示信息,在調用msgctl函數執行刪除操作的前后分別調用了一次shell命令ipcs –q來查看系統IPC的狀態。
(1)在vi編輯器中編輯該程序如下:
程序清單14-13? del_msg.c?調用msgctl刪除指定隊列
#include <sys/msg.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <stdlib.h>
int main ( int argc ,char *argv[] )
{
??? int qid ;
???
??? if ( argc != 2 ){ /*?命令行參數出錯?*/
??????? puts ( "USAGE: del_msgq.c <queue ID >" );
??????? exit ( 1 );
??? }
???
??? qid = atoi ( argv[1] ); /*?通過命令行參數得到組ID */
??? system( "ipcs -q");
???
??? if ( ( msgctl( qid, IPC_RMID, NULL ) ) < 0 ){ /*?刪除指定的消息隊列?*/
??????? perror ("msgctl");
??????? exit (1 );
??? }
??? system( "ipcs -q");
??? printf ( "successfully removed %d? queue/n", qid ); /*?刪除隊列成功?*/
??? exit( 0 );
}
(2)在shell中編譯該程序如下:
$gcc del_msg.c–o del_msg
(3)在shell中運行該程序如下:
$./ del_msg
------ Message Queues --------
key??????? msqid????? owner????? perms????? used-bytes?? messages???
0x0000007b 0????????? root?????? 666??????? 0??????????? 0??????????
------ Message Queues --------
key??????? msqid????? owner????? perms????? used-bytes?? messages???
successfully removed 0? queue ?
?
?
7.??讀寫消息隊列
一旦獲得了隊列標識符,用戶就可以開始在該消息隊列上執行相關操作了。為了向隊列
傳遞消息,用戶可以使用?msgsnd系統調用.
由于消息隊列的特殊性,系統為這個數據類型提供了兩個接口(msgsnd函數,msgrcv函數),分別對應寫消息隊列及讀消息隊列。將一個新的消息寫入隊列,使用函數msgsnd,函數原型如下:
#include <sys/msg.h>
int msgsnd ( int msqid, const void *prt, size_t nbytes, int flags);
對于寫入隊列的每一個消息,都含有三個值,正長整型的類型字段、數據長度字段和實際數據字節。新的消息總是放在隊列的尾部,函數中參數msqid指定要操作的隊列,ptr指針指向一個msgbuf的結構,定義如下:
struct msgbuf{
??? long mtype;
??? char mbuf[];
};
這是一個模板的消息結構,其中成員?mbuf是一個字符數組,長度是根據具體的消息來決定的,切忌消息不能以NULL結尾。成員mtype是消息的類型字段。
函數參數nbytes指定了消息的長度,參數flags指明函數的行為。函數成功返回0,失敗返回–1并設置錯誤變量errno。errno可能出現的值有:EAGAIN、EACCES、EFAULT、EIDRM、EINTR、EINVAL和ENOMEM。當函數成功返回后會更新相應隊列的msqid_ds結構。
使用函數msgrcv可以從隊列中讀取消息,函數原型如下:
#include <sys/msg.h>
ssize_t msgrcv ( int msqid, void *ptr, size_t nbytes, long type , int flag);
函數中參數msqid為指定要讀的隊列,參數ptr為要接收數據的緩沖區,nbytes為要接收數據的長度,當隊列中滿足條件的消息長度大于nbytes的值時,則會參照行為參數flag的值決定如何操作:當flag中設置了MSG_NOERROR位時,則將消息截短到nbytes指定的長度后返回。如沒有MSG_NOERROR位,則函數出錯返回,并設置錯誤變量errno。設置type參數指定msgrcv函數所要讀取的消息,tyre的取值及相應操作如表14-10所示。
表14-10? type值詳解
| type | 操????作 |
| 等于0 | 返回隊列最上面的消息(根據先進先出規則) |
| 大于0 | 返回消息類型與type相等的第1條消息 |
| 小于0 | 返回消息類型小于等于type絕對值的最小值的第1條消息 |
參數flag定義函數的行為,如設置了IPC_NOWAIT位,則當隊列中無符合條件的消息時,函數出錯返回,errno的值為ENOMSG。如沒有設置IPC_NOWAIT位,則進程阻塞直到出現滿足條件的消息出現為止,然后函數讀取消息返回。
下面實例演示了消息隊列在進程間的通信。程序中創建了一個消息的模板結構體,并對聲明變量做初始化。使用msgget函數創建了一個消息隊列,使用msgsnd函數向該隊列中發送了一條消息。
(1)在vi編輯器中編輯該程序如下:
程序清單14-14? snd_msg.c?調用msgsnd函數向隊列中發送消息
#include <sys/msg.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <stdlib.h>
struct msg{???????????????????? /*聲明消息結構體*/
??? long msg_types;???????????? /*消息類型成員*/???
??? char msg_buf[511];????????? /*消息*/
};
int main( void ) {
??? int???? qid;
??? int ??????? pid;
??? int ??? ??? len;
??? struct msg pmsg;??????????? /*一個消息的結構體變量*/
???
??? pmsg.msg_types = getpid();? /*消息類型為當前進程的ID*/
??? sprintf (pmsg.msg_buf,"hello!this is :%d/n/0", getpid() ); /*初始化消息*/
??? len = strlen ( pmsg.msg_buf );?? /*取得消息長度*/
???
??? if ( (qid=msgget(IPC_PRIVATE, IPC_CREAT | 0666)) < 0 ) {? /*創建一個消
??? ??????????????????????????????????????????????????????????? ??息隊列*/
??????? perror ( "msgget" );
??????? exit (1) ;
??? }
???
??? if ( (msgsnd(qid, &pmsg, len, 0 )) < 0 ){?? /*向消息隊列中發送消息*/
??????? perror ( "msgsn" );
??????? exit ( 1 );
??? }
??? printf ("successfully send a message to the queue: %d /n", qid);
??? exit ( 0 ) ;
}
(2)在shell中編譯該程序如下:
$gcc snd_msg.c –o snd_msg
(3)在shell中運行該程序如下:
$./ snd_msg
successfully send a message to the queue 0
上述程序中,先定義了一個消息的結構體。該結構體中包含兩個成員,long類型成員msg_types是消息的類型,注意,在消息隊列中是以消息類型做索引值來進行檢索的。char類型數組存放消息。在程序中先聲明了一個消息的結構體變量,并做相應初始化,然后使用了msgget函數創建一個消息隊列,并將該消息發送到此消息隊列中。以下是一個使用消息隊列發送消息的程序。
下面實例演示了如何使用隊列讀取消息。在程序的開始部分,判斷用戶是否輸入了目標消息隊列ID,如果沒有,則打印命令的幫助信息;如果用戶輸入了隊列的ID,則從隊列中取出該消息,并輸出到標準輸出。
(1)在vi編輯器中編輯該程序。
程序清單14-15? rcv_msg.c?使用msgrcv函數從指定隊列中讀出消息
#include <sys/msg.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <stdlib.h>
#define BUFSZ 4096
struct msg{???????????? /*聲明消息結構體*/
??? long msg_types;???? /*消息類型成員*/???
??? char msg_buf[511];? /*消息*/
};
int main( int argc, char * argv[] ) {
??? int???? qid;
??? int ??????? len;
??? struct msg pmsg;
??? if ( argc != 2 ){? /**/
??????? perror ( "USAGE: read_msg <queue ID>" );
??????? exit ( 1 );
??? }
???
??? qid = atoi ( argv[1] );?? /*從命令行中獲得消息隊列的ID*/
??? /*從指定隊列讀取消息?*/
??? len = msgrcv ( qid, &pmsg, BUFSZ, 0, 0 );
???
??? if ( len > 0 ){
??????? pmsg.msg_buf[len] = '/0';?????????????????????? /*為消息添加結束符*/
??????? printf ("reading queue id :%05ld/n", qid ); /*輸出隊列ID*/
??????? /*該消息類型就是發送消息的進程ID*/
??????? printf ("message type : %05ld/n", pmsg.msg_types );
??????? printf ("message length : %d bytes/n", len );?? /*消息長度*/
??????? printf ("mesage text: %s/n", pmsg.msg_buf); /*消息內容*/
??? }
??? else if ( len == 0 )
??????? printf ("have no message from queue %d/n", qid );
??? else {
??????? perror ( "msgrcv");
??????? exit (1);
??? }
??? system("ipcs -q")??
??? exit ( 0 ) ;
}
(2)在shell中編譯該程序如下:
$gcc rcv_msg.c–o rcv _msg
(3)在shell中運行該程序如下:
$./ rcv_msg 0
reading queue id :0
message type : 03662
message length : 20 bytes
mesage text: hello!this is :3662
------ Message Queues --------
key??????? ??msqid????? owner????? perms????? used-bytes?? messages???
0x00000000 ?0????????? root?????? ?666??????? ?0??????????? 0??????????
該程序中聲明了一個消息的結構體類型變量,并從命令行中得到所要操作的消息隊列,然后使用函數msgrcv從指定消息隊列中讀取隊列中最上面的一條消息(函數的第4個參數等于0,說明根據先進先出規則,應從隊列的最上面讀取一條消息),并將該消息輸出到標準輸出。在發送消息的程序中,消息類型字段指定的是發送消息進程的ID,可以使用該內容來判斷信息的來源。
總結
以上是生活随笔為你收集整理的Linux进程间通讯之消息队列的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux进程间通信——管道
- 下一篇: linux通信--信号量