IPC——消息隊列

概述緩存

System V IPC引入了3中通訊方式,本文主要介紹消息隊列函數

System V IPC再也不以文件的形式存在,所以沒有文件描述符這個東西,可是它有相似的「標識符」。徹底能夠認爲這個「標識符」就是文件描述符的替代者,可是它是專門給System V IPC使用的,因此咱們不能使用文件IO函數來操做「標識符」,只能使用System V IPC的特有API才能操做。ui

如何獲取這個「標識符」?spa

調用某API建立好某個「通訊結構」之後,API就會返回一個惟一的「標識符」。3d

 

消息隊列原理code

消息隊列本質blog

消息隊列的本質就是由內核建立的用於存放消息的鏈表,因爲是存放消息的,因此咱們就把這個鏈表稱爲了消息隊列。通訊的進程經過共享操做同一個消息隊列,就能實現進程間通訊。隊列

消息隊列模型進程

每一個消息由兩部分組成,分別是消息編號(消息類型)和消息正文。
1)消息編號:識別消息用
2)消息正文:真正的信息內容ip

 

消息隊列收發數據

發送消息

①進程先封裝一個消息包

這個消息包其實就是以下類型的一個結構體變量,封包時將消息編號和消息正文
寫到結構體的成員中。

struct msgbuf
{
    long mtype;             /* 放消息編號,必須> 0 */
    char mtext[msgsz]; /* 消息內容(消息正文) */
}; 

②調用相應的API發送消息

調用API時經過「消息隊列的標識符」找到對應的消息隊列,而後將消息包發送給消息隊列,消息包(存放消息的結構體變量)會被做爲一個鏈表節點插入鏈表。

接收消息

調用API接收消息時,必須傳遞兩個重要的信息

(a)消息隊列標識符
(b)你要接收消息的編號

有了這兩個信息,API就能夠找到對應的消息隊列,而後從消息隊列中取出你所要編號的消息,如此就收到了別人所發送的信息。

 

消息隊列使用步驟

①使用msgget函數建立新的消息隊列、或者獲取已存在的某個消息隊列,並返回惟一標識消息隊列的標識符(msqID),後續收發消息就是使用這個標識符來實現的。

②收發消息

發送消息:使用msgsnd函數,利用消息隊列標識符發送某編號的消息
接收消息:使用msgrcv函數,利用消息隊列標識符接收某編號的消息

③使用msgctl函數,利用消息隊列標識符刪除消息隊列

對於使用消息隊列來通訊的多個進程來講,只須要一個進程來建立消息隊列就能夠了,對於其它要參與通訊的進程來講,直接使用這個建立好的消息隊列便可。爲了保證消息隊列的建立,最好是讓每個進程都包含建立消息隊列的代碼,誰先運行就由誰建立,後運行的進程若是發現它想用的那個消息隊列已經建立好了,就直接使用,當衆多進程共享操做同一個消息隊列時,便可實現進程間的通訊。

 

API

msgget

函數原型

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>                  
int msgget(key_t key, int msgflg); 

功能

利用key值建立、或者獲取一個消息隊列。key值也可以惟一的標識消息隊列。

若是key沒有對應任何消息隊列,那就建立一個新的消息隊列

若是key已經對應了某個消息隊列,說明你要的消息隊列已經存在了,那就獲取這個消息隊列來使用

參數

key值:

用於爲消息隊列生成(計算出)惟一的消息隊列ID。

咱們能夠指定三種形式的key值:

①指定爲IPC_PRIVATE宏,指定這個宏後,每次調用msgget時都會建立一個新的消息隊列。若是你每次使用的必須是新消息隊列的話,就能夠指定這個,不過這個用的不多。由於通常來講,只要有一個消息隊列能夠用來通訊就能夠了,並不須要每次都建立一個全新的消息隊列。

②能夠本身指定一個整形數,可是容易重複指定。原本我想建立一個新的消息隊列,結果我所指定的這個整形數,以前就已經被用於建立某個消息隊列了,當個人指定重複時,msgget就不會建立新消息隊列,而是使用的是別人以前就建立好的消息隊列。不多使用這種方式

③使用ftok函數來生成key

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);

ftok經過指定路徑名和一個整形數,就能夠計算並返回一個惟一對應的key值,只要路徑名和整形數不變,所對應的key值就惟一不變的。不過因爲ftok只會使用整形數(proj_id)的低8位,所以咱們每每會指定爲一個ASCII碼值,由於ASCII碼值恰好是8位的整形數。

msgflg:

指定建立時的原始權限,好比0664

建立一個新的消息隊列時,除了原始權限,還須要指定IPC_CREAT選項。msgid = msgget(key, 0664|IPC_CREAT);

若是key值沒有對應任何消息隊列,就會建立一個新的消息隊列,此時就會用到msgflg參數,可是若是key已經對應了某個早已存在消息隊列,就直接返回這個已存在消息隊列的ID(標識符),此時不會用到msgflg參數。

返回值

成功:返回消息隊列標識符(消息隊列的ID)對於每個建立好的消息隊列來講,ID是固定的。

失敗:失敗返回-1,並設置errno。

 

msgsnd

函數原型 

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>                
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); 

功能

發送消息到消息隊列上。將消息掛到消息隊列上。

 

參數

msqid:消息隊列的標識符。

msgp:存放消息的緩存的地址,類型struct msgbuf類型。這個緩存就是一個消息包(存放消息的結構體變量)。

struct msgbuf
{
    long mtype;         /* 放消息編號,必須 > 0 */
    char mtext[msgsz];  /* 消息內容(消息正文) */
}; 

msgsz:消息正文大大小。

msgflg

0:阻塞發送消息。也就是說,若是沒有發送成功的話,該函數會一直阻塞等,直到發送成功爲止。

IPC_NOWAIT:非阻塞方式發送消息,無論發送成功與否,函數都將返回也就是說,發送不成功的的話,函數不會阻塞。

返回值

成功:返回0,失敗:返回-1,errno被設置

 

msgrcv

函數原型

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); 

功能

接收消息,從消息隊列中取出別人所放的某個編號的消息。

參數

msqid:消息隊列的標識符。

msgp:緩存地址,緩存用於存放所接收的消息。類型仍是struct msgbuf,同上

msgsz:消息正文的大小

msgtyp:你要接收消息的編號

int msgflg:

0:阻塞接收消息。也就是說若是沒有消息時,阻塞接收(msgrcv函數會休眠)。

IPC_NOWAIT:非阻塞接收消息。也就是說沒有消息時,該函數不阻塞。他會由於讀取失敗而錯誤返回

返回值

成功:返回消息正文的字節數。失敗:返回-1,errno被設置

 

msgctl

函數原型 

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>                            
int msgctl(int msqid, int cmd, struct msqid_ds *buf); 

功能

根據cmd指定的要求,去控制消息隊列。能夠有那些控制?

獲取消息隊列的屬性信息

修改消息隊列的屬性信息

刪除消息隊列

等等

咱們調用msgctl函數的最多見目的就是刪除消息隊列,事實上,刪除消息隊列只是各類消息隊列控制中的一種。

參數

msqid:消息隊列標識符

cmd:控制選項,其實cmd有不少選項,我這裏只簡單介紹三個

①IPC_STAT:將msqid消息隊列的屬性信息,讀到第三個參數所指定的緩存。

②IPC_SET:使用第三個參數中的新設置去修改消息隊列的屬性

+ 定一個struct msqid_ds buf。
+ 將新的屬性信息設置到buf中
+ cmd指定爲IPC_SET後,msgctl函數就會使用buf中的新屬性去修改消息隊列原有的屬性。

③IPC_RMID:刪除消息隊列.刪除消息隊列時,用不到第三個參數,用不到時設置爲NULL。

buf:存放屬性信息

有的時候須要給第三個參數,有時不須要,取決於cmd的設置。buf的類型爲struct msqid_ds。

結構體中的成員都是用來存放消息隊列的屬性信息的。

struct msqid_ds 
{
    struct ipc_perm  msg_perm; /* 消息隊列的讀寫權限和全部者 */
    time_t  msg_stime;             /* 最後一次向隊列發送消息的時間*/
    time_t  msg_rtime;             /* 最後一次從消息隊列接收消息的時間 */
    time_t  msg_ctime;             /* 消息隊列屬性最後一次被修改的時間 */
    unsigned  long __msg_cbytes; /* 隊列中當前全部消息總的字節數 */
    msgqnum_t  msg_qnum;     /* 隊列中當前消息的條數*/
    msglen_t msg_qbytes;        /* 隊列中容許的最大的總的字節數 */
    pid_t  msg_lspid;                /* 最後一次向隊列發送消息的進程PID */
    pid_t  msg_lrpid;                 /* 最後一次從隊列接受消息的進程PID */
};
struct ipc_perm 
{
    key_t          __key;       /* Key supplied to msgget(2):消息隊列的key值 */
    uid_t          uid;         /* UID of owner :當前這一刻正在使用消息隊列的用戶 */
    gid_t          gid;         /* GID of owner :正在使用的用戶所在用戶組 */
    uid_t          cuid;        /* UID of creator :建立消息隊列的用戶 */
    gid_t          cgid;        /* GID of creator :建立消息隊列的用戶所在用戶組*/
    unsigned short mode;        /* Permissions:讀寫權限(好比0664) */
    unsigned short __seq;       /* Sequence number :序列號,保障消息隊列ID不被當即重複使用 */
};

返回值

成功:返回0。失敗:返回-1,errno被設置

 

多進程共享消息隊列

建立進程

若是建立者使用"./file", 'a'生成一個key值,而後調用msgget建立了一個消息隊列,好比:

key = ftok("./file", 'a');
msgid = msgget(key, 0664|IPC_CREAT);

當建立者獲得msgid後,便可操做消息隊列。

其餘進程共享操做消息隊列

共享的方法很簡單,只要你能拿到別人建立好的消息隊列的ID,便可共享操做同一個消息隊列,實現進程間通訊。

獲取別人建立好的消息隊列的ID,有兩個方法:

①建立者把ID保存到某文件,共享進程讀出ID便可。這種狀況下,共享進程根本不須要調用msgget函數來返回ID。

②調用msgget獲取已在消息隊列的ID

使用ftok函數,利用與建立者相同的「路徑名」和8位整形數,生成相同的key值

調用msgget函數,利用key找到別人建立好的消息隊列,返回ID

key = ftok("./file", 'a');
msgid = msgget(key, 0664|IPC_CREAT);

拿到了消息隊列的ID後就能共享操做了。這種方法是最經常使用的方法,由於ftok所用到的「路徑名」和「8位的整形數」比較好記憶,因此,你只要記住別人生成key值時所用的「路徑名」和「8位的整形數」,你就必定能共享操做別人建立好的消息隊列。

 

 ipcs

用於報告Linux中進程間通訊設施的狀態,顯示的信息包括消息列表、共享內存和信號量的信息。

- a 或者 什麼都不跟:消息隊列、共享內存、信號量的信息都會顯示出來

[root@localhost ~]# ipcs

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x00000000 262144     root       600        524288     2          dest         
0x00000000 360449     root       600        16777216   2          dest         
0x00000000 393218     root       600        524288     2          dest         

------ Semaphore Arrays --------
key        semid      owner      perms      nsems     

[root@localhost ~]# ipcs -a

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x00000000 262144     root       600        524288     2          dest         
0x00000000 360449     root       600        16777216   2          dest         
0x00000000 393218     root       600        524288     2          dest         

------ Semaphore Arrays --------
key        semid      owner      perms      nsems

- m:只顯示共享內存的信息  

- q:只顯示消息隊列的信息

- s:只顯示信號量的信息

 

ipcrm

用來刪除一個或更多的消息隊列、信號量集或者共享內存標識。

刪除共享內存

M:按照key值刪除
ipcrm -M key

m:按照標識符刪除
ipcrm -m msgid

刪除消息隊列

Q:按照key值刪除
q:按照標識符刪除

刪除信號量

S:按照key值刪除
s:按照標識符刪除

 

消息隊列示例代碼

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <sys/types.h>
  5 #include <sys/ipc.h>
  6 #include <sys/msg.h>
  7 #include <sys/types.h>
  8 #include <sys/stat.h>
  9 #include <fcntl.h>
 10 #include <strings.h>
 11 #include <signal.h>
 12 
 13 #define MSG_FILE "./msgfile"
 14 
 15 #define MSG_SIZE 1024
 16 
 17 struct msgbuf
 18 {
 19     long mtype;         /* 放消息編號,必須 > 0 */
 20     char mtext[MSG_SIZE];  /* 消息內容(消息正文) */
 21 };                                    
 22 
 23 
 24 void print_err(char *estr)
 25 {
 26     perror(estr);
 27     exit(-1);
 28 }
 29 
 30 int creat_or_get_msgque(void)
 31 {
 32     int msgid = -1;
 33     key_t key = -1;
 34     int fd = 0;
 35 
 36     /* 建立一個消息隊列的專用文件,ftok會用到這個文件的路徑名 */
 37     fd = open(MSG_FILE, O_RDWR|O_CREAT, 0664);
 38     if(fd == -1) print_err("open fail");
 39     
 40     /* 利用存在的文件路徑名和8位整形數,計算出key */
 41     key = ftok(MSG_FILE, 'a');
 42     if(key == -1) print_err("ftok fail");
 43 
 44     /* 利用key建立、或者獲取消息隊列 */
 45     msgid = msgget(key, 0664|IPC_CREAT);
 46     if(msgid == -1) print_err("msgget fail");
 47 
 48     return msgid;
 49 }    
 50 
 51 int msgid = -1;
 52 void signal_fun(int signo)
 53 {
 54     msgctl(msgid, IPC_RMID, NULL);
 55     remove(MSG_FILE);    
 56     
 57     exit(-1);
 58 }
 59 
 60 int main(int argc, char **argv)
 61 {    
 62     int ret = -1;
 63     long recv_msgtype = 0;
 64     
 65     if(argc !=  2)
 66     {
 67         printf("./a.out recv_msgtype\n");
 68         exit(-1);
 69     }
 70     //atol字符串轉長整形
 71     recv_msgtype = atol(argv[1]);
 72     
 73     
 74     msgid = creat_or_get_msgque();
 75 
 76     ret = fork();
 77     if(ret > 0) //發送消息
 78     {
 79         signal(SIGINT, signal_fun);
 80         struct msgbuf msg_buf = {0};
 81         while(1)
 82         {
 83             bzero(&msg_buf, sizeof(msg_buf));
 84             /* 封裝消息包 */
 85             scanf("%s", msg_buf.mtext);
 86             printf("input snd_msgtype:\n");
 87             scanf("%ld", &msg_buf.mtype);
 88 
 89             /* 發送消息包 */
 90             msgsnd(msgid, &msg_buf, MSG_SIZE, 0);    
 91         }
 92     }
 93     else if(ret == 0)//接收消息
 94     {
 95         struct msgbuf msg_buf = {0};
 96         int ret = 0;
 97         while(1)
 98         {
 99             bzero(&msg_buf, sizeof(msg_buf));
100             ret = msgrcv(msgid, &msg_buf, MSG_SIZE, recv_msgtype, 0);
101             if(ret > 0) 
102             {
103                 printf("%s\n", msg_buf.mtext);
104             }    
105         }
106     }
107     
108         
109     return 0;
110 }
相關文章
相關標籤/搜索