1 概述
1.1 posix消息隊列與system v消息隊列的差別
POSIX表示可移植操作系統接口 ( Portable Operating System Interface of UNIX,縮寫爲 POSIX )。
(1) 對posix消息隊列的讀總是返回最高優先級的最早消息,對system v消息隊列的讀則可以返回任意指定優先級的消息。
(2) 當往一個空隊列放置一個消息時,posix消息隊列允許產生一個信號或啓動一個線程,system v消息隊列則不提供類似機制。
1.2 隊列中的每個消息具有如下屬性
(1) 一個無符號整數優先級(posix)或一個長整數類型(system v)
(2) 消息的數據部分長度(可以爲0)
(3) 數據本身(如果長度大於0)
2 Posix消息隊列操作函數
2.1 創建/獲取一個消息隊列
mqd_t mq_open(const char *name, int oflag); //專用於打開一個消息隊列
mqd_t mq_open(const char *name, int oflag, mode_t mode,
struct mq_attr *attr);
參數:
name: 消息隊列名字;
oflag: 與open函數類型, 可以是O_RDONLY, O_WRONLY, O_RDWR, 還可以按位或上O_CREAT, O_EXCL, O_NONBLOCK.
mode: 如果oflag指定了O_CREAT, 需要指定mode參數;
attr: 指定消息隊列的屬性;
返回值:
成功: 返回消息隊列文件描述符;
失敗: 返回-1;
注意-Posix IPC名字限制:
(1) 必須以"/"開頭, 並且後面不能還有"/", 形如:/file-name;
(2) 名字長度不能超過NAME_MAX
(3) 鏈接時:Link with -lrt(Makefile中使用實時鏈接庫-lrt)
類似對文件的open,我們可以用mq_open來打開一個已經創建的消息隊列或者創建一個消息隊列。這個函數返回一個叫做mqd_t類型的返回值,其本質上還是一個文件描述符,只是在這這裏被叫做消息隊列描述符(message queue descriptor),在進程裏使用這個描述符對消息隊列進程操作。所有被創建出來的消息隊列在系統中都有一個文件與之對應,這個文件名是通過name參數指定的,這裏需要注意的是:name必須是一個以”/”開頭的字符串,比如我想讓消息隊列的名字叫”message”,那麼name應該給的是”/message”。消息隊列創建完畢後,會在/dev/mqueue目錄下產生一個以name命名的文件,我們還可以通過cat這個文件來看這個消息隊列的一些狀態信息。其它進程在消息隊列已經存在的情況下就可以通過mp_open打開名爲name的消息隊列來訪問它。
[root@localhost mqueue]# cat /dev/mqueue/test
QSIZE:48 NOTIFY:0 SIGNO:10 NOTIFY_PID:4442
[root@localhost mqueue]#
2.2 關閉一個消息隊列
#include <mqueue.h>
int mq_close(mqd_t mqdes);
返回: 成功時爲0,出錯時爲-1。
功能: 關閉已打開的消息隊列。
注意:System V沒有此功能函數調用
2.3 刪除一個消息隊列
int mq_unlink(const char *name);
/**
System V 消息隊列
通過msgctl函數, 並將cmd指定爲IPC_RMID來實現
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
**/
返回: 成功時爲0,出錯時爲-1
功能: 從系統中刪除消息隊列。
2.3.1 mqueue_test.cpp
#include <stdio.h>
#include <mqueue.h>
int main()
{
mqd_t mqid = mq_open("/abc", O_CREAT|O_RDONLY, 0666, NULL);
if (mqid == -1)
{
printf("mq_open error!\n");
return -1;
}
printf("mq_open success!\n");
mq_close(mqid);
mq_unlink("/abc");
printf("unlink success!\n");
return 0;
}
編譯
g++ -o mqueue_test ./mqueue_test.cpp -lrt
2.4 獲取/設置消息隊列屬性
#include <mqueue.h>
int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
int mq_setattr(mqd_t mqdes, const struct mq_attr *attr, struct mq_attr *attr);
均返回:成功時爲0, 出錯時爲-1
參數:
newattr: 需要設置的屬性
oldattr: 原來的屬性
每個消息隊列有四個屬性:
struct mq_attr
{
long mq_flags; /* message queue flag : 0, O_NONBLOCK */
long mq_maxmsg; /* max number of messages allowed on queue*/
long mq_msgsize; /* max size of a message (in bytes)*/
long mq_curmsgs; /* number of messages currently on queue */
};
2.4.1 mqueue_get.cpp
#include <stdio.h>
#include <mqueue.h>
int main(int argc,char **argv)
{
mqd_t mqid = mq_open("/test", O_RDONLY|O_CREAT, 0666, NULL);
if (mqid == -1)
{
printf("mq_open error!\n");
return -1;
}
struct mq_attr attr;
if (mq_getattr(mqid, &attr) == -1)
{
printf("mq_getattr error!\n");
return -1;
}
printf("Max messages on queue: %ld\n", attr.mq_maxmsg);
printf("Max message size: %ld\n", attr.mq_msgsize);
printf("current messages: %ld\n", attr.mq_curmsgs);
mq_close(mqid);
return 0;
}
對比System V:
通過msgctl函數, 並將cmd指定爲IPC_STAT/IPC_SET來實現
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
另外每個消息均有一個優先級,它是一個小於MQ_PRIO_MAX的無符號整數
#define MQ_PRIO_MAX 32768
2.5 發送消息/讀取消息
int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio);
int mq_timedsend(mqd_t mqdes, const char *msg_ptr, size_t msg_len,
unsigned int msg_prio, const struct timespec *abs_timeout);
ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio);
ssize_t mq_timedreceive(mqd_t mqdes, char *msg_ptr, size_t msg_len,
unsigned int *msg_prio, const struct timespec *abs_timeout);
返回:成功時爲0,出錯爲-1
返回:成功時爲消息中的字節數,出錯爲-1
參數:
msg_prio 消息的優先級
abs_timeout 超時時間
消息隊列的限制:
MQ_OPEN_MAX : 一個進程能夠同時擁有的打開着消息隊列的最大數目
MQ_PRIO_MAX : 任意消息的最大優先級值加1
在一個消息隊列創建完畢之後,我們可以使用mq_send來對消息隊列發送消息,mq_receive來對消息隊列接收消息。正常的發送消息一般不會阻塞,除非消息隊列處在某種異常狀態或者消息隊列已滿的時候,而消息隊列在空的時候,如果使用mq_receive去試圖接受消息的行爲也會被阻塞,所以就有必要爲兩個方法提供一個帶超時時間的版本。這裏要注意的是msg_prio這個參數,是用來指定消息優先級的。每個消息都有一個優先級,取值範圍是0到sysconf(_SC_MQ_PRIO_MAX) – 1的大小。在Linux上,這個值爲32768。默認情況下,消息隊列會先按照優先級進行排序,就是msg_prio這個值越大的越先出隊列。同一個優先級的消息按照fifo原則處理。在mq_receive方法中的msg_prio是一個指向int的地址,它並不是用來指定取的消息是哪個優先級的,而是會將相關消息的優先級取出來放到相關變量中,以便用戶自己處理優先級。
2.5.1 mqueue_send.cpp
/** 示例: 向消息隊列中發送消息, prio需要從命令行參數中讀取 **/
#include <stdio.h>
#include <stdlib.h>
#include <mqueue.h>
struct Student
{
char name[36];
int age;
};
int main(int argc,char **argv)
{
if (argc != 2)
{
printf("./send <prio>\n");
return -1;
}
mqd_t mqid = mq_open("/test", O_WRONLY|O_CREAT, 0666, NULL);
if (mqid == -1)
{
printf("mq_open error!\n");
return -1;
}
struct Student stu = {"xiaofang", 23};
unsigned prio = atoi(argv[1]);
if (mq_send(mqid, (const char *)&stu, sizeof(stu), prio) == -1)
{
printf("mq_send error!\n");
return -1;
}
mq_close(mqid);
return 0;
}
2.5.2 mqueue_rec.cpp
/** 示例: 從消息隊列中獲取消息 **/
#include <stdio.h>
#include <stdlib.h>
#include <mqueue.h>
struct Student
{
char name[36];
int age;
};
int main(int argc,char **argv)
{
mqd_t mqid = mq_open("/test", O_RDONLY);
if (mqid == -1)
{
printf("mq_open error!\n");
return -1;
}
struct Student buf;
int nrcv;
unsigned prio;
struct mq_attr attr;
if (mq_getattr(mqid, &attr) == -1)
{
printf("mq_getattr error!\n");
return -1;
}
if ((nrcv = mq_receive(mqid, (char *)&buf, attr.mq_msgsize, &prio)) == -1)
{
printf("mq_receive error!\n");
return -1;
}
printf("receive %d bytes, priority: %d, name:%s, age:%d\n", nrcv, prio, buf.name, buf.age);
mq_close(mqid);
return 0;
}
2.6 建立/刪除消息到達通知事件
大家在從消息隊列接收消息的時候會發現,當消息隊列爲空的時候,mq_receive會阻塞,直到有人給隊列發送了消息才能返回並繼續執行。在很多應用場景下,這種同步處理的方式會給程序本身帶來性能瓶頸。爲此,POSI消息隊列使用mq_notify爲處理過程增加了一個異步通知機制。使用這個機制,我們就可以讓隊列在由空變成不空的時候觸發一個異步事件,通知調用進程,以便讓進程可以在隊列爲空的時候不用阻塞等待。這個方法的原型爲:
#include <mqueue.h>
int mq_notify(mqd_t mqdes, const struct sigevent *sevp);
返回: 成功時爲0,出錯時爲-1
功能: 給指定隊列建立或刪除異步事件通知
sigev_notify代表通知的方式: 一般常用兩種取值:SIGEV_SIGNAL, 以信號方式通知; SIGEV_THREAD, 以線程方式通知
如果以信號方式通知: 則需要設定一下兩個參數:
sigev_signo: 信號的代碼
sigev_value: 信號的附加數據(實時信號)
如果以線程方式通知: 則需要設定以下兩個參數:
sigev_notify_function
sigev_notify_attributes
union sigval
{
int sival_int; /* Integer value */
void *sival_ptr; /* pointer value */
};
struct sigevent
{
int sigev_notify; /* SIGEV_{ NONE, SIGNAL, THREAD} */
int sigev_signo; /* signal number if SIGEV_SIGNAL */
union sigval sigev_value; /* passed to signal handler or thread */
void (*sigev_notify_function)(union sigval);
pthread_attr_t *sigev_notify_attribute;
};
參數sevp:
NULL: 表示撤銷已註冊通知;
非空: 表示當消息到達且消息隊列當前爲空, 那麼將得到通知;
通知方式:
(1) 產生一個信號, 需要自己綁定
(2) 創建一個線程, 執行指定的函數
注意: 這種註冊的方式只是在消息隊列從空到非空時才產生消息通知事件, 而且這種註冊方式是一次性的!
** Posix IPC所特有的功能, System V沒有 **/
2.6.1 mqueue_notify1.cpp
/**
示例: 將下面程序多運行幾遍, 尤其是當消息隊列"從空->非空", 多次"從空->非空", 當消息隊列不空時運行該程序時, 觀察該程序的狀態;
**/
#include <stdio.h>
#include <stdlib.h>
#include <mqueue.h>
#include <signal.h>
#include <unistd.h>
struct Student
{
char name[36];
int age;
};
mqd_t mqid;
long size;
void sigHandlerForUSR1(int signo)
{
// 將數據的讀取轉移到對信號SIGUSR1的響應函數中來
struct Student buf;
int nrcv;
unsigned prio;
if ((nrcv = mq_receive(mqid, (char *)&buf, size, &prio)) == -1)
{
printf("mq_receive error!\n");
return;
}
printf("receive %d bytes, priority: %d, name: %s, age: %d", nrcv, prio, buf.name, buf.age);
}
int main(int argc,char **argv)
{
// 安裝信號響應函數
if (signal(SIGUSR1, sigHandlerForUSR1) == SIG_ERR)
{
printf("signal error!\n");
return -1;
}
mqid = mq_open("/test", O_RDONLY);
if (mqid == -1)
{
printf("mq_open error!\n");
return -1;
}
// 獲取消息的最大長度
struct mq_attr attr;
if (mq_getattr(mqid, &attr) == -1)
{
printf("mq_getattr error!\n");
return -1;
}
size = attr.mq_msgsize;
// 註冊消息到達通知事件
struct sigevent event;
event.sigev_notify = SIGEV_SIGNAL; // 指定以信號方式通知
event.sigev_signo = SIGUSR1; // 指定以SIGUSR1通知
if (mq_notify(mqid, &event) == -1)
{
printf("mq_notify error");
return -1;
}
//死循環, 等待信號到來
while (true)
{
printf("x\n");
sleep(1);
}
mq_close(mqid);
return 0;
}
編譯這個程序並執行:
[root@localhost mqueue]# ./mqueue_notify1
x
x
x
x
x
x
會一直打印x,等着隊列變爲非空,我們此時在別的終端給隊列發送一個消息:
[root@localhost mqueue]# ./mqueue_send 123
進程接收到信號,並且現實消息相關內容:
x
x
receive 40 bytes, priority: 123, name: xiaofang, age: 23x
x
x
再發一個試試:
[root@localhost mqueue]# ./mqueue_send 123
顯示:
x
x
x
x
這是因爲只註冊了一次, 所以後面就失效了。
2.6.2 mqueue_notify2.cpp
/**
示例:多次註冊notify, 這樣就能過多次接收消息, 但是還是不能從隊列非空的時候進行接收, 將程序改造如下:
**/
#include <stdio.h>
#include <stdlib.h>
#include <mqueue.h>
#include <signal.h>
#include <unistd.h>
struct Student
{
char name[36];
int age;
};
mqd_t mqid;
long size;
struct sigevent event;
void sigHandlerForUSR1(int signo)
{
// 注意: 是在消息被讀走之前進行註冊,
// 不然該程序就感應不到消息隊列"從空->非空"的一個過程變化了
if (mq_notify(mqid, &event) == -1)
{
printf("mq_notify error!\n");
return;
}
//將數據的讀取轉移到對信號SIGUSR1的響應函數中來
struct Student buf;
int nrcv;
unsigned prio;
if ((nrcv = mq_receive(mqid, (char *)&buf, size, &prio)) == -1)
{
printf("mq_receive error!\n");
return;
}
printf("receive %d bytes, priority: %d, name: %s, age: %d", nrcv, prio, buf.name, buf.age);
}
int main(int argc,char **argv)
{
// 安裝信號響應函數
if (signal(SIGUSR1, sigHandlerForUSR1) == SIG_ERR)
{
printf("signal error!\n");
return -1;
}
mqid = mq_open("/test", O_RDONLY);
if (mqid == -1)
{
printf("mq_open error!\n");
return -1;
}
// 獲取消息的最大長度
struct mq_attr attr;
if (mq_getattr(mqid, &attr) == -1)
{
printf("mq_getattr error!\n");
return -1;
}
size = attr.mq_msgsize;
// 註冊消息到達通知事件
event.sigev_notify = SIGEV_SIGNAL; //指定以信號方式通知
event.sigev_signo = SIGUSR1; //指定以SIGUSR1通知
if (mq_notify(mqid, &event) == -1)
{
printf("mq_notify error!\n");
return -1;
}
//死循環, 等待信號到來
while (true)
{
printf("x\n");
sleep(1);
}
mq_close(mqid);
return 0;
}
mq_notify 注意點總結:
(1) 任何時刻只能有一個進程可以被註冊爲接收某個給定隊列的通知;
(2) 當有一個消息到達某個先前爲空的隊列, 而且已有一個進程被註冊爲接收該隊列的通知時, 只有沒有任何線程阻塞在該隊列的mq_receive調用的前提下, 通知纔會發出;
(3) 當通知被髮送給它的註冊進程時, 該進程的註冊被撤銷. 進程必須再次調用mq_notify以重新註冊(如果需要的話),但是要注意: 重新註冊要放在從消息隊列讀出消息之前而不是之後(如同示例程序);
2.6.3 異步信號安全函數
#include <signal.h>
int sigwait(const sigset_t *set, int *sig);
可以使用sigwait函數代替信號處理程序的信號通知,將信號阻塞到某個函數中,僅僅等待該信號的遞交。採用sigwait實現上面的程序如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <mqueue.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
int main(int argc,char *argv[])
{
mqd_t mqd;
int signo;
void *buff;
ssize_t n;
sigset_t newmask;
struct mq_attr attr;
struct sigevent sigev;
if(argc != 2)
{
printf("usage :mqnotify <name>\n");
exit(0);
}
mqd = mq_open(argv[1],O_RDONLY);
mq_getattr(mqd,&attr);
buff = malloc(attr.mq_msgsize);
sigemptyset(&newmask);
sigaddset(&newmask,SIGUSR1);
sigprocmask(SIG_BLOCK,&newmask,NULL);
sigev.sigev_notify = SIGEV_SIGNAL;
sigev.sigev_signo = SIGUSR1;
if(mq_notify(mqd,&sigev) == -1)
{
perror("mq_notify error!\n");
exit(-1);
}
for(; ;)
{
sigwait(&newmask,&signo); //阻塞並等待該信號
if(signo == SIGUSR1)
{
mq_notify(mqd,&sigev);
while((n = mq_receive(mqd,(char*)buff,attr.mq_msgsize,NULL))>=0)
{
printf("read %ld bytes\n",(long) n);
}
if(errno != EAGAIN)
{
perror("mq_receive error!\n");
exit(-1);
}
}
}
exit(0);
}
啓動線程處理消息通知,程序如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <mqueue.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <pthread.h>
mqd_t mqd;
struct mq_attr attr;
struct sigevent sigev;
static void notify_thread(union sigval);
int main(int argc,char *argv[])
{
if(argc != 2)
{
printf("usage :mqnotify <name>\n");
exit(0);
}
mqd = mq_open(argv[1],O_RDONLY | O_NONBLOCK);
mq_getattr(mqd,&attr);
sigev.sigev_notify = SIGEV_THREAD;
sigev.sigev_value.sival_ptr = NULL;
sigev.sigev_notify_function = notify_thread;
sigev.sigev_notify_attributes = NULL;
if(mq_notify(mqd,&sigev) == -1)
{
perror("mq_notify error");
exit(-1);
}
for(; ;)
{
pause();
}
exit(0);
}
static void notify_thread(union sigval arg)
{
ssize_t n;
void *buff;
printf("notify_thread started\n");
buff = malloc(attr.mq_msgsize);
mq_notify(mqd,&sigev);
while((n = mq_receive(mqd, (char*)buff,attr.mq_msgsize,NULL))>=0)
{
printf("read %ld bytes\n",(long) n);
}
if(errno != EAGAIN)
{
perror("mq_receive error!\n");
exit(-1);
}
free(buff);
pthread_exit(NULL);
}
3 注意
3.1 查看已經成功創建的Posix消息隊列
#其存在與一個虛擬文件系統中, 需要將其掛載到系統中才能查看
Mounting the message queue filesystem On Linux, message queues are created in a virtual filesystem.
(Other implementations may also provide such a feature, but the details are likely to differ.) This
file system can be mounted (by the superuser, 注意是使用root用戶才能成功) using the following commands:
mkdir /dev/mqueue
mount -t mqueue none /dev/mqueue
還可以使用cat查看該消息隊列的狀態, rm刪除:
cat /dev/mqueue/abc
rm abc
還可umount該文件系統
umount /dev/mqueue
3.2 查看具體參數限制
在所有可以顯示的屬性中,O_NONBLOCK是mq_setattr唯一可以更改的參數設置,其他參數對於這個方法都是隻讀的,不能修改。系統提供了其他手段可以對這些限制進行修改:
(1) /proc/sys/fs/mqueue/msg_default:在mq_open的attr參數設置爲NULL的時候,這個文件中的數字限定了mq_maxmsg的值,就是隊列的消息個數限制。默認爲10個,當消息數達到上限之後,再使用mq_send發送消息會阻塞。
[root@localhost mqueue]# cat /proc/sys/fs/mqueue/msg_default
10
(2) /proc/sys/fs/mqueue/msg_max:可以通過mq_open的attr參數設定的mq_maxmsg的數字上限。這個值默認也是10。
[root@localhost mqueue]# cat /proc/sys/fs/mqueue/msg_max
10
(3) /proc/sys/fs/mqueue/msgsize_default:在mq_open的attr參數設置爲NULL的時候,這個文件中的數字限定了mq_msgsize的值,就是隊列的字節數數限制。
[root@localhost mqueue]# cat /proc/sys/fs/mqueue/msgsize_default
8192
(4) /proc/sys/fs/mqueue/msgsize_max:可以通過mq_open的attr參數設定的mq_msgsize的數字上限。
[root@localhost mqueue]# cat /proc/sys/fs/mqueue/msgsize_max
8192
(5) /proc/sys/fs/mqueue/queues_max:系統可以創建的消息隊列個數上限。
[root@localhost mqueue]# cat /proc/sys/fs/mqueue/queues_max
256
本文轉自:
https://blog.csdn.net/NK_test/article/details/50286309
https://blog.csdn.net/renwotao2009/article/details/52710206