Linux進程間通信四 Posix 消息隊列簡介與示例

目錄

1. Posix 消息隊列簡介

2. API接口

2.1 創建或打開消息隊列

2.2 發送消息

2.3 接收消息

2.4 獲取、設置消息隊列屬性

2.5 關閉消息隊列

2.6 刪除消息隊列

2.7 註冊消息通知

3. 示例

3.1 消息生產者

3.2 消息消費者

3.3 編譯運行

4. 通過文件系統操作消息隊列

5. 參考文檔


1. Posix 消息隊列簡介

Posix 消息隊列是基於文件描述符的,因此可以使用諸如select、poll和epoll等IO多路複用方式,這應該是最大的優點,此外,它還是一個優先級隊列,優先級最高的消息會先出隊列。從我的開發經驗來看,實際項目中並不推薦使用系統提供的消息隊列,主要原因是限制太多,比如說Posix 消息隊列,系統範圍內能夠創建的消息隊列個數是優先的,此外,消息的大小和消息隊列的容量也受限,還有一點是僅支持優先級,不支持FIFO,這一點SystemV消息隊列是支持的。雖然不怎麼使用,但是還是需要學習一下它的基本用法,這樣後續需要自己設計的時候可以參考下。

Posix mq可以使用mq_open來創建或者獲取,該函數返回消息隊列描述符mqd_t,每個消息隊列都有一個名字,以斜槓'/'開頭,長度不超過NAME_MAX,且後面不能再有斜槓。不同的進程可以通過同一個名字來操作同一個消息隊列。消息通過mq_sendmq_receive來接收和發送,當不需要消息隊列的時候,使用mq_close關閉它,關閉不代表刪除,消息隊列具有系統生存期,可以使用mq_unlink來刪除它。對了,Posix mq還提供一個異步接收消息的特性,使用mq_notify函數,該接口向消息隊列註冊或註銷一個通知,希望有消息的時候能夠通知它,通知方式有兩種,一種是通過信號,一種是通過一個線程。這裏限制比較多,首先,消息隊列只能註冊一個通知,其次,通知是一次性的,通知完如果還想繼續通知需要重新註冊,還有一點是如果消息被其它進程通過mq_receive取到了,通知不會發生。我感覺這是一個非常雞肋的功能,O(∩_∩)O哈哈~

下面介紹下相關的接口和使用例子

2. API接口

2.1 創建或打開消息隊列

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <mqueue.h>


/**
* @brief 創建或獲取消息隊列
*
* @params name 消息隊列關聯的名字,斜槓開頭,長度不超過NAME_MAX(e.g. 255)
* @params oflag 標誌位,可以O_RDONLY| O_WRONLY| O_RDWR| O_CLOEXEC| O_CREAT| O_EXCL| O_NONBLOCK
* 這裏如果指定了O_CREAT標誌位,還要填寫額外兩個參數,mode和attr
*
* @params mode,參考open函數,通常填0即可
* @params attr,設置一些屬性
*           struct mq_attr {
*               long mq_flags;       /* 不用填 */
*               long mq_maxmsg;      /* Max. # 消息隊列中消息最大個數 */
*               long mq_msgsize;     /* Max. message size (bytes) 最大容量 */
*               long mq_curmsgs;     /* 不用填寫 */
*           };
* @returns 成功返回描述符,失敗返回-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);

# 需要加上編譯選項-lrt
* Link with -lrt.

2.2 發送消息

#include <mqueue.h>

/**
* @brief 發送消息,如果隊列滿了並且沒有設置NONBLOCK標誌位會阻塞
*
* @params mqdes 消息隊列描述符
* @params msg_ptr 要發送的消息buffer
* @params msg_len 消息大小
* @params msg_prio 消息優先級
*
* @returns 成功返回0,失敗返回-1
**/

int mq_send(mqd_t mqdes, const char *msg_ptr,
                     size_t msg_len, unsigned int msg_prio);

#include <time.h>
#include <mqueue.h>

/**
* @brief 同上,多了一個時間參數,用於控制在隊列滿了的情況下,等待多長時間
*
* @params abs_timeout 時間
* @returns 成功返回0,失敗返回-1
**/

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

# 編輯要加上 -lrt選項
Link with -lrt.

2.3 接收消息

#include <mqueue.h>

/**
* @brief 接收消息,無消息時默認阻塞,可以設置不阻塞標誌位更改
*
* @params mqdes 消息隊列描述符
* @params msg_ptr 消息緩存buffer
* @params msg_len 消息緩存大小,必須大於mq_msgsize,這個值可以通過mq_getattr接口獲得
* @params msg_prio 如果設置了則返回消息的優先級

* @returns 成功返回消息大小,失敗返回-1
**/

ssize_t mq_receive(mqd_t mqdes, char *msg_ptr,
                  size_t msg_len, unsigned int *msg_prio);

#include <time.h>
#include <mqueue.h>

/**
* @brief 功能同上,只不過多了一個阻塞時間
**/

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

2.4 獲取、設置消息隊列屬性

#include <mqueue.h>

/**
* @brief 獲取消息隊列屬性
*
* @params mqdes 消息隊列描述符
* @params attr 屬性buffer

           struct mq_attr {
               long mq_flags;       /* Flags: 0 or O_NONBLOCK */
               long mq_maxmsg;      /* Max. 最大消息個數 */
               long mq_msgsize;     /* Max. 消息隊列最大容量 */
               long mq_curmsgs;     /* 當前隊列消息數 */
           };
* @returns 成功返回0,失敗返回-1
**/

int mq_getattr(mqd_t mqdes, struct mq_attr *attr);

/**
* @brief 設置消息隊列屬性
*
* @params mqdes 消息隊列描述符
* @params attr 屬性buffer, 這裏實際上只能設置attr裏面的mq_flags標誌位
* @returns 成功返回0,失敗返回-1
**/

int mq_setattr(mqd_t mqdes, const struct mq_attr *newattr,
                        struct mq_attr *oldattr);

# 編譯鏈接選項 -lrt
Link with -lrt.

2.5 關閉消息隊列

#include <mqueue.h>

/**
* @brief 關閉消息隊列,並不實際刪除消息隊列
*
* @params mqdes 消息隊列描述符
* @returns 成功返回0,失敗返回-1
**/

int mq_close(mqd_t mqdes);

2.6 刪除消息隊列

#include <mqueue.h>
/**
* @brief 將消息隊列從系統中刪除,實際的銷燬要等到所有進程關閉消息隊列描述符才發生
*
* @params name 消息隊列名字
* @returns 成功返回0,失敗返回-1
**/

int mq_unlink(const char *name);

2.7 註冊消息通知

#include <mqueue.h>

/**
* @brief 註冊或註銷一個消息通知,當收到消息的時候根據指定的方式進行回調,可以是信號也可以執行一個線程。僅執行一次,需要反覆通知則每次都要重新註冊,最多允許一個進程註冊。只有消息隊列從空變爲非空並且沒有其它進程等待消息的時候,才觸發通知。
*
* @params mqdes 消息隊列名字
* @params sevp, 爲空表示註銷,非空指定回調方式,或是信號或者是線程
* @returns 成功返回0,失敗返回-1
**/

int mq_notify(mqd_t mqdes, const struct sigevent *sevp);

3. 示例

3.1 消息生產者

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <mqueue.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#define MQ_NAME "/mq_0"

struct smessage{
    char name[128];
    char content[1024];
};

int main(int argc, char** argv) 
{
    int ret = 0;
    
    if (argc < 4)
    {
        printf("Usage: ./mq_write name message prio\n");
        return 0;
    }

    mqd_t mq_fd;
    // 打開或創建消息隊列,如果指定C_CREAT標誌位則需要提供額外兩個參數
    mq_fd = mq_open(MQ_NAME, O_RDWR|O_CREAT, 0, NULL);
    if (mq_fd == (mqd_t)-1)
    {
        perror(__FUNCTION__);
        return -1;
    }
    struct smessage msg;
    memset(&msg, 0, sizeof(msg));
    strncpy(msg.name, argv[1], strlen(argv[1]));
    strncpy(msg.content, argv[2], strlen(argv[2]));
    unsigned int prio = (unsigned int)atoi(argv[3]);

    // 發送消息
    ret = mq_send(mq_fd, (const char *)&msg, sizeof(msg), prio);
    if (ret == -1)
    {
        perror("mq_send error");
        return ret;
    }
    printf("mq_send succ\n");

    return 0;
}

3.2 消息消費者

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <mqueue.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

// 消息隊列名字
#define MQ_NAME "/mq_0"

struct smessage{
    char name[128];
    char content[1024];
};

int main() 
{
    mqd_t mq_fd;
    // 打開或創建消息隊列,如果指定O_CREAT,則需要填充額外兩個參數
    mq_fd = mq_open(MQ_NAME, O_RDWR|O_CREAT, 0, NULL);
    if (mq_fd == (mqd_t)-1)
    {
        perror(__FUNCTION__);
        return -1;
    }

    // 獲取消息隊列屬性,讀取消息的時候要用到
    struct mq_attr attr;
    if (mq_getattr(mq_fd, &attr) == -1)
    {
        perror("mq_getattr");
        return -1;
    }
    printf("mq_msgsize:%ld\n", attr.mq_msgsize);

    // 分配消息緩存
    char *buffer = malloc(attr.mq_msgsize);
    if (buffer == NULL)
    {
        printf("malloc error\n");
        return -1;
    }
    memset(buffer, 0, attr.mq_msgsize);
            
    struct smessage *msg;
    unsigned int prio = 0;

    // 讀取消息
    if (mq_receive(mq_fd, buffer,attr.mq_msgsize, &prio) != -1)
    {
        msg = (struct smessage*)buffer;
        printf("recv msg, name:%s, content:%s, prio:%lu\n", msg->name, msg->content, prio);
    }
    else
    {
        perror("mq_receive error");
    }

    // 釋放buffer
    free(buffer);

    // 關閉消息隊列
    mq_close(mq_fd);

    return 0;
}

3.3 編譯運行

# Makefile
default:
	gcc -o mq_read mq_read.c -lrt
	gcc -o mq_write mq_write.c -lrt
clean:
	rm -rf mq_read mq_write

生產三條優先級不同的消息,可以看到讀取的時候是按照優先級來讀取的。 

 

4. 通過文件系統操作消息隊列

 # mkdir /dev/mqueue
 # mount -t mqueue none /dev/mqueue

 

5. 參考文檔

5.1. https://www.man7.org/linux/man-pages/man7/mq_overview.7.html

================================================================================================

Linux應用程序、內核、驅動、後臺開發交流討論羣(745510310),感興趣的同學可以加羣討論、交流、資料查找等,前進的道路上,你不是一個人奧^_^。...

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