Linux程序間通信--消息隊列
- 消息隊列(message queue)
-
- 1.特點
- 2.消息隊列的使用
-
- 消息隊列的格式
- ftok()函數擷取鍵值
- msgget()函數
- msgsnd()函數
- msgrcv()函數
- msgctl()函數
- 3.模拟實作消息隊列
消息隊列(message queue)
消息隊列提供了一種從一個程序向另一個程序發送一個資料塊的方法。消息隊列是消息的連結表,存放在核心中并由消息隊列辨別符辨別。 每個資料塊都被認為含有一個類型,接收程序可以獨立地接收含有不同類型的資料結構。
我們可以通過發送消息來避免命名管道的同步和阻塞問題(命名管道要讀端和寫端都存在,否則出現阻塞)。但是消息隊列與命名管道一樣,每個資料塊都有一個最大長度的限制。
1.特點
- 消息隊列可以實作消息的随機查詢。消息不一定要以先進先出的次序讀取,程式設計時可以按消息的類型讀取;
- 消息隊列允許一個或多個程序向它寫入或者讀取消息;
- 與無名管道、命名管道一樣,從消息隊列中讀出消息,消息隊列中對應的資料都會被删除;
- 每個消息隊列都有消息隊列辨別符,消息隊列的辨別符在整個系統中是唯一的;
- 消息隊列是消息的連結清單,存放在記憶體中,由核心維護。隻有核心重新開機或人工删除消息隊列時,該消息隊列才會被删除。若不人工删除消息隊列,消息隊列會一直存在于系統中。
2.消息隊列的使用
在消息隊列操作中,同一個鍵(key)值可以保證是同一個消息隊列,同一個消息隊列标示符才能保證不同的程序可以互相通信,同一個消息類型才能保證某個程序取出是對方的資訊。
消息隊列的格式
typedef struct _msg
{
long mtype; // 消息類型
char mtext[100]; // 消息正文
//…… …… // 消息的正文可以有多個成員
}MSG;
消息類型必須是長整型的,而且必須是結構體類型的第一個成員,類型下面是消息正文,正文可以有多個成員(正文成員可以是任意資料類型的)。至于這個結構體類型叫什麼名字,裡面成員叫什麼名字,自行定義,沒有明文規定。
ftok()函數擷取鍵值
System V 提供的程序間通信機制需要一個鍵值,通過鍵值就可在系統内獲得一個唯一的消息隊列辨別符。鍵值可以是人為指定的,也可以通過 ftok() 函數獲得。
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
- 功能:擷取鍵(key)值。
- 參數:
- pathname: 路徑名;
- proj_id: 項目ID,非 0 整數(隻有低 8 位有效);
- 傳回值:成功傳回鍵值,失敗傳回-1。
msgget()函數
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
- 功能:建立一個新的或打開一個已經存在的消息隊列。不同的程序調用此函數,隻要用相同的 key 值就能得到同一個消息隊列的辨別符。
- 參數:
key: ftok() 傳回的 key 值。
msgflg: 辨別函數的行為及消息隊列的權限,其取值如下:
- IPC_CREAT:建立消息隊列;
- IPC_EXCL: 檢測消息隊列是否存在;
- 位或權限位:消息隊列位或權限位後可以設定消息隊列的通路權限,格式和open() 函數的 mode_t 一樣(open() 的使用請點此連結),但可執行權限未使用。
- 傳回值:成功傳回消息隊列的辨別符,失敗傳回-1。
msgsnd()函數
#include <sys/msg.h>
int msgsnd( int msqid, const void *msgp,
size_t msgsz, int msgflg);
- 功能:将新消息添加到消息隊列。
- 參數:
msqid: 消息隊列的辨別符;
msgp: 待發送消息結構體的位址;
msgsz: 消息正文的位元組數;
msgflg: 函數的控制屬性,其取值如下:
- 0:msgsnd() 調用阻塞直到條件滿足為止。
IPC_NOWAIT:若消息沒有立即發送則調用該函數的程序會立即傳回。
傳回值:成功:0,失敗:-1。
傳回值:成功時傳回0,失敗時傳回-1。
msgrcv()函數
#include <sys/msg.h>
ssize_t msgrcv( int msqid, void *msgp, size_t msgsz,
long msgtyp, int msgflg );
- 功能:從辨別符為 msqid 的消息隊列中接收一個消息。一旦接收消息成功,則消息在消息隊列中被删除。
- 參數:
msqid:消息隊列的辨別符,代表要從哪個消息列中擷取消息;
msgp: 存放消息結構體的位址;
msgsz:消息正文的位元組數;
msgtyp:消息的類型。可以有以下幾種類型:
msgflg:函數的控制屬性。其取值如下:
- msgtyp = 0:傳回隊列中的第一個消息;
- msgtyp > 0:傳回隊列中消息類型為 msgtyp 的消息(常用);
- msgtyp < 0:傳回隊列中消息類型值小于或等于 msgtyp 絕對值的消息,如果這種消息有若幹個,則取類型值最小的消息(在擷取某類型消息的時候,若隊列中有多條此類型的消息,則擷取最先添加的消息,即先進先出原則)。
- 0:msgrcv() 調用阻塞直到接收消息成功為止;
- MSG_NOERROR: 若傳回的消息位元組數比 nbytes 位元組數多,則消息就會截短到 nbytes 位元組,且不通知消息發送程序;
- IPC_NOWAIT: 調用程序會立即傳回。若沒有收到消息則立即傳回 -1。
傳回值:成功傳回讀取消息的長度;失敗傳回-1。
msgctl()函數
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
- 功能:對消息隊列進行各種控制,如修改消息隊列的屬性,或删除消息消息隊列。
- 參數:
msqid: 消息隊列的辨別符;
cmd:函數功能的控制。其取值如下:
buf:msqid_ds 資料類型的位址,用來存放或更改消息隊列的屬性。
- IPC_RMID:删除由 msqid 訓示的消息隊列,将它從系統中删除并破壞相關資料結構;
- IPC_STAT:将 msqid 相關的資料結構中各個元素的目前值存入到由 buf 指向的結構中。相對于,把消息隊列的屬性備份到 buf 裡;
- IPC_SET:将 msqid 相關的資料結構中的元素設定為由 buf 指向的結構中的對應值。相當于,消息隊列原來的屬性值清空,再由 buf 來替換。
傳回值:成功傳回0;失敗傳回-1。
3.模拟實作消息隊列
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;
// 建立消息隊列, key_t為辨別符
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if(msgid == -1)
{
perror("msgget");
exit(1);
}
// 向消息隊列中寫消息
while(running)
{
// 輸入資料
printf("Send Message: ");
fgets(buffer, BUFSIZ, stdin); // 從标準輸入讀取資料
data.msg_type = 1; // 消息類型
strcpy(data.text, buffer); // 把标準輸入複制到msg_st的text中
// 向隊列發送資料
if(msgsnd(msgid, (void*)&data, MAX_TEXT, 0) == -1)
{
perror("msgsnd");
exit(1);
}
// 輸入end結束
if(strncmp(buffer, "end", 3) == 0)
running = 0;
sleep(1);
}
return 0;
}
MsgRcv.c
// 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; // 指定想要哪一種消息,type=0表示取第一個
//建立消息隊列 key_t為辨別符
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if(msgid == -1)
{
perror("msgget");
exit(1);
}
//從隊列中擷取消息,直到遇到end為止
while(running)
{
if(msgrcv(msgid, (void*)&data, BUFSIZ, msgtype, 0) == -1)
{
perror("msgrcv");
exit(1);
}
printf("Recive Message: %s\n", data.text);
//遇到end結束
if(strncmp(data.text, "end", 3) == 0)
running = 0;
}
//删除消息隊列
if(msgctl(msgid, IPC_RMID, 0) == -1)
{
perror("msgctl");
exit(1);
}
return 0;
}