消息隊列以鏈表的方式將消息存儲於內核中,調用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和對錯誤的判斷可以提取出同一類型消息中指定長度的消息。