Linux程序通訊之訊息佇列的雙向通訊
上一篇部落格我寫了程序間通訊基於管道的通訊,但是管道的通訊無疑有很大的缺點,最顯著的就是隻能單向通訊,例如:server向client發訊息,client無法回覆;第二個就是隻能在有血緣關係的程序間進行通訊,雖然命名管道解決了第二點,但是第一點還是一個很大的問題。開個玩笑:微信如果只能單向通訊的話,你還會用嗎?我估計發個訊息能難受死你。
所以,這就是接下來幾種程序間通訊的產生條件。訊息佇列、訊號量、共享記憶體都是基於system v 標準的程序間通訊,均可產生雙向通訊的效果,並且並不侷限於程序的關係,即:兩個毫無關係的程序,只要均可以訪問一塊核心上的公共資源,就可以進行這三種基於system v的程序間通訊。它是由作業系統IPC專門設定的一個介面,是一個程序向另一個程序傳送資料塊的方法,看過我上一篇部落格的,我應該說過:管道是基於資料流的通訊,所以這也是管道和基於system v的三種通訊方式之間的差別。
訊息佇列也相當於一個資源,每個都有自己的編號,下面這個函式就是獲得msgid的函式:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
其中msgflg表示生成訊息佇列的方式和許可權,一般使用的話,有三個常用引數,IPC_CREAT、IPC_EXCL、umask,IPC_CREAT單獨使用時代表如果沒有此訊息佇列,那就生成一個,如果當前的訊息佇列存在,那就開啟它;IPC_EXCL單獨使用沒有任何意義,至少目前沒有;二者同時使用表示如果沒有此訊息佇列,則生成一個,如果有,那就出錯並返回。而umask表示建立的當前的訊息佇列的許可權,採用8進製表示。
解釋一下,key值標識一個唯一的訊息佇列, 可以由系統指定產生,也可以由使用者自己指定一個。在key的地方傳IPCPRIVATE時表示系統指定key值。自定義生成key時,需要呼叫ftok函式。
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
pathname表示當前檔案的名稱或路徑,proj_id可以忽略直接設為0。
則執行完msgget函式後,返回一個msgid。
首先我們要知道一點,因為之前說過訊息佇列是按資料塊進行通訊的,所以每個傳送貨接收的訊息資料都在一個個的塊裡,每個塊由一個結構體表示:
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
傳送函式:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msgp表示這個結構體的地址,msgsz表示塊中資料欄位的長度,msgflg也忽略直接設為0;應注意,因為msgsnd函式中沒有傳入資料的型別和具體字串,這就要求我們在傳入結構體的時候,將結構體裡面的兩個欄位都設定好。
接收函式:
#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);
msgp、msgsz、msgflg的表示不變,msgtyp表示需要顯式的傳入型別。
在使用完畢訊息佇列後,我們要銷燬建立的訊息佇列,防止佔用系統資源。有兩種方法可以做到:
1、直接在終端鍵入命令
ipcs -q:顯式當前系統中的所有訊息佇列;
ipcrm -q [msgpid]:跟上msgid表示刪除對應msgid的訊息佇列。
2、呼叫msgctl函式,顧名思義,控制函式,傳入對應的引數就可以終止一個訊息佇列:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
cmd處傳入IPC_RMID就表示刪除對應的訊息佇列,後面的引數可以先不管,直接傳入NULL即可。
實現消佇列的程式碼:
//comm.h
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#define TEXT_SIZE 1024
#define SERVER_TYPE 1
#define CLIENT_TYPE 10
struct msgbuf
{
long mtype; /* type of message */
char mtext[TEXT_SIZE]; /* message text */
};
struct msgbuf msg;
int creatmsgid();
int getmsgid();
int msgsend(int msgid,long types,const char* buffer);
int msgreceive(int msgid,long types,char* buffer);
int destroymsg(int msgid);
//comm.c
#include "comm.h"
static key_t getkey()
{
return ftok("comm.h",0);//獲得一個key
}
int creatmsgid()
{
int msgid=msgget(getkey(),0666|IPC_CREAT|IPC_EXCL);//建立訊息佇列
if(msgid<0)
{
perror("masgget");
return -1;
}
return msgid;
}
int getmsgid()
{
return msgget(getkey(),0666|IPC_CREAT);//注意msgget的引數
}
int msgsend(int msgid,long types,const char* buffer)
{
strcpy(msg.mtext,buffer);//先將結構體的兩個欄位賦值
msg.mtype=types;
int ret=msgsnd(msgid,(void*)&msg,TEXT_SIZE,0);
if(ret<0)
{
perror("msgsnd");
return -3;
}
return 0;
}
int msgreceive(int msgid,long types,char* buffer)
{
msg.mtype=types;
ssize_t size=msgrcv(msgid,(void*)&msg,TEXT_SIZE,msg.mtype,0);
if(size=0)
{
perror("msgrcv");
return -4;
}
printf("%s\n",msg.mtext);
return size;
}
int destroymsg(int msgid)
{
int ret = msgctl(msgid,IPC_RMID,NULL);
if(ret<0)
{
perror("msgctl");
return -2;
}
printf("message destroy done...\n");
return 0;
}
//server.c
#include "comm.h"
int main()
{
int running = 1;
int msgid=creatmsgid();
char buff[1024];
int i=0;
while(running)
{
printf("server enter# ");
fflush(stdout);
ssize_t s=read(0,buff,1024);//從鍵盤接收資料
buff[s-1]=0;
msgsend(msgid,SERVER_TYPE,buff);
printf("client say:");
fflush(stdout);
msgreceive(msgid,CLIENT_TYPE,buff);
}
destroymsg(msgid);
return 0;
}
client.c
#include "comm.h"
int main()
{
int msgid = getmsgid();
int running = 1;
char buff[1024];
int i=0;
while(1)
{
printf("server say:");
fflush(stdout);
msgreceive(msgid,SERVER_TYPE,buff);
printf("client enter#:");
fflush(stdout);
ssize_t s=read(0,buff,1024);
buff[s-1]=0;
msgsend(msgid,CLIENT_TYPE,buff);
}
return 0;
}
實現結果:
因為是阻塞方式實現的訊息佇列,所以只能等待發送或接收的操作完成後,再完成另外一個。
所以圖片的最後在client say#的時候我就放棄傳送了,因為server還沒有傳送,處在阻塞狀態,不可能傳送。
較大的圖片傳不上來,所以這個幀數較小。