程式間通訊方式有哪些?

河北小峰發表於2020-11-01

前言

程式能夠單獨執行並且完成一些任務,但是也經常免不了和其他程式傳輸資料或互相通知訊息,即需要進行通訊,本文將簡單介紹一些程式之間相互通訊的技術--程式間通訊(InterProcess Communication,IPC)。由於篇幅有限,本文不會對每一種進行詳細介紹。

概覽

程式間通訊常見方式如下:

  • 管道

  • FIFO

  • 訊息佇列

  • 訊號量

  • 共享記憶體

  • UNXI域套接字

  • 套接字(Socket)

管道

管道是一種古老的IPC通訊形式。它有兩個特點:

  • 半雙工,即不能同時在兩個方向上傳輸資料。有的系統可能支援全雙工。

  • 只能在父子程式間。經典的形式就是管道由父程式建立,程式fork子程式之後,就可以在父子程式之間使用了。

使用popen函式和pclose函式結合來執行系統命令,就用到了管道,它們宣告如下:

FILE *popen(const char *command,const char *type);
int pclose(FILE *stream);

system()函式雖然也能夠執行系統命令,但是無法獲取執行狀態碼,而執行系統命令本質上就需要建立子程式來完成,因此利用管道可以很方便的獲取子程式的輸出內容。本文不詳細展開。

我們看一個簡單的使用管道的例子,這裡使用了pipe函式來建立管道:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#define MAX_LEN 128
int main(void)
{
    /*0為讀,1為寫*/
    int fd[2] = {0}; //描述符
    pid_t pid = 0;
    char line[MAX_LEN] = {0};
    int n = 0;

    /*建立管道,需要傳入兩個檔案描述符*/
    if(pipe(fd) < 0)
    {
        perror("create pipe failed\n");
        return -1;
    }
    /*fork子程式*/
    if((pid = fork()) < 0)
    {
        perror("fork failed\n");
        return -1;
    }
    /*父程式*/
    else if(pid > 0)
    {
        /*關閉管道的寫描述符*/
        close(fd[1]);

        /*從管道讀取資料*/
        n = read(fd[0],line,MAX_LEN);
        printf("read %d bytes from pipe :%s\n",n,line);

    }
    /*子程式*/
    else
    {
        /*關閉管道的讀描述符*/
        close(fd[0]);
        /*向管道寫入資料*/
        write(fd[1],"www.yanbinghu.com",sizeof("www.yanbinghu.com"));
    }
    return 0;
}

在程式中,我們建立了一個管道,父程式關閉了寫通道,子程式關閉讀通道;子程式向管道內寫入字串,而父程式從管道中讀取字串並輸出。

執行結果:

read 18 bytes from pipe :www.yanbinghu.com

FIFO

FIFO也被稱為命名管道,與管道不同的是,不相關的程式也能夠進行資料交換。

涉及FIFO操作主要函式為:

int mkfifo(const char *path, mode_t mode);

而FIFO也常常有以下兩個用途:

  • 無需建立中間臨時檔案,複製輸出流

  • 多客戶-服務程式應用中,通過FIFO作為匯聚點,傳輸客戶程式和服務程式之間的資料

我們看一個簡單的例子,寫程式程式碼如下:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#define FIFO "/tmp/fifo"
#define MAX_LEN 128
int main(void)
{
    int writeFd;
    char line[MAX_LEN] = {0};
    if(mkfifo(FIFO,S_IRUSR|S_IWUSR) < 0 && (errno != EEXIST))
    {
         perror("make fifo failed:");
         return -1;
    }
    /*關閉管道的讀描述符*/
    writeFd = open(FIFO,O_WRONLY,0);
    /*向管道寫入資料*/
    write(writeFd,"www.yanbinghu.com",sizeof("www.yanbinghu.com"));
    close(writeFd);
    return 0;
}

它首先建立了一個FIFO,並且開啟後,往裡面寫入字串,然後關閉退出。

讀程式程式碼如下:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include<fcntl.h>
#define FIFO "/tmp/fifo"
#define MAX_LEN 128
int main(void)
{
    int readFd,n;
    char line[MAX_LEN] = {0};
    /*開啟FIFO,這裡開啟可能失敗,應該要對返回值處理*/
    readFd = open(FIFO,O_RDONLY,0);
    /*從FIFO讀取資料*/

    n = read(readFd,line,MAX_LEN);
    printf("read %d bytes from pipe :%s\n",n,line);
    close(readFd);
    /*刪除FIFO*/
    unlink(FIFO);
    return 0;
}

它先開啟一個已知的FIFO,然後從FIFO中讀取資料。

在一個終端先執行寫程式,然後執行讀程式,結果如下:

read 18 bytes from pipe :www.yanbinghu.com

我們可以看到,兩個沒有親緣關係的程式可以通過FIFO進行通訊。

訊息佇列

訊息佇列可以認為是一個訊息連結串列,儲存在核心中,程式可以從中讀寫資料。與管道和FIFO不同,程式可以在沒有另外一個程式等待讀的情況下進行寫。另外一方面,管道和FIFO一旦相關程式都關閉並退出後,裡面的資料也就沒有了,但是對於訊息佇列,一個程式往訊息佇列中寫入資料後退出,另外一個程式仍然可以開啟並讀取訊息。訊息佇列與後面介紹的UNIX域套接字相比,在速度上沒有多少優勢。

訊號量

訊號量是一個計數器,它主要用在多個程式需要對共享資料進行訪問的時候。考慮這一的情況,不能同時有兩個程式對同一資料進行訪問,那麼藉助訊號量就可以完成這樣的事情。

它的主要流程如下:

  • 檢查控制該資源的訊號量

  • 如果訊號量值大於0,則資源可用,並且將其減1,表示當前已被使用

  • 如果訊號量值為0,則程式休眠直至訊號量值大於0

也就是說,它實際上是提供了一個不同程式或者程式的不同執行緒之間訪問同步的手段

共享記憶體

共享記憶體允許多個程式共享一個給定的儲存區,由於它們是共享一塊記憶體資料,因此其速度非常快。但是需要另外提供手段來保證共享記憶體的同步訪問,例如它可以用到前面所提到的訊號量來實現訪問同步。

UNIX域套接字

UNIX域套接字和套接字很相似,但是它有更高的效率,因為它不需要執行協議處理,例如計算校驗和,傳送確認報文等等,它僅僅複製資料。

當然,它也只適用於同一臺計算機上的程式間通訊。

例如redis服務配置unixsocket啟動後,通過redis-cli的-s引數就可以指定UNIX域套接字,連線到redis伺服器。

$ redis-cli -s /tmp/redis.sock
redis /tmp/redis.sock> 

它會比使用網路套接字的速度要快。

網路套接字

這個不用多說,它利用網路進行通訊,與前面所提到的通訊方式不同的是,它能用於不同計算機之間的不同程式間通訊

總結

本文簡單介紹了程式間通訊的常見方式,其中對管道和命名管道我們使用了一個例子來簡單說明,因為我們可能會經常見到它。對於FIFO,最後一個引用它的程式終止時,留在FIFO的資料也將會被刪除,而對於訊息佇列卻不是這樣,它會一直留到被顯示刪除或者系統自舉,另外訊息佇列於其他方式相比並沒有特別的優勢。而訊號量實際上常用於共享資料的同步訪問。共享記憶體在程式間傳遞資料非常高效,但是系統沒有對訪問進行同步,因此還需要另外實現資料的訪問同步。套接字(socket)是應該目前應用最廣泛的程式間通訊方式。

參考:

  • 《Unix環境高階程式設計》

  • 《unix網路程式設計卷2:程式間通訊》

  • 《深入Linux核心架構》

相關文章