1. 程式人生 > >進程間通信——XSI IPC之消息隊列

進程間通信——XSI IPC之消息隊列

接收消息 執行c stat spa 頭部 列數 png 同步方法 nbsp

進程間通信XSI IPC有3種:消息隊列、共享內存、信號量。它們之間有很多相似之處,但也有各自的特殊的地方。消息隊列作為其中比較簡單的一種,它會有些什麽東西呢,來一起探討探討。。

消息隊列結構


消息隊列提供了一種從一個進程向另一個進程發送一個數據塊的方法. 每個數據塊都被認為是一個類型,接受進程接收的數據塊可以有不同的類型值。

我們可以通過發送消息來避免命名管道的同步和阻塞問題。 消息隊列與管道不同的是,消息隊列是基於消息的,而管道是基於字節流的,且消息隊列的讀取不一定是先入先出.

命名管道:

      技術分享圖片

消息隊列:

  技術分享圖片

消息隊列與命名管道有一個不足,就是每個消息最大長度是有上限的.而且還註意消息隊列的生命周期是伴隨內核的.

所以如果你沒有顯示的刪除它,那麽在關機前它一直在.這裏的消息隊列我們可以看成一個鏈表隊列,具體為什麽? 讓我們來看看消息隊列的結構.

消息隊列結構:

tp@tp: more /usr/include/linux/msg.h

技術分享圖片

此結構定義了隊列的當前狀態。這裏的_first和_last便說明隊列的鏈式結構,它們分別是消息隊列的頭、尾指針。

初此以外,我們可能還會疑問這個結構是如何表示出這個消息隊列的?舉個例子,就是我要找出消息隊列的"身份證".不妨調出 struct msqid_ds結構體的第一個條目,

也就是IPC的共有結構體,我們再看看它的結構

技術分享圖片

該結構體中第一個成員key,其實就是消息隊列的唯一標識,想找到一個消息隊列就是靠它來找,這個我們待會會說到的。

基本命令


系統能創建多少個消息隊列?

  使用cat /proc/sys/kernel/msgmni查看

每個消息隊列能裝多少個字節?

  用cat /proc/sys/kernel/msgmnb查看

隊列中每一條基類最大是多大?

   用cat /proc/sys/kernel/msgmax查看

顯示消息隊列

  ipcs -q //s:status

技術分享圖片

   ipcs (SystemV IPC系列都可以看)

  技術分享圖片

接下來手工刪除

  ipcrm -q msqid (此例對應65536)

技術分享圖片

相關函數


1.創建消息隊列

  原型:int msgget(key_t key, int msgflg )

  返回值:-1 返回失敗, 個數限制, 打開別人隊列沒權限。0 成功 id標識已打開的消息隊列

參數:

  key_t key //相當於文件名,也可以由函數ftok生產

  int msgflg //指定消息隊列創建。一般是IPC_CREAT 0644 或者0 (為0 則代表由操作系統自動選擇)     

  

2.往消息隊列中寫數據

  原型:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg)

參數:

  void* msgp //想寫入消息隊列數據的地址,是指向消息緩沖區的指針,此位置用來暫時存儲發送接收消息,是一個用戶可定義的通用結構,形態如下:

struct msgbuf
{

  long mtype;       //k接收類型,可看作消息隊列通道號channel   ,同時必須> 0

  char mtext[100]   //存放內容
};

  size_t msgsz //消息大小, 不算上通道號大小

  int msgflg //一般輸入0,由操作系統自行選擇

3.從消息隊列中讀數據

 原型:ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg)

參數:

  void *msgp //接收的消息放在那裏

  size_t msgsz //接收消息的地方大小,不算上通道號

  long msgtyp   //從消息隊列內讀取的消息形態(選擇何種通道)。值為0,表示消息隊列中的所有消息都讀取

  int msgflg //0

註意msgflg這個參數,它用來核心程序在隊列沒有數據的情況的下所采取的行動.

如果msgflg和常數IPC_NOWAIT合用,在msgsnd()執行時若是消息隊列已滿,則msgsnd()不會阻塞,而會立即返回-1,

如果執行的是msgrcv(),則在消息隊列呈空時,不做等待馬上返回-1,並設定錯誤碼ENOMSG。

當msgflg為0時,msgsnd()及msgrcv()在隊列呈滿或呈空的情形時,采取阻塞等待的處理方式.

4.設置消息隊列屬性

  原型:int msgctl(int msqid,int cmd, struct msqid_ds *buf)

參數:

int cmd

msgctl系統調用函數對msgqid標識的消息隊列執行cmd設置。操作系統定義了常見的3種cmd操作:

  IPC_STAT:該命令用來獲取消息隊列對應的msqid_ds數據結構,並將其保存到buf指定的地址空間.

  IPC_SET:該命令用來設置消息隊列的屬性,要設置的屬性存儲在buf中.

  IPC_RMID:從內核中刪除msqid標識的消息隊列.函數調用刪除消息隊列

  

struct msqid_ds *buf); //cmd不是IPC_STAT 可設置為0,自動選擇

消息隊列應用


小練習:

創建客戶端(client)和服務端(server) ,使得不同的client端可以通過消息隊列向server端發送請求,server端分別對client做出回應。

思路:大致如圖 (channel 相當於消息類型)

技術分享圖片

實現代碼:

  server.c

技術分享圖片
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<sys/ipc.h>
  4 #include<sys/msg.h>
  5 #include<string.h>
  6 #include<unistd.h>
  7 struct msgbuf{
  8     long channel ;//通道號
  9     char mtext[100];
 10 };
 11 
 12 
 13 int main(void)
 14 {
 15     int id = msgget(12345, IPC_CREAT|0600); //創建消息隊列
 16     if(id == -1) perror("msgget"),exit(1);
 17 
 18     while(1)
 19     {
 20         struct msgbuf mb;
 21         memset(&mb, 0x00, sizeof(mb));
 22         if(msgrcv(id ,&mb, 100,1, 0) == -1) //從消息隊列接收client端送來數據
 23             perror("msgrcv"),exit(1);
 24 
 25         printf("Get from Client:%s\n", mb.mtext+ sizeof(long));
 26 //      fflush(stdout);
 27         mb.channel = *(long*)(mb.mtext); //將頭4個字節強轉賦值給隊列通道號
 28         //發送回消息隊列
 29         msgsnd(id, &mb, strlen(mb.mtext+sizeof(long)) + sizeof(long), 0);
 30     }
 31 
 32 }
 33 
server.c

  client.c

技術分享圖片
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<sys/ipc.h>
  4 #include<sys/msg.h>
  5 #include<string.h>
  6 #include<unistd.h>
  7 
  8 struct msgbuf
  9 {
 10     long channel;  //消息隊列通道號
 11     char mtext[100];
 12 };
 13 
 14 int main(void)
 15 {
 16     int id = msgget(12345, 0);
 17     if(id == -1) perror("msgget"),exit(1);
 18     while(1)
 19     {
 20         struct msgbuf mb,rcv;
 21         memset(&mb, 0x00, sizeof(mb));
 22         //從stdin讀入數據到mb.mtext後移sizeof(long)字節出
 23         while(fgets(mb.mtext+sizeof(long), 100- sizeof(long), stdin) != NULL)
 24         {
 25             *(long*)mb.mtext = getpid(); //頭部賦值為pid
 26             mb.channel = 1;  //選用通道號 1
 27 
 28             //發送到消息隊列. 註意對發送大小的處理
 29             msgsnd(id, &mb, strlen(mb.mtext+ sizeof(long))+sizeof(long), 0);
 30             printf("send ok!\n");
 31 
 32             //從消息隊列獲取
 33             memset(&rcv, 0x00, sizeof(rcv));  //清空
 34             if(msgrcv(id, &rcv, 100, (long)getpid(), 0) ==-1)
 35                 perror("msgrcv"),exit(1);
 36             printf("Message back: %s\n", rcv.mtext + sizeof(long));
 37         }
 38     }
 39     return 0;
 40 }
client.c

相關makefile

技術分享圖片

大致使用這些函數便簡單地實現了雙向通信,效果就像這樣

技術分享圖片

最後總結,消息隊列相對命名管道優勢。

消息隊列與命名管道相比,消息隊列的優勢在於:


  1.消息隊列也可以獨立於發送和接收進程而存在,從而消除了在同步命名管道的打開和關閉時可能產生的困難。

  2.基於發送消息機制,避免了像命名管道存在的同步、阻塞問題,不需要由進程自己來提供同步方法。

  3.接收程序可以通過消息類型有選擇地接收數據,而不是像命名管道中那樣默認地接收數據。
  4.實現了雙向通信

進程間通信——XSI IPC之消息隊列