Linux進程通信之POSIX消息隊列

消息隊列是 Linux IPC 中很常用的一種通信方式,它通常用來在不同進程間發送特定格式的消息數據。

消息隊列和之前討論過的 管道和 FIFO 有很大的區別,主要有以下兩點:

  • 一個進程向消息隊列寫入消息之前,並不需要某個進程在該隊列上等待該消息的到達,而管道和 FIFO 是相反的,進程向其中寫消息時,管道和 FIFO 必需已經打開來讀,否則寫進程就會阻塞(默認情況下)。
  •  IPC 的持續性不同。管道和 FIFO  隨進程 的持續性,當管道和 FIFO 最後一次關閉發生時,仍在管道和 FIFO 中的數據會被丟棄。消息隊列是 隨內核 的持續性,即一個進程向消息隊列寫入消息後,然後終止,另外一個進程可以在以後某個時刻打開該隊列讀取消息。只要內核沒有重新自舉,消息隊列沒有被刪除。

消息隊列中的每條消息通常具有以下屬性:

  • 一個表示優先級的整數;
  • 消息的數據部分的長度;
  •  消息數據本身;

POSIX 消息隊列的一個可能的設計是一個如下圖所示的消息鏈表,鏈表頭部有消息隊列的屬性信息。


圖 1 消息隊列的可能佈局

1 POSIX 消息隊列的創建和關閉

POSIX 消息隊列的創建,關閉和刪除用到以下三個函數接口:

#include <mqueue.h>
mqd_t mq_open(const char *name, int oflag, /* mode_t mode, struct mq_attr *attr */);
                       //成功返回消息隊列描述符,失敗返回-1
mqd_t mq_close(mqd_t mqdes);
mqd_t mq_unlink(const char *name);
                           //成功返回0,失敗返回-1

mq_open 用於打開或創建一個消息隊列。 

name :表示消息隊列的名字,它符合 POSIX IPC 的名字規則。

oflag :表示打開的方式,和 open 函數的類似。有必須的選項: O_RDONLY  O_WRONLY  O_RDWR ,還有可選的選項: O_NONBLOCK  O_CREAT  O_EXCL

mode :是一個可選參數,在 oflag 中含有 O_CREAT 標誌且消息隊列不存在時,才需要提供該參數。表示默認訪問權限。可以參考 open 

attr :也是一個可選參數,在 oflag 中含有 O_CREAT 標誌且消息隊列不存在時才需要。該參數用於給新隊列設定某些屬性,如果是空指針,那麼就採用默認屬性。

mq_open 返回值是 mqd_t 類型的值,被稱爲消息隊列描述符。在 Linux  2.6.18中該類型的定義爲整型:

#include <bits/mqueue.h>
typedef int mqd_t;

mq_close 用於關閉一個消息隊列,和文件的 close 類型,關閉後,消息隊列並不從系統中刪除。一個進程結束,會自動調用關閉打開着的消息隊列。

mq_unlink 用於刪除一個消息隊列。消息隊列創建後只有通過調用該函數或者是內核自舉才能進行刪除。每個消息隊列都有一個保存當前打開着描述符數的引用計數器,和文件一樣,因此本函數能夠實現類似於 unlink 函數刪除一個文件的機制。

POSIX 消息隊列的名字 所創建的真正路徑名和具體的系統實現有關 ,關於具體 POSIX IPC 的名字規則可以參考《 UNIX  網絡編程 卷 2 :進程間通信》的 P14 

經過測試,在 Linux  2.6.18中,所創建的POSIX 消息隊列不會在文件系統中創建真正的路徑名。且 POSIX 的名字只能以一個 ’/’開頭,名字中不能包含其他的’/’。

2 POSIX 消息隊列的屬性

POSIX 標準規定消息隊列屬性 mq_attr 必須要含有以下四個內容:

long    mq_flags //消息隊列的標誌:0或O_NONBLOCK,用來表示是否阻塞 
long    mq_maxmsg  //消息隊列的最大消息數
long    mq_msgsize  //消息隊列中每個消息的最大字節數
long    mq_curmsgs  //消息隊列中當前的消息數目

在 Linux  2.6.18中mq_attr 結構的定義如下:

#include <bits/mqueue.h>
struct mq_attr
{
  long int mq_flags;      /* Message queue flags.  */
  long int mq_maxmsg;   /* Maximum number of messages.  */
  long int mq_msgsize;   /* Maximum message size.  */
  long int mq_curmsgs;   /* Number of messages currently queued.  */
  long int __pad[4];
};

POSIX 消息隊列的屬性設置和獲取可以通過下面兩個函數實現:

#include <mqueue.h>
mqd_t mq_getattr(mqd_t mqdes, struct mq_attr *attr);
mqd_t mq_setattr(mqd_t mqdes, struct mq_attr *newattr, struct mq_attr *oldattr);
                               //成功返回0,失敗返回-1

mq_getattr 用於獲取當前消息隊列的屬性, mq_setattr 用於設置當前消息隊列的屬性。其中 mq_setattr 中的 oldattr 用於保存修改前的消息隊列的屬性,可以爲空。

mq_setattr 可以設置的屬性只有 mq_flags ,用來設置或清除消息隊列的非阻塞標誌。 newattr 結構的其他屬性被忽略。 mq_maxmsg  mq_msgsize 屬性只能在創建消息隊列時通過 mq_open 來設置。 mq_open 只會設置該兩個屬性,忽略另外兩個屬性 。 mq_curmsgs 屬性只能被獲取而不能被設置。

下面是測試代碼:

#include <iostream>
#include <cstring>

#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <mqueue.h>

using namespace std;

int main()
{
    mqd_t mqID;
    mqID = mq_open("/anonymQueue", O_RDWR | O_CREAT, 0666, NULL);

    if (mqID < 0)
    {
        cout<<"open message queue error..."<<strerror(errno)<<endl;
        return -1;
    }

    mq_attr mqAttr;
    if (mq_getattr(mqID, &mqAttr) < 0)
    {
        cout<<"get the message queue attribute error"<<endl;
        return -1;
    }

    cout<<"mq_flags:"<<mqAttr.mq_flags<<endl;
    cout<<"mq_maxmsg:"<<mqAttr.mq_maxmsg<<endl;
    cout<<"mq_msgsize:"<<mqAttr.mq_msgsize<<endl;
    cout<<"mq_curmsgs:"<<mqAttr.mq_curmsgs<<endl;
}

在 Linux  2.6.18中執行結果是:

mq_flags:0
mq_maxmsg:10
mq_msgsize:8192
mq_curmsgs:0

3 POSIX 消息隊列的使用

POSIX 消息隊列可以通過以下兩個函數來進行發送和接收消息:

#include <mqueue.h>
mqd_t mq_send(mqd_t mqdes, const char *msg_ptr,
                      size_t msg_len, unsigned msg_prio);
                     //成功返回0,出錯返回-1

mqd_t mq_receive(mqd_t mqdes, char *msg_ptr,
                      size_t msg_len, unsigned *msg_prio);
                     //成功返回接收到消息的字節數,出錯返回-1

#ifdef __USE_XOPEN2K
mqd_t mq_timedsend(mqd_t mqdes, const char *msg_ptr,
                      size_t msg_len, unsigned msg_prio,
                      const struct timespec *abs_timeout);

mqd_t mq_timedreceive(mqd_t mqdes, char *msg_ptr,
                      size_t msg_len, unsigned *msg_prio,
                      const struct timespec *abs_timeout);
#endif

mq_send 向消息隊列中寫入一條消息, mq_receive 從消息隊列中讀取一條消息。

mqdes :消息隊列描述符;

msg_ptr :指向消息體緩衝區的指針;

msg_len :消息體的長度,其中 mq_receive 的該參數不能小於能寫入隊列中消息的最大大小,即一定要大於等於該隊列的 mq_attr 結構中 mq_msgsize 的大小。如果 mq_receive 中的 msg_len 小於該值,就會返回 EMSGSIZE 錯誤。POXIS 消息隊列發送的消息長度可以爲 0 。

msg_prio :消息的優先級;它是一個小於 MQ_PRIO_MAX 的數,數值越大,優先級越高。 POSIX 消息隊列在調用 mq_receive 時總是返回隊列中 最高優先級的最早消息。如果消息不需要設定優先級,那麼可以在 mq_send 是置 msg_prio  0  mq_receive msg_prio 置爲 NULL 

還有兩個XSI定義的擴展接口限時發送和接收消息的函數: mq_timedsend  mq_timedreceive 函數。默認情況下 mq_send  mq_receive 是阻塞進行調用,可以通過 mq_setattr來設置爲 O_NONBLOCK  

下面是消息隊列使用的測試代碼:

#include <iostream>
#include <cstring>
#include <errno.h>

#include <unistd.h>
#include <fcntl.h>
#include <mqueue.h>

using namespace std;

int main()
{
    mqd_t mqID;
    mqID = mq_open("/anonymQueue", O_RDWR | O_CREAT | O_EXCL, 0666, NULL);

    if (mqID < 0)
    {
        if (errno == EEXIST)
        {
            mq_unlink("/anonymQueue");
            mqID = mq_open("/anonymQueue", O_RDWR | O_CREAT, 0666, NULL);
        }
        else
        {
            cout<<"open message queue error..."<<strerror(errno)<<endl;
            return -1;
        }
    }

    if (fork() == 0)
    {
        mq_attr mqAttr;
        mq_getattr(mqID, &mqAttr);

        char *buf = new char[mqAttr.mq_msgsize];

        for (int i = 1; i <= 5; ++i)
        {
            if (mq_receive(mqID, buf, mqAttr.mq_msgsize, NULL) < 0)
            {
                cout<<"receive message  failed. ";
                cout<<"error info:"<<strerror(errno)<<endl;
                continue;
            }

            cout<<"receive message "<<i<<": "<<buf<<endl;   
        }
        exit(0);
    }

    char msg[] = "yuki";
    for (int i = 1; i <= 5; ++i)
    {
        if (mq_send(mqID, msg, sizeof(msg), i) < 0)
        {
            cout<<"send message "<<i<<" failed. ";
            cout<<"error info:"<<strerror(errno)<<endl;
        }
        cout<<"send message "<<i<<" success. "<<endl;   

        sleep(1);
    }
}

在 Linux  2.6.18下的執行結構如下:

send message 1 success. 
receive message 1: yuki
send message 2 success. 
receive message 2: yuki
send message 3 success. 
receive message 3: yuki
send message 4 success. 
receive message 4: yuki
send message 5 success. 
receive message 5: yuki

4 POSIX 消息隊列的限制

POSIX 消息隊列本身的限制就是 mq_attr 中的 mq_maxmsg  mq_msgsize ,分別用於限定消息隊列中的最大消息數和每個消息的最大字節數。在前面已經說過了,這兩個參數可以在調用 mq_open 創建一個消息隊列的時候設定。當這個設定是受到系統內核限制的。

下面是在 Linux  2.6.18下 shell 對啓動進程的 POSIX 消息隊列大小的限制:

# ulimit -a |grep message
POSIX message queues     (bytes, -q) 819200

限制大小爲 800KB  該大小是整個消息隊列的大小,不僅僅是最大消息數 * 消息的最大大小;還包括消息隊列的額外開銷 。前面我們知道 Linux  2.6.18下POSIX 消息隊列默認的最大消息數和消息的最大大小分別爲:

mq_maxmsg = 10
mq_msgsize = 8192

爲了說明上面的限制大小包括消息隊列的額外開銷,下面是測試代碼:

#include <iostream>
#include <cstring>
#include <errno.h>

#include <unistd.h>
#include <fcntl.h>
#include <mqueue.h>

using namespace std;

int main(int argc, char **argv)
{
    mqd_t mqID;
    mq_attr attr;
    attr.mq_maxmsg = atoi(argv[1]);
    attr.mq_msgsize = atoi(argv[2]);

    mqID = mq_open("/anonymQueue", O_RDWR | O_CREAT | O_EXCL, 0666, &attr);

    if (mqID < 0)
    {
        if (errno == EEXIST)
        {
            mq_unlink("/anonymQueue");
            mqID = mq_open("/anonymQueue", O_RDWR | O_CREAT, 0666, &attr);

            if(mqID < 0)
            {
                cout<<"open message queue error..."<<strerror(errno)<<endl;
                return -1;
            }
        }
        else
        {
            cout<<"open message queue error..."<<strerror(errno)<<endl;
            return -1;
        }
    }

    mq_attr mqAttr;
    if (mq_getattr(mqID, &mqAttr) < 0)
    {
        cout<<"get the message queue attribute error"<<endl;
        return -1;
    }

    cout<<"mq_flags:"<<mqAttr.mq_flags<<endl;
    cout<<"mq_maxmsg:"<<mqAttr.mq_maxmsg<<endl;
    cout<<"mq_msgsize:"<<mqAttr.mq_msgsize<<endl;
    cout<<"mq_curmsgs:"<<mqAttr.mq_curmsgs<<endl; 
}

下面進行創建消息隊列時設置最大消息數和消息的最大大小進行測試:

[root@idcserver program]# g++ -g test.cpp -lrt
[root@idcserver program]# ./a.out 10 81920
open message queue error...Cannot allocate memory
[root@idcserver program]# ./a.out 10 80000
open message queue error...Cannot allocate memory
[root@idcserver program]# ./a.out 10 70000
open message queue error...Cannot allocate memory
[root@idcserver program]# ./a.out 10 60000
mq_flags:0
mq_maxmsg:10
mq_msgsize:60000
mq_curmsgs:0

從上面可以看出消息隊列真正存放消息數據的大小是沒有 819200B 的。可以通過修改該限制參數,來改變消息隊列的所能容納消息的數量。可以通過下面方式來修改限制,但這會在 shell 啓動進程結束後失效,可以將設置寫入開機啓動的腳本中執行,例如 .bashrc  rc.local 

[root@idcserver ~]# ulimit -q 1024000000
[root@idcserver ~]# ulimit -a |grep message
POSIX message queues     (bytes, -q) 1024000000

下面再次測試可以設置的消息隊列的屬性。

[root@idcserver program]# ./a.out 10 81920
mq_flags:0
mq_maxmsg:10
mq_msgsize:81920
mq_curmsgs:0
[root@idcserver program]# ./a.out 10 819200
mq_flags:0
mq_maxmsg:10
mq_msgsize:819200
mq_curmsgs:0
[root@idcserver program]# ./a.out 1000 8192  
mq_flags:0
mq_maxmsg:1000
mq_msgsize:8192
mq_curmsgs:0

POSIX 消息隊列在實現上還有另外兩個限制:

MQ_OPEN_MAX :一個進程能同時打開的消息隊列的最大數目,POSIX要求至少爲8;

MQ_PRIO_MAX :消息的最大優先級,POSIX要求至少爲32;

Aug 7, 2013 AM 08:53 @lab 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章