1. 程式人生 > >Linux 多工程式設計——程序間通訊:訊息佇列(Message Queues)

Linux 多工程式設計——程序間通訊:訊息佇列(Message Queues)

概述
訊息佇列提供了一種在兩個不相關的程序之間傳遞資料的簡單高效的方法,其特點如下:

1)訊息佇列可以實現訊息的隨機查詢。訊息不一定要以先進先出的次序讀取,程式設計時可以按訊息的型別讀取。

2)訊息佇列允許一個或多個程序向它寫入或者讀取訊息。

3)與無名管道、命名管道一樣,從訊息佇列中讀出訊息,訊息佇列中對應的資料都會被刪除。

4)每個訊息佇列都有訊息佇列識別符號,訊息佇列的識別符號在整個系統中是唯一的。

5)訊息佇列是訊息的連結串列,存放在記憶體中,由核心維護。只有核心重啟或人工刪除訊息佇列時,該訊息佇列才會被刪除。若不人工刪除訊息佇列,訊息佇列會一直存在於系統中。

訊息佇列常用操作函式如下:

#include <sys/msg.h>

#include <sys/types.h>

#include <sys/ipc.h>

 

key_t ftok(const char *pathname, int proj_id);

int msgget(key_t key, int msgflg);

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

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

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

對於訊息佇列的操作,我們可以類比為這麼一個過程:假如 A 有個東西要給 B,因為某些原因 A 不能當面直接給 B,這時候他們需要藉助第三方託管(如銀行),A 找到某個具體地址的建設銀行,然後把東西放到某個保險櫃裡(如 1 號保險櫃),對於 B 而言,要想成功取出 A 的東西,必須保證去同一地址的同一間銀行取東西,而且只有 1 號保險櫃的東西才是 A 給自己的。


對於上面的例子,涉及到幾個比較重要的資訊:地址、銀行、保險櫃號碼

只有同一個地址才能保證是同一個銀行,只有是同一個銀行雙方才能借助它來託管,只有同一個保險櫃號碼才能保證是對方託管給自己的東西。

而在訊息佇列操作中,鍵(key)值相當於地址,訊息佇列標示符相當於具體的某個銀行,訊息型別相當於保險櫃號碼。

同一個鍵(key)值可以保證是同一個訊息佇列,同一個訊息佇列標示符才能保證不同的程序可以相互通訊,同一個訊息型別才能保證某個程序取出是對方的資訊。

鍵(key)值
System V 提供的程序間通訊機制需要一個 key 值,通過 key 值就可在系統內獲得一個唯一的訊息佇列識別符號。key 值可以是人為指定的,也可以通過 ftok() 函式獲得。

需要的標頭檔案:

#include <sys/types.h>
#include <sys/ipc.h>

key_t ftok(const char *pathname, int proj_id);

功能:

獲取鍵(key)值

引數:

pathname: 路徑名

proj_id: 專案ID,非 0 整數(只有低 8 位有效)

返回值:

成功:key 值

失敗:-1

訊息佇列的建立
所需標頭檔案:

#include <sys/msg.h>


int msgget(key_t key, int msgflg);

功能:

建立一個新的或開啟一個已經存在的訊息佇列。不同的程序呼叫此函式,只要用相同的 key 值就能得到同一個訊息佇列的識別符號。

引數:

key: ftok() 返回的 key 值

msgflg: 標識函式的行為及訊息佇列的許可權,其取值如下:

IPC_CREAT:建立訊息佇列。

IPC_EXCL: 檢測訊息佇列是否存在。

位或許可權位:訊息佇列位或許可權位後可以設定訊息佇列的訪問許可權,格式和open() 函式的 mode_t 一樣(open() 的使用請點此連結),但可執行許可權未使用。

返回值:

成功:訊息佇列的識別符號

失敗:-1

示例程式碼如下:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
 
int main(int argc, char *argv[])
{
    key_t key;
    int  msgqid;
    
    key = ftok(".", 2012); // key 值
    
    // 建立訊息佇列
    msgqid = msgget(key, IPC_CREAT|0666);
    
    return 0;
}


執行結果如下:

訊息佇列的讀寫操作
對於訊息佇列的讀寫,都是以訊息型別為準。訊息型別相當於保險櫃號碼,A 往 1 號保險櫃放東西,對方想取出 A 的東西必須也是從 1 號保險櫃裡取。同理,某一程序往訊息佇列新增 a 型別的訊息,別的程序要想取出這程序新增的資訊也必須取 a 型別的訊息。

在學習訊息佇列讀寫操作前,我們先學習訊息佇列的訊息格式:


typedef struct _msg
{
    long mtype;         // 訊息型別
    char mtext[100]; // 訊息正文
    //…… ……          // 訊息的正文可以有多個成員
}MSG;

訊息型別必須是長整型的,而且必須是結構體型別的第一個成員,型別下面是訊息正文,正文可以有多個成員(正文成員可以是任意資料型別的)。至於這個結構體型別叫什麼名字,裡面成員叫什麼名字,自行定義,沒有明文規定。


新增資訊
所需標頭檔案:

#include <sys/msg.h>

int msgsnd(  int msqid, const void *msgp, size_t msgsz, int msgflg);

功能:

將新訊息新增到訊息佇列。

引數:

msqid: 訊息佇列的識別符號。

msgp:  待發送訊息結構體的地址。

msgsz: 訊息正文的位元組數。

msgflg:函式的控制屬性,其取值如下:

0:msgsnd() 呼叫阻塞直到條件滿足為止。

IPC_NOWAIT: 若訊息沒有立即傳送則呼叫該函式的程序會立即返回。

返回值:

成功:0

失敗:-1

獲取資訊
所需標頭檔案:

#include <sys/msg.h>

ssize_t msgrcv( int msqid, void *msgp,  size_t msgsz, long msgtyp, int msgflg );

功能:

從識別符號為 msqid 的訊息佇列中接收一個訊息。一旦接收訊息成功,則訊息在訊息佇列中被刪除。

引數:

msqid:訊息佇列的識別符號,代表要從哪個訊息列中獲取訊息。

msgp: 存放訊息結構體的地址。

msgsz:訊息正文的位元組數。

msgtyp:訊息的型別。可以有以下幾種型別:

msgtyp = 0:返回佇列中的第一個訊息。

msgtyp > 0:返回佇列中訊息型別為 msgtyp 的訊息(常用)。

msgtyp < 0:返回佇列中訊息型別值小於或等於 msgtyp 絕對值的訊息,如果這種訊息有若干個,則取型別值最小的訊息。

注意:在獲取某型別訊息的時候,若佇列中有多條此型別的訊息,則獲取最先新增的訊息,即先進先出原則。

msgflg:函式的控制屬性。其取值如下:
0:msgrcv() 呼叫阻塞直到接收訊息成功為止。

MSG_NOERROR: 若返回的訊息位元組數比 nbytes 位元組數多,則訊息就會截短到 nbytes 位元組,且不通知訊息傳送程序。

IPC_NOWAIT: 呼叫程序會立即返回。若沒有收到訊息則立即返回 -1。

返回值:

成功:讀取訊息的長度

失敗:-1

訊息佇列的控制
所需標頭檔案:

#include <sys/msg.h>


int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:

對訊息佇列進行各種控制,如修改訊息佇列的屬性,或刪除訊息訊息佇列。

引數:

msqid:訊息佇列的識別符號。

cmd:函式功能的控制。其取值如下:

IPC_RMID:刪除由 msqid 指示的訊息佇列,將它從系統中刪除並破壞相關資料結構。

IPC_STAT:將 msqid 相關的資料結構中各個元素的當前值存入到由 buf 指向的結構中。相對於,把訊息佇列的屬性備份到 buf 裡。

IPC_SET:將 msqid 相關的資料結構中的元素設定為由 buf 指向的結構中的對應值。相當於,訊息佇列原來的屬性值清空,再由 buf 來替換。

buf:msqid_ds 資料型別的地址,用來存放或更改訊息佇列的屬性。

返回值:

成功:0

失敗:-1

實踐示例


寫端示例程式碼如下:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
 
typedef struct _msg
{
    long mtype;
    char mtext[50];
}MSG;
 
int main(int argc, char *argv[])
{
    key_t key;
    int  msgqid;
    MSG msg;
    
    key = ftok("./", 2015); // key 值
    
    // 建立訊息佇列
    msgqid = msgget(key, IPC_CREAT|0666);
    if(msgqid == -1)
    {
        perror("msgget");
        exit(-1);
    }
    
    msg.mtype = 10;    // 訊息型別
    strcpy(msg.mtext, "hello mike"); // 正文內容
    
    /* 新增訊息
    msg_id:訊息佇列識別符號
    &msg:訊息結構體地址
    sizeof(msg)-sizeof(long):訊息正文大小
    0:習慣用0
    */
    msgsnd(msgqid, &msg, sizeof(msg)-sizeof(long), 0);
    
    return 0;
}

讀端示例程式碼如下:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
 
typedef struct _msg
{
    long mtype;
    char mtext[50];
}MSG;
 
int main(int argc, char *argv[])
{
    key_t key;
    int  msgqid;
    
    
    key = ftok("./", 2015); // key 值
    
    // 建立訊息佇列
    msgqid = msgget(key, IPC_CREAT|0666);
    if(msgqid == -1)
    {
        perror("msgget");
        exit(-1);
    }
    
    MSG msg;
    memset(&msg, 0, sizeof(msg));
    
    /* 取出型別為 10 的訊息
    msg_id:訊息佇列識別符號
    &msg:訊息結構體地址
    sizeof(msg)-sizeof(long):訊息正文大小
    (long)10:訊息的型別
    0:習慣用0
    */
    msgrcv(msgqid, &msg, sizeof(msg)-sizeof(long), (long)10, 0);
    printf("msg.mtext=%s\n", msg.mtext); 
    
    // 把訊息佇列刪除
    // IPC_RMID:刪除標誌位
    msgctl(msgqid, IPC_RMID, NULL);
    
    return 0;
}

執行結果如下:


--------------------- 
作者:Mike__Jiang 
來源:CSDN 
原文:https://blog.csdn.net/tennysonsky/article/details/46331643 
版權宣告:本文為博主原創文章,轉載請附上博文連結!