ipc之消息隊列


  消息隊列以鏈表的方式將消息存儲於內核中,調用msgsnd,msgrcv函數往消息隊列里面投送,取出指定的消息。

  • 創建一個消息隊列

  生成一個消息隊列或者獲取已有消息隊列id

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

       int msgget(key_t key, int msgflg);

msgget函數返回key值對應的消息隊列id。

  1. key是一個用來與一個ipc對象進行對應的東西,起到在內核中標識的作用。

  2.返回的id起到的是外部也就是我們應用層的標識作用,例如所有操作消息隊列的函數,都是用msgid來唯一確定一個消息隊列。

  3.msgflg用來指定消息隊列的權限,操作屬性,高位為操作屬性,地位為操作權限,比如msgflg通常使用的高位值:

    IPC_CREAT:用來創建一個消息隊列

    IPC_EXCL:查詢由key指定的消息隊列釋放存在

    IPC_NOWAIT:之后的消息隊列操作都為非阻塞

    一個例子 msgget(key, IPC_CREAT|0666); 創建由key指定的消息隊列,操作權限為0666。

         id = msgget(key, 0666) 得到key值對應的消息隊列的id。

  當key值為 IPC_PRIVATE 時,或者key值不為它但是msgflg指定了 IPC_CREAT ,則創建一個新的消息隊列,如果這個消息隊列不存在時。指定 IPC_PRIVATE 為key值時,總是創建一個新的消息隊列,生成的消息隊列key值為0。

 

由此可見獲得消息隊列操作 ID是很關鍵的,一般有3種方法獲得id:

  1.指定key值為 IPC_PRIVATE ,創建一個新的消息隊列,讓后將id值寫入一個文件,另一個進程讀取該文件,獲得id值,這樣2個進程就可以通過這個消息隊列通信了。

  2.手動指定key值為某個值,多個進程都看看得到這個key,這樣做的問題就是可能有一個key值和指定key值一樣的消息隊列存在了,需要處理這樣的錯誤,換一個key值。

  3.使用ftok函數生成一個key,同一個key調用msgget得到的id肯定是相同的。

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

       key_t ftok(const char *pathname, int proj_id);  (proj_id is 1-255)

 

  ftok函數的實現是通過stat函數獲取pathname的st_dev,st_ino成員(部分位)和proj_id(只用低8位)進行組合生成一個key值,這樣做的話,只要pathname和proj_id確定,那么key值基本就確定。

  1.但是這個函數依然存在一種可能就是pathname不一樣,proj_id一樣,仍然得到一個一樣的key,這是因為st_dev,st_ino成員數據被截斷了,可能剛好保留的數據是相同的。

  2.還有一點就是必須確保pathname這個文件全程都不會被改動,否則A進程獲得key之后,在B進程獲取key之前,修改pathname這個文件,從而影響st_dev,st_ino,導致B得到的key和A不一樣,雖然pathname,proj_id並沒有變。

 

  • 消息隊列屬性描述

  通過 msgctl可以獲得,設置,刪除消息隊列

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

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

 

  它的使用類似io操作中的ioctl。cmd有如下值:

  IPC_STAT:獲取由msgid指定的消息隊列的描述結構體,存放於buf中。

  IPC_SET:設置消息隊列的描述結構體

  IPC_RMID:立刻刪除指定key值的消息隊列,key值存放於buf結構體中。如果刪除后仍有進程讀寫這個消息隊列,則返回EIDRM錯誤。

一般,如果消息隊列出錯了,使用IPC_RMID刪除消息隊列,釋放它在內核中占有的資源。

 

  • 消息的發送和接收

  消息隊列是在一定的空間內建立一個鏈表,每次都將最近一個發送的消息放在隊列鏈表的末尾,當取走一個消息時,它占據的相應空間就釋放出來用以給后面要加入的消息使用。

  通過函數msgsnd,msgrcv可以實現對指定消息隊列進行消息發送和接收。

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

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

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

 

  msgsnd:向msgid指定的消息隊列末尾追加一個由msgp指向的消息,消息內容大小為msgsz。通常消息通過一個結構體進行描述,一般形式如下:

           struct msgbuf {
               long mtype;       /* message type, must be > 0 */
               char mtext[1];    /* message data */
           };

 

這個結構體可分為2個部分,mtype用以標識消息類型,mtext這個部分就是消息的內容,可以是你想要的描述消息內容的任何形式如數組,結構體等等。msgsnd,msgrcv里面所指定的消息大小msgsz指的是消息結構體內容mtext部分的長度,不包括mtype!消息類型必須大於0,至於為什么,看到msgrcv函數時就可以知道。

  msgflg:通常有2種取值:

    0:默認值,忽略標識位。消息發送時,當消息隊列里面的空間超過限制值時,msgsnd將發生阻塞,直到空間騰出來可以滿足需求(就是有人讀走消息),或者msgid指定的消息隊列已被刪除,或者捕獲一個信號,否則立馬返回。

    IPC_NOWAIT:非阻塞,空間不足時不阻塞,而是返回一個錯誤值EAGAIN

  一個阻塞的msgsnd是可以被信號中斷的。中斷后返回錯誤,錯誤值EINTR。msgsnd函數一旦被信號中斷,永遠都不會重啟調用,即便安裝信號處理函數時指定了重啟標識SA_RESTART。因此對於一個阻塞的消息隊列,要注意對其錯誤的處理。

 

  msgrcv:從msgid指定的消息隊列里面取出由msgtyp指定類型的消息存放於msgp指向的空間。取出的消息數據大小由msgsz指定。成功時,返回拷貝到mtext中實際的字節數。

  msgtyp有3種情況,用以控制取出消息的方式:

    等於0:取出隊列中的第一個消息,這樣可以以先進先出的方式取消息(因為msgsnd都是把消息添加到消息隊列的最后面)。

    大於0:取出mtype於msgtyp相同的消息。

    小於0:取出消息隊列中mtype值小於等於msgtyp絕對值的所有消息中mtype值最小的那個消息。(假設mtype設定為消息的優先級,這種方式可以用於控制消息隊列取消息的優先級)

  從上面的描述可以看出這就是為什么消息結構體中mtype值為什么一定要大於0的原因

  msgflg有多種組合方式:

    0:忽略標識位,當消息隊列中無指定消息時阻塞,直到有指定類型消息,或者消息隊列被刪除,或者被信號中斷。后面2種都會返回錯誤。

    IPC_NOWAIT:非阻塞方式讀取消息。無消息時返回ENOMSG

    MSG_NOERROR:如果消息類型匹配上了,但是消息數據大小大於msgrcv指定的msgsz,則返回錯誤E2BIG。但是如果指定了MSG_NOERROR標識,則過大的消息數據按照msgsz指定的大小截斷。

    MSG_EXCEPT:如果msgtyp大於0,指定此標識表示按照先進先出的方式取出第一個非msgtyp的消息。

 

  一個消息隊列中的消息大小是可以不一樣的,即便是同一類型的消息,這樣當消息數據是變長時,避免出現空間的浪費。配合使用MSG_NOERROR和對錯誤的判斷可以提取出同一類型消息中指定長度的消息。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM