概述緩存
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 }