IPC之—訊息佇列
什麼是訊息佇列
訊息佇列提供了一種從一個程序向另一個程序傳送一個數據塊的方法。每個資料塊都被認為是有一個型別,接收者今晨會二手的資料塊可以有不同的型別值。我們可以通過傳送訊息來避免命名管道的同步和阻塞問題。訊息佇列與管道不同的是,訊息佇列是基於訊息的,而管道是基於位元組流的,且訊息佇列的讀取不一定是先入先出。訊息佇列與命名管道有一樣的不足,就是每個訊息佇列的最大長度是有上限的,每個訊息佇列的總子節數是有上限的,系統上訊息佇列的總數也有一個上限。
如下圖所示
核心為每個IPC物件維護一個數據結構(/usr/include/liux/ipc.h)
如下圖
訊息佇列的特點
- 訊息佇列是訊息的連結串列,具有特定的格式,存放在記憶體中並由訊息佇列識別符號標識.
- 訊息佇列允許一個或多個程序向它寫入與讀取訊息.
- 管道和命名管道都是通訊資料都是先進先出的原則。
- 訊息佇列可以實現訊息的隨機查詢,訊息不一定要以先進先出的次序讀取,也可以按訊息的型別讀取.比FIFO更有優勢
- 生命週期隨核心。
訊息佇列的結構如下圖所示
可以看到第一個條目就是IPC結構體,即是共有的,後面的都是訊息佇列所私有的成員,訊息佇列是用連結串列實現的。
建立訊息佇列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflag)
建立如果成功,返回值將訊息佇列識別符號(非負整數),否則返回-1。
key 可以認為是一個埠號,也可以由ftok函式生成。
msgflag :
IPC_CREAT: 如果IPC不存在,就建立一個,否則就開啟操作。
IPC_EXCL:只有在共享記憶體不存在時,顯得共享記憶體才建立,否則就產生錯誤
IPC_CREAT和IPC_EXCL一起使用,msgget()將返回一個新建的IPC識別符號;如果該IPC資源已經存在,或者返回-1.
下面用程式碼實現建立一個訊息佇列,並用命令刪除訊息佇列
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#define PATHNAME "."//設定路徑為當前目錄
#define ID 0
int main()
{
key_t key = ftok(PATHNAME,ID);//建立key值
if(key<0)
{
perror("ftok error!");
return -1;
}
int msgid = msgget(key,IPC_CREAT|IPC_EXCL|0666);//建立訊息佇列,並把許可權改為666
if(msgid<0)
{
perror("msgget error!\n");
return -2;
}
return msgid;
}
執行結果如下,
訊息佇列的刪除
上面已經提到了,用命令刪除一個訊息佇列,下面介紹一下如何用程式碼刪除一個訊息佇列。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
//他不僅可以實現刪除訊息佇列,還是控制型函式
第一個引數就是要刪除的訊息佇列的ID,第二個引數是IPC_RMID,第三個引數不需要關心,直接設定為空就可。刪除成功返回0,失敗返回-1.
if(msgctl(msgid,IPC_RMID,NULL)<0)
{
printf("msgctl perror!\n");
return -3;
}
else
return 0;
//這幾行程式碼只需要加入上面的程式碼建立好訊息佇列後,就可以驗證。
設定訊息佇列的屬性
這個和上面所用到的刪除訊息佇列的函式是一樣的。
msg系統呼叫對msgqid標識的訊息佇列執行cmd操作,系統定義了3中cmd操作:IPC_STAT,IPC_SET,IPC_RMID
IPC_STAT:用來獲取訊息佇列對應的msgqid_ds資料結構,將其儲存在 buf 指定的地址空間。
IPC_SET :用來設定訊息佇列中的屬性,要設定的屬性存在 buf 中
IPC_RMID:從核心中刪除msgqid標識的訊息佇列。
既然訊息佇列是用來實現程序間通訊的,那它必然就會有讀和寫功能。這裡的讀和寫不同,system V是系統提供的第三方介面,和管道不一樣,它的其中之一特點是可以實現雙向通訊。
讀寫所呼叫的函式是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); //msgsnd將資料放到訊息佇列中
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); //msgrcv從佇列中取⽤訊息
msqid:訊息佇列的標識碼
msgp:指向訊息緩衝區的指標,此位置是來暫時儲存傳送和接收的訊息,是一個使用者可定義的通用結構
msgsz:訊息的大小。
msgtyp:從訊息佇列內讀取的訊息形態。如果值為零,則表示訊息佇列中的所有訊息都會被讀取。
函式如果呼叫成功則返回0,失敗則返回-1;
用訊息佇列實現程序間通訊,程式碼如下
//comm.h
#ifndef _COMM_H_
#define _COMM_H_
#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/types.h>
#include<sys/msg.h>
#include<string.h>
#define FILENAME "."//代表但當前路徑
#define ID 0x6666
#define SERVER_TYPE 1
#define CLIENT_TYPE 2
struct _msgbuf
{
long mtype;
char mtext[1024];
};
int creatMsgQueue();//訊息佇列的建立
int getMsgQueue();//獲取訊息佇列
int destoryMsgQueue(int msgid);//訊息佇列的刪除
int sendMsg(int msgid,int type,const char*msg);//傳送訊息
int recvMsg(int msgid,int type,char *out);//接收訊息
#endif
//comm.c
#include"comm.h"
static int commMsgQueue(int flags)
{
key_t _key = ftok(FILENAME,ID);//獲取key值
if(_key<0)
{
printf("ftok");
return -1;
}
int msgid = msgget(_key,flags);
if(msgid<0)
{
printf("msgget");
return -2;
}
return msgid;
}
int creatMsgQueue()
{
return commMsgQueue(IPC_CREAT | IPC_EXCL|0666);
}
int getMsgQueue()
{
return commMsgQueue(IPC_CREAT);
}
int destoryMsgQueue(int msgid)
{
if(msgctl(msgid,IPC_RMID,NULL)<0)
{
perror("msgctl");
return -1;
}
return 0;
}
int sendMsg(int msgid,int type,const char*msg)
{
struct _msgbuf _mb;
_mb.mtype = type;
strcpy(_mb.mtext,msg);
if(msgsnd(msgid,&_mb,sizeof(_mb.mtext),0)<0)
{
perror("msgsnd");
return -1;
}
return 0;
}
int recvMsg(int msgid,int type,char *out)
{
truct _msgbuf _mb;
if(msgrcv(msgid,&_mb,sizeof(_mb.mtext),type,0)<0)
{
perror("msgrcv");
return -1;
}
strcpy(out,_mb.mtext);
return 0;
}
//server.c
#include"comm.h"
int main()
{
int msgid = creatMsgQueue();//creat msg
char buf[1024];
while(1)
{
buf[0] = 0;
recvMsg(msgid,CLIENT_TYPE,buf);
printf("client say# %s\n",buf);
printf("please Enter# ");
fflush(stdout);//使上面那句話重新整理到螢幕
ssize_t s = read(0,buf,sizeof(buf)-1);
if(s>0)
{
buf[s-1] = 0;//buyonghuanhang
sendMsg(msgid,SERVER_TYPE,buf);
}
}
destoryMsgQueue(msgid);
return 0;
}
//client.c
#include"comm.h"
int main()
{
int msgid = getMsgQueue();
char buf[1024];
while(1)
{
buf[0] = 0;
printf("client Enter#");
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf)-1);
if(s>0)
{
buf[s-1] = 0;
sendMsg(msgid,CLIENT_TYPE,buf);
}
recvMsg(msgid,SERVER_TYPE,buf);
printf("server say# %s\n",buf);
}
return 0;
}
執行結果如下,實現了訊息佇列之間的通訊