IPC之訊息佇列詳解與使用
一、 概念
訊息佇列就是一個訊息的連結串列。對訊息佇列有寫許可權的程序可以向其中按照一定的規則新增新訊息;對訊息佇列有讀許可權的程序可以從訊息佇列中讀出訊息。訊息佇列是隨核心持續的。下面介紹三個概念:
1;隨程序持續:IPC一直存在,直至開啟IPC物件的最後一個程序關閉該物件為止,如管道和有名管道
2;隨核心持續:IPC一直持續到核心重新自舉或者顯示刪除物件為止。如:訊息佇列,訊號量,共享記憶體
3;隨檔案系統持續:IPC一直持續的顯示刪除該物件為止
System V訊息佇列目前被大量使用。
二、 訊息佇列的資訊
每個訊息佇列都有一個佇列頭,用結構struct msg_queue來描述。佇列頭中包含了該訊息佇列的大量資訊,包括訊息佇列鍵值、使用者ID、組ID、訊息佇列中訊息數目等等,甚至記錄了最近對訊息佇列讀寫程序的ID。讀者可以訪問這些資訊,也可以設定其中的某些資訊。這個結構存於系統空間。
struct msg_queue {
structkern_ipc_perm q_perm;
time_tq_stime; /* last msgsndtime */
time_tq_rtime; /* last msgrcvtime */
time_tq_ctime; /* last changetime */
unsignedlong q_cbytes; /* current number of bytes on queue*/
unsignedlong q_qnum; /* number of messages inqueue */
unsignedlong q_qbytes; /* max number of bytes on queue */
pid_tq_lspid; /* pid oflast msgsnd */
pid_tq_lrpid; /* lastreceive pid */
structlist_head q_messages;
structlist_head q_receivers;
structlist_head q_senders;
};
結構msqid_ds用來設定或返回訊息佇列的資訊,存在於使用者空間;
structmsqid_ds{
struct ipc_perm msg_perm;
struct msg *msg_first; /* first message on queue,unused*/
struct msg *msg_last; /* last message in queue,unused*/
__kernel_time_t msg_stime; /* last msgsnd time */
__kernel_time_t msg_rtime; /* last msgrcv time */
__kernel_time_t msg_ctime; /* last change time */
unsigned long msg_lcbytes; /* Reuse junk fields for 32bit */
unsigned long msg_lqbytes; /* ditto */
unsigned short msg_cbytes; /* current number of byteson queue */
unsigned short msg_qnum; /* number of messages in queue*/
unsigned short msg_qbytes; /* max number of bytes onqueue */
__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */
__kernel_ipc_pid_t msg_lrpid; /* last receive pid*/
};
三、 開啟建立訊息佇列
訊息佇列的核心持續性要求每個訊息佇列都在系統範圍內對應唯一的鍵值,所以,要獲得一個訊息佇列的描述字,只需提供該訊息佇列的鍵值即可。msgget用於建立一個訊息佇列或開啟一個現存的佇列。
名稱:: |
msgget |
功能: |
建立訊息佇列 |
標頭檔案: |
#include <sys/types.h> #include <sys/msg.h> #inlcude <sys/ipc.h> |
函式原形: |
int msgget(key_t key,int msgflag); |
引數: |
key 訊息佇列的鍵 flag 一些標誌位 |
返回值: |
若成功則為訊息佇列描述字若出錯則為-1。 |
引數key是一個鍵值,由ftok獲得;msgflg引數是一些標誌位。該呼叫返回與健值key相對應的訊息佇列描述字。
在以下兩種情況下,該呼叫將建立一個新的訊息佇列:
1.如果沒有訊息佇列與健值key相對應,並且msgflg中包含了IPC_CREAT標誌位;
2.key引數為IPC_PRIVATE;
引數msgflg可以為以下:IPC_CREAT(建立訊息佇列)、IPC_EXCL( )、IPC_NOWAIT( )或三者的或結果。
還有注意的是:當建立一個新佇列時,系統自動初始化struct msqid_ds結構的下列成員。
ipc_perm結構按我們以前說的進行初始化。該結構中mode成員按flag中的相應許可權位設定。
msg_qnum,msg_lspid,msg_lrpid,msg_stime,msg_rtime都設定為0。
msg_ctime設定為當前時間。
msg_qbytes設定為系統限制值。
四、獲得和修改訊息佇列屬性,刪除訊息佇列
名稱:: |
msgctl |
功能: |
對訊息佇列進行多種操作 |
標頭檔案: |
#include <sys/msg.h> |
函式原形: |
int msgctl(int msqid, int cmd,struct msqid_ds *buf); |
引數: |
msqid 訊息佇列描述字 cmd 要執行的操作 buf 此佇列的struct msqid_ds結構 |
返回值: |
若成功返回0,若出錯返回-1。 |
該系統呼叫對由msqid標識的訊息佇列執行cmd操作,共有三種cmd操作:IPC_STAT、IPC_SET、IPC_RMID。
IPC_STAT:該命令用來獲取訊息佇列資訊,返回的資訊存貯在buf指向的msqid_da結構中;
IPC_SET:該命令用來設定訊息佇列的屬性,要設定的屬性儲存在buf指向的msqid_ds結構中;可設定屬性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes,同時,也影響msg_ctime成員。
IPC_RMID:刪除msqid_ds標識的訊息佇列.
五、用訊息佇列傳送和接收訊息
名稱:: |
msgsnd |
功能: |
將資料放到訊息佇列上 |
標頭檔案: |
#include <sys/types.h> #include <sys/msg.h> #inlcude <sys/ipc.h> |
函式原形: |
int msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg); |
引數: |
msqid 訊息佇列描述字 msgp 指向訊息資料的指標 msgsz 傳送訊息的大小 msgflg 標誌位 |
返回值: |
若成功則為0,若出錯則為-1。 |
向msgid代表的訊息佇列傳送一個訊息,即將傳送的訊息儲存在msgp指向的msgbuf結構中,訊息的大小由msgze指定。
structmsgbuf{
longmtype; /*訊息型別*/
charmtext[1]; /*訊息資料*/
};
我們可以把msgbuf結構看成是一個模版,程式設計師可以根據自己的需要來設計直接的訊息結構。舉例來說,如果某個應用需要交換由一個整數後跟一個8位元組字元陣列構成的訊息,那它可以如下定義自己的結構:
typedefstruct my_msgbuf{
longmtypel
int mshort;
char mchar[MY_DATA];
}Message;
對傳送訊息來說,有意義的msgflg標誌為IPC_NOWAIT,指明在訊息佇列沒有足夠空間容納要傳送的訊息時,msgsnd是否等待。造成msgsnd()等待的條件有兩種:
1.當前訊息的大小與當前訊息佇列中的位元組數之和超過了訊息佇列的總容量;
2.當前訊息佇列的訊息數(單位"個")不小於訊息佇列的總容量(單位"位元組數"),此時,雖然訊息佇列中的訊息數目很多,但基本上都只有一個位元組。
msgsnd()解除阻塞的條件有三個:
1.不滿足上述兩個條件,即訊息佇列中有容納該訊息的空間;
2.msqid代表的訊息佇列被刪除;
3.呼叫msgsnd()的程序被訊號中斷;
當msgsnd成功返回,與訊息佇列相關的msqid_ds結構得到更新,以標明發出該呼叫的程序ID(msg_lspid),進行該呼叫的時間(msg_stime),並指示佇列中增加了一條訊息。
六、訊息佇列的使用
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
void msg_stat(int,struct msqid_ds );
int main(void)
{
int gflags,sflags,rflags;
key_t key;
int msgid;
int reval;
struct msgsbuf
{
int mtype;
char mtext[1];
}msg_sbuf; /*傳送訊息緩衝區資料結構*/
struct msgmbuf
{
int mtype;
char mtext[10];
}msg_rbuf; /*接收訊息緩衝區資料結構*/
struct msqid_ds msg_ginfo,msg_sinfo;
char* msgpath="/unix/msgqueue";
key=ftok(msgpath,'a'); /*獲取訊息佇列鍵值*/
gflags=IPC_CREAT|IPC_EXCL;
msgid=msgget(key,gflags|00666); /*呼叫msgget建立訊息佇列*/
if(msgid==-1)
{
printf("msg create error\n");
return;
}
msg_stat(msgid,msg_ginfo);
/*建立一個訊息佇列後,輸出訊息佇列預設屬性。第一次呼叫msg_stat 子函式*/
sflags=IPC_NOWAIT; /*訊息佇列滿時,msgsnd 不等待,立刻出錯返回*/
msg_sbuf.mtype=10;
msg_sbuf.mtext[0]='a'; /*將要傳送的訊息資料*/
reval=msgsnd(msgid,&msg_sbuf,sizeof(msg_sbuf.mtext),sflags); /*呼叫msgsnd傳送訊息*/
if(reval==-1)
{
printf("message send error\n");
}
msg_stat(msgid,msg_ginfo);
/*成功傳送一個訊息後,輸出此時訊息佇列屬性。第二次呼叫msg_stat 子函式*/
rflags=IPC_NOWAIT|MSG_NOERROR; /*含義見表10.1*/
reval=msgrcv(msgid,&msg_rbuf,4,10,rflags);
/*呼叫msgrcv接收訊息,接收資料長度為4,type > 0,含義見表10.2*/
if(reval==-1)
{
printf("read msg error\n");
}
else
{
printf("read from msg queue %d bytes\n",reval); /*列印接收到資料的位元組數*/
}
msg_stat(msgid,msg_ginfo);
/*從訊息佇列中讀出訊息後,再次輸出訊息佇列屬性。第三次呼叫msg_stat 子函式*/
msg_sinfo.msg_perm.uid=8;
/*試圖更改訊息佇列的預設屬性(要求root使用者許可權),所有者有效使用者ID更改為8*/
msg_sinfo.msg_perm.gid=8;
/*訊息佇列的所有者有效組ID更改為8*/
msg_sinfo.msg_qbytes=16388;
/*訊息佇列可容納最大位元組數更改為16388(預設為16384)*/
reval=msgctl(msgid,IPC_SET,&msg_sinfo); /*呼叫msgctl設定訊息佇列屬性*/
if(reval==-1)
{
printf("msg set info error\n");
return;
}
msg_stat(msgid,msg_ginfo); /*驗證設定訊息佇列屬性。第三次呼叫msg_stat 子函式*/
reval=msgctl(msgid,IPC_RMID,NULL); /* 操作完畢,呼叫msgctl刪除訊息佇列*/
if(reval==-1)
{
printf("unlink msg queue error\n");
return;
}
return 0;
}
void msg_stat (int msgid,struct msqid_ds msg_info)
{
int reval;
sleep(1); /*只是為了後面輸出時間的方便*/
reval=msgctl(msgid,IPC_STAT,&msg_info); /*呼叫msgctl 獲得訊息佇列屬性資訊*/
if(reval==-1)
{
printf("get msg info error\n");
return;
}
printf("\n");
printf("current number of bytes on queue is %d\n",msg_info.msg_cbytes);
printf("number of messages in queue is %d\n",msg_info.msg_qnum);
printf("max number of bytes on queue is %d\n",msg_info.msg_qbytes);
/*每個訊息佇列的容量(位元組數)都有限制MSGMNB,值的大小因系統而異。在建立*/
/*新的訊息佇列時,msg_qbytes的預設值就是MSGMNB*/
printf("pid of last msgsnd is %d\n",msg_info.msg_lspid);
/*最近一個執行msgsnd函式的程序ID*/
printf("pid of last msgrcv is %d\n",msg_info.msg_lrpid);
/*最近一個執行msgrcv函式的程序ID*/
printf("last msgsnd time is %s", ctime(&(msg_info.msg_stime)));
/*最近一次執行msgsnd函式的時間。ctime()將時間轉變成周、月、日、時分秒、年的*/
/*形式,屬於標準C函式*/
printf("last msgrcv time is %s", ctime(&(msg_info.msg_rtime)));
/*最近一次執行msgrcv函式的時間*/
printf("last change time is %s", ctime(&(msg_info.msg_ctime)));
/*最近一次改變該訊息佇列的時間*/
printf("msg uid is %d\n",msg_info.msg_perm.uid);/*訊息佇列所有者的有效使用者ID*/
printf("msg gid is %d\n",msg_info.msg_perm.gid); /*訊息佇列所有者的有效組ID*/
}