1. 程式人生 > >Linux作業系統--訊息佇列

Linux作業系統--訊息佇列

     1、訊息佇列的特點

   (1)訊息佇列是訊息的連結串列,具有特定的格式,存放在記憶體中並由訊息佇列識別符號標識.
    (2)訊息佇列允許一個或多個程序向它寫入與讀取訊息.
    (3)管道和命名管道都是通訊資料都是先進先出的原則。
    (4)訊息佇列可以實現訊息的隨機查詢,訊息不一定要以先進先出的次序讀取,也可以按訊息的型別讀取.比FIFO更有優勢。

   目前主要有兩種型別的訊息佇列:POSIX訊息佇列以及System V訊息佇列,System V訊息佇列目前被大量使用。System V訊息佇列是隨核心持續的,只有在核心重起或者人工刪除時,該訊息佇列才會被刪除。 

二、訊息佇列的建立

1、msgget函式 該函式用來建立和訪問一個訊息佇列。它的原型為:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t, key, int msgflg);
引數key為訊息佇列的鍵值,通常是一個長整型,可以設定為任何整數值。該引數可以使用者直接指定,也可以呼叫ftok函式來生成,如果直接設為IPC_RPIVATE,表示總是建立新的訊息佇列。

引數msgflg用來建立訊息佇列並設定存取許可權,例如IPC_CREAT|0666,它表示建立一個當前使用者,使用者組以及其他使用者有讀寫許可權的訊息佇列。如果加上IPC_EXCL,則表示只有在指定的訊息佇列不存在時,才會建立新的訊息佇列。函式執行成功後,返回訊息佇列的識別符號,否則返回-1.

ftok函式的一般形式如下:

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname,int proj_id);
引數pathname用來指定程序有存取許可權的一個路徑。

引數proj_id用來指定某個特定字元。函式執行成功後,返回一個訊息佇列的鍵值,否則返回-1.

//使用msgget函式來建立一個訊息佇列
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int main()
{
    int qid;
    key_t key;
    key=ftok("/home/", 'a');      //生成訊息佇列的鍵值
    if (key<0)
    {
        perror("ftok error");
        exit(1);
    }
    qid =msgget(key,IPC_CREAT|0666); //建立一個訊息佇列
    if (qid<0)
    {
        perror("msgget error");
        exit(1);
        
    }
    else
    {
        printf("Done!\n");
    }
    return 0;
}


通過ipcs -q 可以看到建立的訊息佇列的鍵值為 0X61020005,識別符號為65536.

三、訊息佇列的控制

1、msgctl函式 該函式用來對訊息佇列進行各種操作,例如修改訊息佇列的屬性,清除佇列中的所有訊息等。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msgid, int command, struct msgid_ds *buf);
引數msqid為訊息佇列的識別符號; 引數cmd為所要進行的操作,包括以下三種: IPC_STAT :獲取訊息佇列的狀態,返回的資訊將會儲存在buf指向的msqid_ds結構中。 IPC_SET   :設定訊息佇列的屬性,要設定的屬性儲存在buf指向的msqid_ds結構中。 IPC_RMID:  刪除訊息佇列,同時清除佇列中的所有訊息。 msqid_ds結構的定義如下:
struct msqid_ds

{
    
    struct ipc_perm msg_perm;            //存取許可權
    
    struct msg *msg_first;               //訊息佇列頭指標
    
    struct msg *msg_last;                //訊息佇列尾指標
    
    __kernel_time_t msg_stime;           //最後一次插入訊息佇列訊息的時間
    
    __kernel_time_t msg_rtime;           //最後一次接收訊息即刪除佇列中一個訊息的時間
    
    __kernel_time_t msg_ctime;           //最後一次修改的時間
    
    struct wait_queue *wwait;            //傳送訊息等待程序佇列
    
    struct wait_queue *rwait;
    
    unsigned short msg_cbytes;           //當前佇列的位元組數
    
    unsigned short msg_qnum;             //訊息佇列中的訊息個數
    
    unsigned short msg_qbytes;           //佇列的最大位元組數
    
    __kernel_ipc_pid_t msg_lspid;         //最後一次訊息傳送程序的pid
    
    __kernel_ipc_pid_t msg_lrpid;         //最後一次訊息傳送程序的pid
    
};

訊息佇列被建立後,即使不再使用,系統也不會自動清理。
//清除建立的訊息佇列
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int main()
{
    int qid;
    int status;
    printf("請輸入需要刪除的訊息佇列的識別符號:");
    scanf("%d",&qid);
    status=msgctl(qid, IPC_RMID, NULL);  //刪除指定的訊息佇列
    if(status<0)
    {
        perror("msgctl error");
        exit(1);
    }
    printf("removed!\n");
    return 0;
}

四、訊息佇列的讀寫

1、msgsnd函式

該函式用來把訊息新增到訊息佇列中。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsend(int msqid, struct msgbuf  *msgp, int msgsz, int msgflg);
引數msqid為訊息佇列的識別符號,要寫入的訊息儲存在引數msgp所指向的msgbuf結構中,訊息的 大小由引數msgsz決定,引數msgflg用來設定訊息佇列沒有足夠空間時msgsnd函式執行的動作,例如是否等待。

msgbuf結構用來包含一個訊息,其定義如下:

struct msgbuf
{
    long mtype;  //訊息的型別
    char mtext[];//訊息的內容
};
當程序呼叫msgsnd函式寫入一個訊息時,系統會首先檢查程序對該訊息佇列是否有寫許可權,接著檢視訊息的長度是否超過系統允許的範圍,以及訊息佇列的剩餘空間情況等。如果條件都符合,系統會為訊息分配訊息頭和訊息資料區,將訊息從使用者空間複製到訊息資料區後,並鏈入訊息佇列的尾部。同時,在訊息頭中填寫訊息的型別、大小以及指向訊息資料區的指標等。

如果msgsnd函式被阻塞,則在下面某個條件滿足時解除阻塞。

(1)訊息佇列中有容納要寫入訊息的空間。

(2)訊息佇列被刪除。

(3)程序被訊號中斷。

msgrcv函式用來從訊息佇列中讀取(接收)一個訊息。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv(int msqid,struct msgbuf *msgp,int msgsz,long msgtyp,int msgflg);
引數msqid為為訊息佇列的識別符號,訊息返回後將會儲存在引數msgp指向的msgbuf結構中,該結構mtext成員的長度由引數msgsz決定,引數msgtyp為請求讀取訊息的型別,有如下3種情況:

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

(2)msgtyp>0:返回訊息佇列中該型別的第一個訊息。

(3)msgtyp<0:在型別小於等於msgtyp絕對值的所有訊息中,返回型別值最小的第一個訊息。

當程序呼叫msgrcv函式讀取一個訊息時,如果返回訊息小於等於使用者請求,系統會將訊息複製到使用者空間,然後從訊息佇列中刪除該訊息,並喚醒阻塞的寫入程序;如果訊息大於使用者請求,則返回錯誤資訊。

如果msgrcv函式被阻塞,則在下面某個條件滿足時解除阻塞:

(1)訊息佇列中有了滿足條件的訊息。

(2)訊息佇列被刪除。

(3)程序被訊號中斷。

示例:

傳送訊息程式:

//實現兩個程序間的訊息傳遞,訊息傳送程式
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MSG_SIZE 128
struct msgbuf           //定義訊息結構
{
    long mtype;         //訊息型別
    char mtext[MSG_SIZE];//訊息的內容
};
int main()
{
    int qid;
    key_t key;
    int ret;
    struct msgbuf buf;    //訊息緩衝區
    key=ftok("/home", 'a');  //生成訊息佇列的鍵值
    if (key<0)
    {
        perror("ftok error");
        exit(1);
    }
    qid=msgget(key, IPC_CREAT|0666);  //建立一個訊息佇列
    if (qid<0)
    {
        perror("msgget error");
        exit(1);
    }
    while (1)
    {
        printf("input the message:");
        fgets(buf.mtext,MSG_SIZE,stdin);  //從鍵盤輸入訊息的內容
        if (strncmp(buf.mtext, "exit",4)==0)  //如果鍵盤輸入exit,退出迴圈
        {
            buf.mtype=getpid();
            ret=msgsnd(qid, &buf, MSG_SIZE, 0);
            break;
        }
        buf.mtype=getpid();                //訊息的型別,這裡設定為當前程序的識別符號
        ret=msgsnd(qid, &buf, MSG_SIZE, 0); //向訊息佇列中傳送一個訊息
        if (ret<0)
        {
            perror("msgsnd error");
            exit(1);
        }
        else
        {
            printf("send!\n");
        }
    }
    return 0;
}


接收端:

//實現兩個程序間的訊息傳遞,編寫訊息接收程式
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MSG_SIZE 128
struct msgbuf           //定義訊息結構
{
    long mtype;         //訊息型別
    char mtext[MSG_SIZE];//訊息的內容
};
int main()
{
    int qid;
    key_t key;
    int ret;
    struct msgbuf buf;
    key=ftok("/home", 'a');
    if (key<0)
    {
        printf("ftok error");
        exit(1);
    }
    qid=msgget(key,IPC_EXCL|0666);  //開啟訊息佇列
    if (qid<0)
    {
        perror("msgget error");
        exit(1);
    }
    while (1)
    {
        memset(&buf, 0, sizeof(buf));
        ret=msgrcv(qid, &buf, MSG_SIZE, 0, 0); //讀取訊息佇列中的一個的訊息
        if (ret<0)
        {
            perror("msgrcv error");
            exit(1);
        }
        else
        {
            if (strncmp(buf.mtext, "exit",4)==0)
            {
                break;
            }
            printf("received message:\n");
            printf("type=%ld,length=%ld,text:%s\n",buf.mtype,strlen(buf.mtext)-1,buf.mtext); //輸入訊息
        }
    }
    
    return  0;
    
}