Linux程序間通訊(IPC)程式設計實踐(三) 詳解System V訊息佇列(1)
阿新 • • 發佈:2019-02-10
訊息佇列簡介
訊息佇列提供了一個從一個程序向另外一個程序傳送一塊資料的方法(本機);每個資料塊都被認為是有一個型別,接收者程序接收的資料塊可以有不同的型別值。訊息佇列也有管道一樣的不足:
(1)每個訊息的最長位元組數的上限(MSGMAX);
(2)系統中訊息佇列的總條數也有一個上限(MSGMNI);
(3)每個訊息佇列所能夠儲存的總位元組數是有上限的(MSGMNB) .
訊息佇列與管道的區別:最主要的區別是管道通訊是要求兩個程序之間要有親緣關係,只能承載無格式的位元組流,而訊息隊列當中,通訊的兩個程序之間可以是完全無關的程序,它是有格式的(可類比TCP\UDP)。至於它與有名管道的區別,首先FIFO是要儲存在磁碟上的一種通訊方式,而訊息佇列是在記憶體中的。
檢視系統中的三個限制的上限:
IPC物件資料結構
//核心為每個IPC物件維護一個數據結構 struct ipc_perm { key_t __key; /* Key supplied to msgget(2) */ uid_t uid; /* Effective UID of owner */ gid_t gid; /* Effective GID of owner */ uid_t cuid; /* Effective UID of creator */ gid_t cgid; /* Effective GID of creator */ unsigned short mode; /* Permissions */ unsigned short __seq; /* Sequence number */ };
訊息佇列在核心中的表示//訊息佇列特有的結構 struct msqid_ds { struct ipc_perm msg_perm; /* Ownership and permissions 各類IPC物件所共有的資料結構*/ time_t msg_stime; /* Time of last msgsnd(2) */ time_t msg_rtime; /* Time of last msgrcv(2) */ time_t msg_ctime; /* Time of last change */ unsigned long __msg_cbytes; /* Current number of bytes in queue (nonstandard) 訊息佇列中當前所儲存的位元組數 */ msgqnum_t msg_qnum; /* Current number of messages in queue 訊息佇列中當前所儲存的訊息數 */ msglen_t msg_qbytes; /* Maximum number of bytes allowed in queue 訊息佇列所允許的最大位元組數 */ pid_t msg_lspid; /* PID of last msgsnd(2) 最後一個傳送資料的程序號*/ pid_t msg_lrpid; /* PID of last msgrcv(2) 最後一個接受的程序號*/ };
訊息在訊息佇列中是以連結串列形式組織的, 每個節點的型別類似如下:
struct msq_Node
{
Type msq_type; //型別
Length msg_len; //長度
Data msg_data; //資料
struct msg_Node *next;
};
訊息佇列的基本API
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
本節先介紹前2個API,傳送和接受訊息的API請參考下篇部落格。
msgget
int msgget(key_t key, int msgflg);
引數:
key: 某個訊息佇列的名字
msgflg:由九個許可權標誌構成,如0644,它們的用法和建立檔案時使用的mode模式標誌是一樣的(但是訊息佇列沒有x(執行)許可權)
返回值:
成功返回訊息佇列編號,即該訊息佇列的標識碼;失敗返回-1
接下來我們舉幾個例項來看一下建立訊息佇列的不同操作及其結果:
/** 示例1: 在msgflg處指定IPC_CREAT, 如果不存在該訊息佇列, 則建立之**/
int main(int argc, char *argv[])
{
//指定IPC_CREAT,如果不存在, 則建立訊息佇列
int msgid = msgget(1234, 0666|IPC_CREAT);
if (msgid == -1)
err_exit("msgget error");
cout << "msgget success" << endl;
}
注意:KEY是16進位制顯示。我們可以使用命令 ipcrm -q 刪除訊息佇列或者 ipcrm -Q [key](此時key!=0); Ipcs檢視訊息佇列;要指定IPC_CREAT,才可建立,類似於檔案的creat
/** 示例2:IPC_CREAT|IPC_EXCL, 如果該訊息佇列已經存在, 則返回出錯 **/
int main(int argc, char *argv[])
{
//指定IPC_EXCL, 如果已經存在,則報告檔案已經存在(錯誤)
int msgid = msgget(1234, 0666|IPC_CREAT|IPC_EXCL);
if (msgid == -1)
err_exit("msgget error");
cout << "msgget success" << endl;
}
/**示例3:將key指定為IPC_PRIVATE(值為0)
將key指定為IPC_PRIVATE之後,則每呼叫一次msgget會建立一個新的訊息佇列
而且每次建立的訊息佇列的描述符都是不同的! 因此, 除非將MessageID(key)傳送給其他程序(除非有關聯的程序),否則其他程序也無法使用該訊息佇列
但是具有親緣程序的是可以使用的(fork)
因此, IPC_PRIVATE建立的訊息佇列,只能用在與當前程序有關係的程序中使用!
**/
這也意味著程序不能共享這個訊息佇列,父子兄弟程序是可以的
int main(int argc, char *argv[])
{
//指定IPC_PRIVATE
int msgid = msgget(IPC_PRIVATE, 0666|IPC_CREAT|IPC_EXCL);
if (msgid == -1)
err_exit("msgget error");
cout << "msgget success" << endl;
}
以上KEY為0x00000000的都是使用IPC_PRIVATE建立的。
/** 示例4: 僅開啟訊息佇列時, msgflg選項可以直接忽略(填0), 此時是以訊息佇列建立時的許可權進行開啟
**/
int main(int argc, char *argv[])
{
int msgid = msgget(1234, 0);
if (msgid == -1)
err_exit("msgget error");
cout << "msgget success" << endl;
cout << "msgid = " << msgid << endl;
}
//示例5:低許可權建立,高許可權開啟
int main()
{
//低許可權建立
int msgid = msgget(0x255,0444 | IPC_CREAT);
if (msgid < 0)
err_exit("mesget error");
else
cout << "Create Mes OK, msgid = " << msgid << endl;
//高許可權開啟
msgid = msgget(0x255,0644 | IPC_CREAT);
if (msgid < 0)
err_exit("mesget error");
else
cout << "Create Mes OK, msgid = " << msgid << endl;
}
會發生“Permission denied”的錯誤。msgctl函式
功能:獲取/設定訊息佇列的資訊
- int msgctl(int msqid, int cmd, struct msqid_ds *buf);
引數:
msqid: 由msgget函式返回的訊息佇列標識碼
/** 示例1: IPC_RMID, 刪除訊息佇列
注意: 訊息佇列並沒有運用”引用計數”的功能
**/
int main()
{
int msgid = msgget(1234, 0);
if (msgid == -1)
err_exit("msgget error");
if (msgctl(msgid, IPC_RMID, NULL) == -1)
err_exit("msgctl IPC_RMID error");
cout << "msgctl IPC_RMID success" << endl;
}
/** 示例2: IPC_STAT,訊息佇列相關資訊讀入buf
**/
int main()
{
int msgid = msgget(0x255, 0666|IPC_CREAT);
if (msgid == -1)
err_exit("msgget error");
struct msqid_ds buf;
if (msgctl(msgid,IPC_STAT,&buf) == -1)
err_exit("msgctl error");
printf("buf.msg_perm.mode = %o\n",buf.msg_perm.mode); //%o以八進位制列印
printf("buf.__key = %x\n", buf.msg_perm.__key); //%x以十六進位制列印
cout << "buf.__msg_cbytes = " << buf.__msg_cbytes << endl;
cout << "buf.msg_qbytes = " << buf.msg_qbytes << endl;
cout << "buf.msg_lspid = " << buf.msg_lspid << endl;
}
** 實踐:IPC_SET,一般需要先獲取,然後再設定
**/
int main()
{
int msgid = msgget(0x255, 0);
if (msgid == -1)
err_exit("msgget error");
//獲取訊息佇列的屬性
struct msqid_ds buf;
if (msgctl(msgid,IPC_STAT,&buf) == -1)
err_exit("msgctl error");
//設定訊息佇列的屬性
buf.msg_perm.mode = 0600;
if (msgctl(msgid, IPC_SET, &buf) == -1)
err_exit("msgctl error");
//獲取並列印
bzero(&buf, sizeof(buf));
if (msgctl(msgid, IPC_STAT, &buf) == -1)
err_exit("msgctl IPC_STAT error");
printf("mode = %o\n", buf.msg_perm.mode);
}