天天看點

Linux 程序間通信基礎(六)--消息隊列

近正好有一些空餘時間,在這裡總結一下曾經使用過的Linux程序間通信的幾種方法,貼出來幫助有需要的人,也有助于自己總結經驗加深了解。上一次我們梳理了共享記憶體的相關知識,這一次梳理消息隊列。

(一)概念

在介紹消息隊列之前,我們先來簡單說一說System V IPC通信機制。

System V IPC機制最初是由AT&T System V.2版本的UNIX引入的。這些機制是專門用于IPC(Inter-Process Communication 程序間通信)的,它們在同一個版本中被應用,又有着相似的程式設計接口,是以它們通常被稱為System V IPC通信機制。

消息隊列是最後一個System V IPC機制。消息隊列的本質也是在不同的線程之間共享的一段記憶體。隻是它與共享記憶體不同,程式不能夠直接通過API讀取或者寫入這段共享記憶體。而它有着自己的特殊機制,就像一段隊列,發送者可以向消息隊列中加入一段消息,而負責接收的程式責可以從隊列中讀取一則消息。先被發送的消息将先被讀取,也不用程式開發者去考慮同步相關的事情。系統會幫你確定它。

Linux 程式間通信基礎(六)--消息隊列

(二)消息隊列的建立,發送,接收和銷毀

同樣于共享記憶體,System V提供了一套極其類似的API供開發者們操作消息隊列。雖然API非常類似,但是它内部的機制則差異很大,這使得消息隊列和共享記憶體的應用場景也有很大不同。我們先來介紹一下操作消息隊列的主要API。

我們可以使用msgget()函數來建立,或者是得到一個消息隊列:

int msgget( key_t key, int msgflg );
           

其中,

--key:是消息的列的名字,也是系統内消息隊列的唯一辨別。通路同一個消息隊列的不同程序需要使用同一個名字。

--msgflg:消息隊列的标志,包含9個比特标志位,其内容與建立檔案時的mode相同。有一個特殊的标志IPC_CREAT可以和權限标志以或的形式傳入。

成功是傳回一個整數辨別消息隊列的句柄,失敗是傳回-1。

我們可以使用msgsnd()函數來向一個消息隊列發送資訊:

int msgsnd( int msgid, const void* msg_ptr, size_t msg_sz, int msgflt );
           

其中,

--msgid:是線程内消息隊列的辨別,是msgget()的傳回值。

--msg_ptr:是消息結構的指針,它可以是使用者自定義的結構體,不過它必須以長整數開頭(即結構提的第一個變量必須是長整數),這個長整數表示消息的類型,這個長整數很重要,因為消息接收函數會根據它對消息進行篩選。

--msg_zs:是消息體的長度,注意它是不包括消息類型這個長整數的,是除其意外的大小。

--msgflt:控制消息隊列的标志,如果在标志中設定了IPC_NOWAIT,在出現諸如消息隊列滿了等情況時,函數會立刻傳回-1,如果不設定的話函數會挂起一段時間。

我們使用msgrcv()函數來從消息隊列中讀取一個消息:

int msgrcv( int msgid, void* msg_ptr, size_t msg_sz, long int msgtype, int msgflg );
           

其中,

--msgid:是線程内消息隊列的辨別,是msgget()的傳回值。

--msg_ptr:是消息結構的指針,它與msgsnd中的是一樣的。

--msg_zs:是消息體的長度,注意它是不包括消息類型這個長整數的,是除其意外的大小。

--msgtype:消息類型,就是消息開頭的那一段長整數,設定為0則會從消息隊列中讀取第一條消息,設定為>0的值會讀取該類型的第一條消息。如果設定為<0的值,将會讀取小于該值絕對值的所有類型的第一條消息。

--msgflt:控制消息隊列的标志,如果在标志中設定了IPC_NOWAIT,在出現諸如消息隊列滿了等情況時,函數會立刻傳回-1,如果不設定的話函數會挂起一段時間。

我們使用msgctg()函數來控制消息隊列,這一點與共享記憶體非常相似:

int msgctl( int msgid, int command, struct msqid_ds* buf );
           

其中,

--msg_id:是消息隊列的标示符,也即是msgget()的傳回值。

--command:是要采取的動作,它有三個有效值,如下所示:

說明
IPC_STAT 把buf結構中的值設定為共享記憶體的關聯值
IPC_SET                                 如果擁有足夠的權限,将共享記憶體的值設定為buf中的值
IPC_RMID 删除共享記憶體段

--buf:是一個msgid_ds結構的指針它可以設定消息隊列的關聯值。msgid_ds的結構如下所示:

struct msgid_ds {
    uid_t msg_perm.uid;
    uid_t msg_perm.gid;
    mode_t msg_perm.mode;
}
           

(三)使用消息隊列通信的例子

現在我們就來嘗試寫兩個簡單例子來使用以下消息隊列,我們首先要寫一個頭檔案,定義我們的消息格式。因為這是個非常簡單的例子,是以我們的消息結構裡隻用一個長整型的消息類型(必須),兩外再加上一段字元串,它表示具體消息内容。我們把它儲存為msgdef.h:

#ifndef _MSGDEF_H_
#define _MSGDEF_H_

#define MSG_FD          1111    /* message id*/
#define MSG_TEXT_SIZE   24      /* message text max size definition */

/* Structure of general message */
typedef struct message_body
{
    long int    msg_type;                   /* message type */
    char        msg_text[MSG_TEXT_SIZE];    /* message text */
}MSG_BODY;

#endif /* _MSGDEF_H_ */
           

現在我們來完成消息接收者,它将從消息隊列讀取一個消息并且列印到螢幕上,建立一個新檔案msgrecv.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>

#include "msgdef.h"

int main()
{
    int result;
    int msgfd;
    MSG_BODY message;

    /* Create a message list. */
    msgfd = msgget( MSG_FD, IPC_CREAT | 0666 );
    if( msgfd < 0 )
    {
        printf( "error:can't apply a message list.\n" );
        return 1;
    }

    do
    {
        /* Get the first message from the message list and print it's text. */
        msgrcv( msgfd, &message, MSG_TEXT_SIZE, 0, 0 );
        printf( "receive: %s\n", message.msg_text );
    }
    while( memcmp( message.msg_text, "close", 5 ) != 0 );

    /* Close the message list. */
    msgctl( msgfd, IPC_RMID, 0 );

    return 0;
}
           

然後我們來完成消息的發送者,它會讀取使用者的輸入并發送到消息隊列。我們把它命名為msgsend.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>

#include "msgdef.h"

int main()
{
    int result;
    int msgfd;
    MSG_BODY message;

    /* Create a message list. */
    msgfd = msgget( MSG_FD, IPC_CREAT | 0666 );
    if( msgfd < 0 )
    {
        printf( "error:can't apply a message list.\n" );
        return 1;
    }

    
    do
    {
        /* Send the input of users to receiver. */
        printf( "Please input something:" );
        scanf( "%s", message.msg_text );
        msgsnd( msgfd, &message, MSG_TEXT_SIZE, 0 );
    }
    while( memcmp( message.msg_text, "close", 5 ) != 0 );
    
    return 0;
}
           

現在我們來編譯一下這兩個程式:

[email protected]:/home/root/workspace/msgq/system-v# gcc msgrecv.c -o msgrecv
[email protected]:/home/root/workspace/msgq/system-v# gcc msgsend.c -o msgsend
           

嘗試啟動接收者:

[email protected]:/home/root/workspace/msgq/system-v# ./msgrecv 
           

現在我們另外啟動一個終端,嘗試啟動發送者,并輸入一下東西:

[email protected]:/home/root/workspace/msgq/linux# ./msgsend 
Please input something:abc
Please input something:hellow
Please input something:fuck
Please input something:ono
Please input something:close
[email protected]:/home/root/workspace/msgq/linux# 
           

然後我們看一下接收者那邊的情況:

[email protected]:/home/root/workspace/msgq/system-v# ./msgrecv 
receive: abc
receive: hellow
receive: fuck
receive: ono
receive: close
[email protected]:/home/root/workspace/msgq/system-v# 
           

可以看到輸入的内容被通過消息隊列成功的傳遞了。

繼續閱讀