Linux進程間通訊——消息隊列

下面來講說如何用不用消息隊列來進行進程間的通訊,消息隊列與命名管道有不少類似之處。有關命名管道的更多內容能夠參閱個人另外一篇文章:Linux進程間通訊——使用命名管道緩存

 

1、什麼是消息隊列數據結構

消息隊列提供了 一種從一個進程向另外一個進程發送一個數據塊的方法。  每一個數據塊都被認爲含有一個類型,接收進程能夠獨立地接收含有不一樣類型的數據結構。咱們能夠經過發送消息來避免命名管道的同步和阻塞問題。可是消息隊列 與命名管道同樣,每一個數據塊都有一個最大長度的限制。函數

Linux用宏MSGMAX和MSGMNB來限制一條消息的最大長度和一個隊列的最大長度。ui

2、在Linux中使用消息隊列spa

Linux提供了一系列消息隊列的函數接口來讓咱們方便地使用它來實現進程間的通訊。它的用法與其餘兩個System V PIC機制,即信號量和共享內存類似。.net

一、msgget函數指針

該函數用來建立和訪問一個消息隊列。它的原型爲:code

int msgget(key_t, key, int msgflg);

 

與 其餘的IPC機制同樣,程序必須提供一個鍵來命名某個特定的消息隊列。msgflg是一個權限標誌,表示消息隊列的訪問權限,它與文件的訪問權限同樣。 msgflg能夠與IPC_CREAT作或操做,表示當key所命名的消息隊列不存在時建立一個消息隊列,若是key所命名的消息隊列存在 時,IPC_CREAT標誌會被忽略,而只返回一個標識符。blog

它返回一個以key命名的消息隊列的標識符(非零整數),失敗時返回-1.接口

二、msgsnd函數

該函數用來把消息添加到消息隊列中。它的原型爲:

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

 

msgid是由msgget函數返回的消息隊列標識符。

msg_ptr是一個指向準備發送消息的指針,可是消息的數據結構卻有必定的要求,指針msg_ptr所指向的消息結構必定要是以一個長整型成員變量開始的結構體,接收函數將用這個成員來肯定消息的類型。因此消息結構要定義成這樣:

struct my_message{  
    long int message_type;  
    /* The data you wish to transfer*/  
};

 

msg_sz是msg_ptr指向的消息的長度,注意是消息的長度,而不是整個結構體的長度,也就是說msg_sz是不包括長整型消息類型成員變量的長度。

msgflg用於控制當前消息隊列滿或隊列消息到達系統範圍的限制時將要發生的事情。參數 msgflg 的值能夠指定爲IPC_NOWAIT。這相似於文件IO的非阻塞IO標誌。若消息隊列已滿,則指定IPC_NOWAIT使得msgsnd當即出錯返回EAGAIN若是沒有指定IPC_NOWAIT,則進程阻塞直到下述狀況出現爲止:①有空間能夠容納要發送的消息 ②從系統中刪除了此隊列(返回EIDRM「標識符被刪除」)③捕捉到一個信號,並從信號處理程序返回(返回EINTR)

若是調用成功,消息數據的一分副本將被放到消息隊列中,並返回0,失敗時返回-1.

三、msgrcv函數

該函數用來從一個消息隊列獲取消息,它的原型爲

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

 

msgid, msg_ptr, msg_st的做用也函數msgsnd函數的同樣。

msgtype能夠實現一種簡單的接收優先級。若是msgtype爲0,就獲取隊列中的第一個消息。若是它的值大於零,將獲取具備相同消息類型的第一個信息。若是它小於零,就獲取類型等於或小於msgtype的絕對值的第一個消息。

msgflg用於控制當隊列中沒有相應類型的消息能夠接收時將發生的事情。

調用成功時,該函數返回放到接收緩存區中的字節數,消息被複制到由msg_ptr指向的用戶分配的緩存區中,而後刪除消息隊列中的對應消息。失敗時返回-1.

 

四、msgctl函數

該函數用來控制消息隊列,它與共享內存的shmctl函數類似,它的原型爲:

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

 

command是將要採起的動做,它能夠取3個值,

    IPC_STAT:把msgid_ds結構中的數據設置爲消息隊列的當前關聯值,即用消息隊列的當前關聯值覆蓋msgid_ds的值。

    IPC_SET:若是進程有足夠的權限,就把消息列隊的當前關聯值設置爲msgid_ds結構中給出的值

    IPC_RMID:刪除消息隊列

buf是指向msgid_ds結構的指針,它指向消息隊列模式和訪問權限的結構。msgid_ds結構至少包括如下成員:

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

 

成功時返回0,失敗時返回-1.

3、使用消息隊列進行進程間通訊

快馬加鞭,介紹 完消息隊列的定義和可以使用的接口以後,咱們來看看它是怎麼讓進程進行通訊的。因爲可讓不相關的進程進行行通訊,因此咱們在這裏將會編寫兩個程 序,msgreceive和msgsned來表示接收和發送信息。根據正常的狀況,咱們容許兩個程序均可以建立消息,但只有接收者在接收完最後一個消息之 後,它才把它刪除。

接收信息的程序源文件爲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; //注意1  
  
    //創建消息隊列  
    msgid = msgget((key_t)1234, 0666 | IPC_CREAT);  
    if(msgid == -1)  
    {  
        fprintf(stderr, "msgget failed with error: %d\n", errno);  
        exit(EXIT_FAILURE);  
    }  
    //從隊列中獲取消息,直到遇到end消息爲止  
    while(running)  
    {  
        if(msgrcv(msgid, (void*)&data, BUFSIZ, msgtype, 0) == -1)  
        {  
            fprintf(stderr, "msgrcv failed with errno: %d\n", errno);  
            exit(EXIT_FAILURE);  
        }  
        printf("You wrote: %s\n",data.text);  
        //遇到end結束  
        if(strncmp(data.text, "end", 3) == 0)  
            running = 0;  
    }  
    //刪除消息隊列  
    if(msgctl(msgid, IPC_RMID, 0) == -1)  
    {  
        fprintf(stderr, "msgctl(IPC_RMID) failed\n");  
        exit(EXIT_FAILURE);  
    }  
    exit(EXIT_SUCCESS);  
}

 

發送信息的程序的源文件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;  
  
    //創建消息隊列  
    msgid = msgget((key_t)1234, 0666 | IPC_CREAT);  
    if(msgid == -1)  
    {  
        fprintf(stderr, "msgget failed with error: %d\n", errno);  
        exit(EXIT_FAILURE);  
    }  
  
    //向消息隊列中寫消息,直到寫入end  
    while(running)  
    {  
        //輸入數據  
        printf("Enter some text: ");  
        fgets(buffer, BUFSIZ, stdin);  
        data.msg_type = 1;    //注意2  
        strcpy(data.text, buffer);  
        //向隊列發送數據  
        if(msgsnd(msgid, (void*)&data, MAX_TEXT, 0) == -1)  
        {  
            fprintf(stderr, "msgsnd failed\n");  
            exit(EXIT_FAILURE);  
        }  
        //輸入end結束輸入  
        if(strncmp(buffer, "end", 3) == 0)  
            running = 0;  
        sleep(1);  
    }  
    exit(EXIT_SUCCESS);  
}

運行結果以下:

4、例子分析——消息類型

這裏主要說明一 下消息類型是怎麼一回事,注意msgreceive.c文件main函數中定義的變量msgtype(註釋爲注意1),它做爲msgrcv函數的接收信息 類型參數的值,其值爲0,表示獲取隊列中第一個可用的消息。再來看看msgsend.c文件中while循環中的語句data.msg_type = 1(註釋爲注意2),它用來設置發送的信息的信息類型,即其發送的信息的類型爲1。因此程序msgreceive可以接收到程序msgsend發送的信 息。

若是把注意1,即msgreceive.c文件main函數中的語句由long int msgtype = 0;改變爲long int msgtype = 2;會發生什麼狀況,msgreceive將不能接收到程序msgsend發送的信息。由於在調用msgrcv函數時,若是msgtype(第四個參數) 大於零,則將只獲取具備相同消息類型的第一個消息,修改後獲取的消息類型爲2,而msgsend發送的消息類型爲1,因此不能被msgreceive程序 接收。從新編譯msgreceive.c文件並再次執行,其結果以下:

咱們能夠看到,msgreceive並無接收到信息和輸出,並且當msgsend輸入end結束後,msgreceive也沒有結束,經過jobs命令咱們能夠看到它還在後臺運行着。

5、消息隊列與命名管道的比較

消息隊列跟命名 管道有很多的相同之處,經過與命名管道同樣,消息隊列進行通訊的進程能夠是不相關的進程,同時它們都是經過發送和接收的方式來傳遞數據的。在命名管道中, 發送數據用write,接收數據用read,則在消息隊列中,發送數據用msgsnd,接收數據用msgrcv。並且它們對每一個數據都有一個最大長度的限 制。

 

與命名管道相 比,消息隊列的優點在於,一、消息隊列也能夠獨立於發送和接收進程而存在,從而消除了在同步命名管道的打開和關閉時可能產生的困難。二、同時經過發送消息 還能夠避免命名管道的同步和阻塞問題,不須要由進程本身來提供同步方法。三、接收程序能夠經過消息類型有選擇地接收數據,而不是像命名管道中那樣,只能默 認地接收。

相關文章
相關標籤/搜索