1. 程式人生 > >Linux程序通訊之訊息佇列的雙向通訊

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還沒有傳送,處在阻塞狀態,不可能傳送。
  較大的圖片傳不上來,所以這個幀數較小。

這裡寫圖片描述