咱們已經知道進程通訊的方式是有多種的,在上一篇博客中講述了經過管道實現簡單的進程間通訊,那麼接下來咱們看看與之相似的另外一種方式,經過消息隊列來實現進程間通訊。linux
什麼是消息隊列shell
消息隊列提供了一種由一個進程向另外一個進程發送塊數據的方法。另外,每個數據塊被看做有一個類型,而接收進程能夠獨立接收具備不一樣類型的數據塊。消息隊列的好處在於咱們幾乎能夠徹底避免同步問題,而且能夠經過發送消息屏蔽有名管道的問題。更好的是,咱們可使用某些緊急方式發送消息。壞處在於,與管道相似,在每個數據塊上有一個最大尺寸限制,同時在系統中全部消息隊列上的塊尺寸上也有一個最大尺寸限制。數據結構
如何使用消息隊列ide
咱們先來了解一下System V IPC函數
System V IPC工具
System V IPC指的是AT&T在System V.2發行版中引入的三種進程間通訊工具:(1)信號量,用來管理對共享資源的訪問 (2)共享內存,用來高效地實現進程間的數據共享 (3)消息隊列,用來實現進程間數據的傳遞。咱們把這三種工具統稱爲System V IPC的對象,每一個對象都具備一個惟一的IPC標識符(identifier)。要保證不一樣的進程可以獲取同一個IPC對象,必須提供一個IPC關鍵字(IPC key),內核負責把IPC關鍵字轉換成IPC標識符。 ui
System V IPC具備類似的語法,通常操做以下:spa
(1)選擇IPC關鍵字,可使用以下三種方式:3d
a)IPC_PRIVATE。由內核負責選擇一個關鍵字而後生成一個IPC對象並把IPC標識符直接傳遞給另外一個進程。
b)直接選擇一個關鍵字。
c)使用ftok()函數生成一個關鍵字。指針
(2)使用semget()/shmget()/msgget()函數根據IPC關鍵字key和一個標誌flag建立或訪問IPC對象。若是key是IPC_PRIVATE;或者key還沒有與已經存在的IPC對象相關聯且flag中包含IPC_CREAT標誌,那麼就會建立一個全新的IPC對象。
(3)使用semctl()/shmctl()/msgctl()函數修改IPC對象的屬性。
(4)使用semctl()/shmctl()/msgctl()函數和IPC_RMID標誌銷燬IPC實例。
System V IPC爲每一個IPC對象設置了一個ipc_perm結構體並在建立IPC對象的時候進行初始化。這個結構體中定義了IPC對象的訪問權限和全部者:
struct ipc_perm{
uid_t uid; //全部者的用戶id
gid_t gid; //全部者的組id
uid_t cuid; //建立者的用戶id
gid_t cgid; //建立者的組id
mode_t mode; //訪問模式
…
};
能夠在/usr/include/linux/ipc.h目錄進行查看。
shell中管理IPC對象的命令是ipcs、ipcmk和ipcrm。
不少人看到這裏估計就看不下去了,我也是,怎麼這麼麻煩啊,這麼多相關的東西。別急,咱們再來繼續分析,咱們要使用消息隊列、信號量和共享內存就必須知道上面這些,具體怎樣實際的應用呢,咱們繼續來看。
在上面提到了IPC關鍵字,這裏咱們採用ftok函數來生成關鍵字,下面是一些在使用中會涉及的函數
》ftok函數
函數ftok把一個已存在的路徑名和一個整數標識得轉換成一個key_t值,稱爲IPC鍵:
# include <sys/types.h>
# include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
參數介紹:*pathname, 路徑名 proj_id,項目號,不爲0
》int msgget(key_t key, int msgflg); 建立消息隊列
參數介紹:
>key,IPC關鍵字
>msgflag,IPC_CREAT 若是IPC不存在,則建立一個IPC資源,不然打開操做。 IPC_EXCL:只有在共享內存不存在的時候,新的共享內存才創建,不然就產生錯誤。
若是單獨使用IPC_CREAT, XXXget()函數要麼返回一個已經存在的共享內存的操做符,要
麼返回一個新建的共享內存的標識符。
若是將IPC_CREAT和IPC_EXCL標誌一塊兒使用, XXXget()將返回一個新建的IPC標識符
;若是該IPC資源已存在,或者返回-1。 IPC_EXEL標誌原本並無太大的意義,可是和IPC_CREAT標誌一塊兒使用能夠來保證
》向隊列讀/寫消息
msgrcv從隊列中取⽤消息:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
msgsnd將數據放到消息隊列中:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
參數: msqid:消息隊列的標識碼 msgp:指向消息緩衝區的指針,此位置⽤來暫時存儲發送和接收的消息,是一個用戶可定義的通用結構,形態以下:
struct msgstru
{ long mtype; //大於於0
char mtext[用戶指定大小]; };
msgsz:消息的大小。
msgtyp:從消息隊列內讀取的消息形態。若是值爲零,則表示消息隊列中的全部消息都會被讀取。
msgflg:用來指明核心程序在隊列沒有數據的狀況下所應採起的行動。若是msgflg和常數IPC_NOWAIT合用,則在msgsnd()執行時如果消息隊列已滿,則msgsnd()將不會阻塞,而會當即返回-1,若是執行的是msgrcv(),則在消息隊列呈空時,不作等待立刻返回-1,並設定錯誤碼爲ENOMSG。當msgflg爲0時, msgsnd()及msgrcv()在隊列呈滿或呈空的情形時,採起阻塞等待的處理模式。
》設置消息隊列屬性
原型: int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
參數: msgctl 系統調用對 msgqid 標識的消息隊列執行 cmd 操做,系統定義了 3 種 cmd 操做: IPC_STAT , IPC_SET , IPC_RMID
IPC_STAT : 該命令用來獲取消息隊列對應的 msqid_ds 數據結構,並將其保存到 buf 指
定的地址空間。
IPC_SET : 該命令用來設置消息隊列的屬性,要設置的屬性存儲在buf中。
IPC_RMID : 從內核中刪除 msqid 標識的消息隊列
示例代碼:
comm.h:定義和函數聲明
1 #pragma once 2 #include<stdio.h> 3 #include<sys/ipc.h> 4 #include<sys/msg.h> 5 #include<stdlib.h> 6 #include<string.h> 7 #include<unistd.h> 8 #include <sys/types.h> 9 #include<time.h> 10 extern const int ser_send_type;//server 11 extern const int cli_send_type;//client 12 #define _PATH_NAME_ "/tmp" 13 #define _PROJ_ID_ 0x111 14 #define _SIZE_ 512 15 16 int create_msg_queue(); 17 int get_msg_queue(); 18 int recv_msg(int msg_id,int type,char* out); 19 int send_mag(int msg_id,int type,char* in); 20 21 typedef struct msgbuf 22 { 23 long mtype; 24 char mtext[_SIZE_]; 25 } msg_t;
comm.c:對server和client須要調用的一些函數的一些封裝
1 #include"comm.h" 2 const int ser_send_type=1;//server 3 const int cli_send_type=2;//client 4 int comm_msg_queue(int flag) 5 { 6 key_t key = ftok(_PATH_NAME_,_PROJ_ID_); 7 if(key == -1) 8 { 9 perror("ftok"); 10 return -2; 11 } 12 int msg_id=msgget(key,flag); 13 if(msg_id<0) 14 perror("msgget"); 15 return msg_id; 16 } 17 int create_msg_queue() 18 { 19 int flag=IPC_CREAT|IPC_EXCL|0664; 20 return comm_msg_queue(flag); 21 } 22 int get_msg_queue() 23 { 24 int flag=IPC_CREAT; 25 return comm_msg_queue(flag); 26 } 27 28 int recv_msg(int msg_id,int type,char* out) 29 { 30 msg_t msg; 31 msg.mtype=type; 32 size_t ret=msgrcv(msg_id,&msg,sizeof(msg.mtext),type,0); 33 if(ret<0) 34 { 35 perror("msgrcv"); 36 return 1; 37 } 38 strcpy(out,msg.mtext); 39 return 0; 40 } 41 int send_msg(int msg_id,int type,char* msg_in) 42 { 43 msg_t msg; 44 msg.mtype=type; 45 strncpy(msg.mtext,msg_in, strlen(msg_in)+1); 46 size_t ret=msgsnd(msg_id,&msg,sizeof(msg.mtext),0); 47 if(ret<0) 48 { 49 perror("msgsnd"); 50 return 2; 51 } 52 return 0; 53 } 54 int destroy_msg(int msg_id) 55 { 56 msgctl(msg_id,IPC_RMID,NULL); 57 }
server.c:
1 #include "comm.h" 2 3 int main() 4 { 5 int msg_id=create_msg_queue(); 6 char buf[_SIZE_]; 7 while(1) 8 { 9 memset(buf,0,sizeof(buf)); 10 printf("please enter >:"); 11 fgets(buf,sizeof(buf)-1,stdin); 12 if( strncmp(buf,"quit",4)==0) 13 { 14 printf("server bye!\n"); 15 break; 16 } 17 send_msg(msg_id,ser_send_type,buf); 18 memset(buf,0,sizeof(buf)); 19 recv_msg(msg_id,cli_send_type,buf); 20 printf("client:%s",buf); 21 } 22 destroy_msg(msg_id); 23 return 0; 24 }
client.c:
1 #include"comm.h" 2 int main() 3 { 4 int msg_id=get_msg_queue(); 5 if(msg_id < 0){ 6 return 1; 7 } 8 char buf[_SIZE_]; 9 while(1) 10 { 11 recv_msg(msg_id,ser_send_type,buf); 12 printf("server:%s",buf); 13 memset(buf,0,sizeof(buf)); 14 15 printf("please enter:"); 16 fgets(buf,sizeof(buf)-1,stdin); 17 if(strncmp(buf,"quit",4)==0) 18 { 19 printf("client bye!\n"); 20 break; 21 } 22 send_msg(msg_id,cli_send_type,buf); 23 } 24 destroy_msg(msg_id); 25 return 0; 26 27 }
最後再貼出makefile文件:
.PHONY: all all:server client server:server.o comm.o gcc -o $@ $^ client:client.o comm.o gcc -o $@ $^ %.o:%.c gcc -c $< .PHONY:clean clean: rm -f server client *.o
最後咱們再打開一個終端,一個運行server,一個運行client。看一下咱們高大上的結果: