linux 進程間通訊之System V 消息隊列

1.概述

  • 用來引用消息隊列的句柄是一個由msgget()調用返回的標識符。這些標識符與文件描述是不一樣的。
  • 消息隊列進行的通訊是面向消息的,即讀者接收到寫者寫入的整條消息。也就是說,不能只讀取一條消息的一部分也不能一次性讀取多條消息。
  • 除了包含數據以外,每條消息還有一個用整數表示的類型。

2.建立或打開一個消息隊列

#include<sys/types.h>
#include<sys/msg.h>
int msgget(key_t key,int msgflg);
//return message queue identifier on success,or -1 on error
複製代碼
  • key: 參數能夠設置值爲IPC_PRIVATE,系統會建立一個全新的IPC對象,或者使用ftok()函數生成一個(接近惟一)key,key相同的狀況下,系統會返回一個已建立相同key的IPC對象。程序員

  • msgflg:指定施加於新消息隊列之上的權限和檢查一個既有隊列的權限的位掩碼(相似與文件權限)安全

    • IPC_CREAT: 若是沒有與指定的key對應的消息隊列,那麼就建立一個新隊列,不然返回已建立的隊列
    • IPC_EXCL:與IPC_CREAT一塊兒使用,若是指定的key對應的隊列已經存在,那麼調用就會失敗並返回EEXIST錯誤。

建立一個消息隊列bash

int msqid=msgget(IPC_PRIVATE,IPC_CREAT|S_IRUSR|S_IWUSR);//Read and Write by owner
if(msqid==-1){
    errExit("msgget");
}
複製代碼

3.交換消息

msgsnd()和msgrcv()系統調用執行消息隊列上的I/O。這兩個系統調用接收的第一個參數是消息隊列標識符(msqid)。第二個是參數msgp是一個由程序員定義的結構的指針,該結構用於存放被髮送或接受的消息,結構的常規形式以下:服務器

struct msg{
    long type;//message type
    void* body;//message body
    ...
}
複製代碼

消息的第一個部分必須指明瞭消息的類型,它用一個類型爲long的整數來表示,而消息的剩餘部分則是自定義的一個結構,其長度、內容、字段名和類型都均可以是任意的,也能夠多個字段。數據結構

需特別指出:消息的大小是除了type字段外的全部字段的大小socket

3.1 發送消息

從消息隊列中寫入消息須要隊列上的寫權限ide

#include<sys/types.h>
#include<sys/msg.h>
int msgsnd(int msqid,const void *msgp,size_t msgsz, int msgflg);
//return 0 on success,or -1 on error
複製代碼

使用msgsnd()發送消息必需要將消息結構中的type字段值設爲一個大於0的值並將須要傳遞的信息複製到自定義的body字段中。函數

  • msqid隊列的標識符ui

  • msgp消息的結構體指針spa

  • msgsz參數指定了body字段包含的字節數,即消息的大小。

  • msgflg是一組標記的位掩碼,用於控制msgsnd()操做

    • IPC_NOWAIT:執行一個非阻塞的發送操做:當消息隊列滿時,msgsnd()會阻塞直到隊列中有足夠的空間來存放這條消息。若是指定了這條消息,那麼msgsnd()就會當即返回EAGAIN錯誤

使用msgsnd()發送一條消息

struct mbuf{
    long mtype; //message type
    char mtext[1024]; //message body
}
struct mbuf msg;
int msqid,msgLen;

...

msgLen=strlen(msg.mtext);
if(msgsnd(msqid,&msg,msgLen,IPC_NOWAIT)==-1){
    errExit("msgsnd");
}
...
複製代碼

3.2 接收消息

從消息隊列中讀取消息須要隊列上的讀權限

#include<sys/types.h>
#include<sys/msg.h>
ssize_t msgrcv(int msqid,void *msgp,size_t maxmsgsz,long msgtyp,int msgflg);
//return number of bytes copied into body field, or -1 on error
複製代碼
  • msqid 隊列的標識符

  • maxmsgsz 參數值要大於或等於需讀取的消息的大小

  • msgp 緩衝區中消息的最大可用空間是經過maxmsgz參數來指定的。若是隊列中待刪除的消息體的大小超過了maxmsgsz字節,那麼就不會從隊列中刪除消息,而且會返回錯誤E2BIG。

  • mtype 讀取消息的順序能夠根據mtype字段來選擇

    • mshtyp==0 將會刪除隊列中的第一條消息並將其返回給調用進程。

    • msgtyoe>0 將隊列中第一條消息裏的type字段值等於msgtype的消息刪除並返回給進程。能夠用於讓各個進程選取與本身的進程ID匹配的消息,這樣就會競爭讀取同一條消息。

    • msgtype<0 將隊列變成優先隊列即最小堆形式。隊列中消息type最小而且其值小於或等於msgtype的絕對值的第一條消息刪除並返回給調用進程,若是沒有,則堵塞直到出現匹配的消息爲止。

  • msgflg 控制msgrcv()操做

    • IPC_NOWAIT 執行一個非阻塞接收。若是隊列中沒有匹配的消息將會堵塞。設置該標記則會當即返回ENOMSG錯誤。
    • MSG_EXCEPT 需msgtype>0 才起做用,使得隊列返回第一條type不等於msgtype的消息刪除並返回給調用進程(Linux特有)。
    • MSG_NOERROR 若是需讀取的消息的大小超過了可用空間將會返回E2BIG錯誤。設置該標記將會把消息的大小截短爲maxmsgsz字節,然會返回給調用者,其他的消息將丟棄。

使用msgrcv()讀取一條消息

struct mbuf{
    long mtype;
    char mtext[1024];
}
int msqid,msgLen;
struct mbuf msg;
....

msgLen=msgrcv(msqid,&msg,1024,0,IPC_NOWAIT);
if(msgLen==-1){
    errExit("msgrcv");
}
複製代碼

4.消息隊列控制操做

#include<sys/types.h>
#include<sys/msg.h>
int msgctl(int msqid,int cmd,struct msqid_ds *buf);
// return  0 on success,or -1 on error
複製代碼
  • cmd 參數制定了在隊列上執行的操做。
    • IPC_RMID 當即刪除消息隊列對象及其關聯的msqid_ds數據結構,而且隊列中的消息都會丟失,被阻塞的讀者和寫者進程會當即醒來,msgsnd()和msgrcv會失敗並返回錯誤EIDRM。這個操做會忽略傳遞進來的buf參數。
    • IPC_STAT 將消息隊列關聯的msqid_ds 數據結構的副本放到buf中。
    • IPC_SET 使用buf提供的值更新隊列關聯的msqid_ds數據結構的字段。

還有一些其餘的cmd參數,感興趣能夠網上查看下,這裏只列出常見參數。

使用msgctl刪除System V 消息隊列

...
if(msgctl(msqid,IPC_RMID,NULL)==-1){
    errExit("msgctl");
}
複製代碼

4.1 消息隊列關聯的數據結構

struct msqid_ds
{
  struct ipc_perm msg_perm;	/* structure describing operation permission */
  __time_t msg_stime;		/* time of last msgsnd command */
  __time_t msg_rtime;		/* time of last msgrcv command */
  __time_t msg_ctime;		/* time of last change */
  __syscall_ulong_t __msg_cbytes; /* current number of bytes on queue */
  msgqnum_t msg_qnum;		/* number of messages currently on queue */
  msglen_t msg_qbytes;		/* max number of bytes allowed on queue */
  __pid_t msg_lspid;		/* pid of last msgsnd() */
  __pid_t msg_lrpid;		/* pid of last msgrcv() */
  __syscall_ulong_t __glibc_reserved4;
  __syscall_ulong_t __glibc_reserved5;
};

struct ipc_perm
  {
    __key_t __key;			/* Key.  */
    __uid_t uid;			/* Owner's user ID. */ __gid_t gid; /* Owner's group ID.  */
    __uid_t cuid;			/* Creator's user ID. */ __gid_t cgid; /* Creator's group ID.  */
    unsigned short int mode;		/* Read/write permission.  */
    unsigned short int __pad1;
    unsigned short int __seq;		/* Sequence number.  */
    unsigned short int __pad2;
    __syscall_ulong_t __glibc_reserved1;
    __syscall_ulong_t __glibc_reserved2;
  };

複製代碼

修改一個System V 消息隊列的msg_qbytes 設置

...
struct msqid_ds ds;
int msqid;
...
if(msgctl(msqid,IPC_STAT,&ds)==-1){
    errExit("msgctl");
}
ds.msg_qbytes=1048576 //1MB
if(msgctl(msqid,IPC_SET,&ds)==-1){
    errExit("msgctl");
}
複製代碼

5.消息隊列的限制

  • MSGMNI 規定了系統中所能建立消息隊列的數量。
  • MSGMAX 規定了單條消息中最多可寫入的字節數(寫入消息超過該值,返回EINVAL錯誤)。
  • MSGMNB 規定了消息隊列中最多能保存的字節數,並用來初始化msqid_ds數據結構的msg_qbytes字段。若是達到了一個隊列的msg_qbytes限制,那麼msgsnd()會阻塞或在設置IPC_NOWAIT時返回EAGAIN錯誤。

還有一些其餘的限制,感興趣能夠網上查看下,這裏只列出常見限制。

Linux 特有的msgctl() IPC_INFO 操做可以獲取一個類型爲msginfo的結構,其中包含了各類消息隊列限制的值

struct msginfo buf;
msgctl(0, IPC_INFO,(struct msqid_ds *)&buf);

/* buffer for msgctl calls IPC_INFO, MSG_INFO */
struct msginfo
  {
    int msgpool;
    int msgmap;
    int msgmax;
    int msgmnb;
    int msgmni;
    int msgssz;
    int msgtql;
    unsigned short int msgseg;
  };

複製代碼

6. 使用消息隊列實現文件服務器應用程序

一個客戶端使用一個消息隊列的客戶端/服務器IPC

服務器端核心代碼

...
for(;;){
    msgLen=msgcrv(serverId,&req,REQ_MSG_SIZE,0,0);
    if(msgLen==-1){
        if(errno==EINTR)//Interrupted by SIGCHLD handler?
        continue;
        errMsg("msgrcv");
        break;
    }
    pid=fork();
    if(pid==-1){
        errMsg("fork");
        break;
    }
    if(pid==0){
        serveRequest(&req);
        _exit(EXIT_SUCCESS);
    }
}
...
複製代碼

客戶端核心代碼

...
clientId=msgget(IPC_PRIVATE,S_IRUSR|S_IWUSR|S_IWGRP);//確保服務端可以有寫權限
...
msgLen=msgrcv(clientId,&req,RESP_MSG_SIZE,0,0);
if(msgLen==-1){
    errExit("msgrcv");
}
...
for(;;){
    msgLen=msgrcv(clientId,&resp,RESP_MSG_SIZE,0,0);
    if(msgLen==-1){
        errExit("msgrcv");
    }
    ...
}
...
複製代碼

7.System V 消息隊列的缺點

  • 消息隊列是經過標識符引用的,而不是像大多數其餘UNIX I/O機制那樣使用文件描述符。這意味這在各類基於文件描述符的I/O技術(如select()、poll()以及epoll)將沒法應用於消息隊列上。此外,在程序中編寫同時處理消息隊列的輸入和基於文件描述符的I/O機制的代碼要比編寫只處理文件描述符的代碼要更加複雜。

  • 使用鍵而不是文件名來標識消息隊列會增長額外的程序設計複雜性。ftok()函數一般能產生一個惟一的鍵,但卻沒法保證,使用IPC_PRIVATE鍵能確保產生惟一的隊列標識符,但須要使這個標識符對須要用到它的其餘進程可見。

  • 消息隊列是無鏈接的,內核不會對待管道、FIFO以及socket那樣維護引用隊列的進程數,會帶來幾個問題:

    • 一個應用程序什麼時候可以安全地刪除一個消息隊列?
    • 應用程序如何確保再也不使用的隊列會被刪除呢?
  • 消息隊列的總數、消息的大小以及單個隊列的容量都是有限制的。這些限制都是可配置的,但若是一個應用程序超出了這些默認限制的範圍,那麼安裝應用程序的時候就須要完成一些額外的工做了。

相關文章
相關標籤/搜索