天天看點

程序間通信 --管道

程序間通信

每個程序各自有不同的使用者位址空間,任何一個程序的全局變量在另一個程序中都看不到,是以程序之間要交換資料必須通過核心,在核心中開辟一塊緩沖區,程序1把資料從使用者空間拷貝到核心緩沖區,程序2再從核心緩沖區把資料讀走,核心提供的這種機制稱為程序間通信。也就是說兩個程序之間是相對獨立的,而且是互不幹擾沒有交集的,程序間通信就是讓兩個程序之間産生聯系。

  下面是實作程序間通信的圖示
           
程式間通信 --管道

實作方法一:匿名管道

管道是一種最基本的IPC機制,由pipe函數建立

#include <unistd.h>
int pipe (int fileds[]);
           

調用pipe函數在核心中開辟一塊緩沖區(稱為管道)用于通信,他有一個讀端和一個寫端,然後通過fileds參數傳出給使用者程式兩個檔案描述符,filedes0, filedes1,通過read(filedes[0])和filedes[1],那麼開辟了管道之後如何實作通信呢,接下來我們來看圖示。

程式間通信 --管道

1.父程序調用pipe開辟管道,得到兩個檔案描述符指向管道的兩端。

2.父程序調用fork建立子程序,那麼子程序也有兩個檔案描述符指向同一條管道。

3.父程序關閉管道讀端,子程序關閉管道寫寫端,父程序可以往管道裡寫,子程序可以從管道裡讀,管道使用環形隊列實作,資料從讀端流出,從寫端流入這樣就實作了程序間通信。

下邊我們用代碼示範一下:

#include<stdio.h>
 #include<unistd.h>
 #include<errno.h>
 #include<string.h>
 int main()
 {
     int _pipe[];//定義參數
     int ret = pipe(_pipe);
     if(ret==-)//建立管道失敗
     {
         printf("creat pipe error!errno code is :%d\n",errno);//錯誤碼
         return ;//傳回值,這樣你就會知道到底是哪裡出現了錯誤
     }
     pid_t id = fork();
     if(id<)//建立子程序失敗
     {
         printf("fork error!");
         return ;
     }
     else if(id==)
     {
         //child
         close(_pipe[]);//關閉讀端
         int i = ;
         char *_mesg = NULL;
         while(i<)
         {
             _mesg = "I am child!";
             write(_pipe[],_mesg,strlen(_mesg)+);//xie
             sleep();
             i++;
         }
     }
     else
     {
         //father
         close(_pipe[]);//關閉寫端
         char _mesg_c[];
         int j = ;
         while(j<)
         {
             memset(_mesg_c,'\0',sizeof(_mesg_c));
             read(_pipe[],_mesg_c,sizeof(_mesg_c));
             printf("%s\n",_mesg_c);
             j++;
         }
     }
 }
           

運作結果

程式間通信 --管道

這樣就實作了程序間通信。(單向通信)父程序讀,子程序寫。

使用管道需要注意以下四種特殊情況(假設都是阻塞I/O操作,沒有設定O_NONBLOCK标志)

1. 如果所有指向管道寫端的檔案描述符都關閉了,(管道寫端的引用計數為0),而仍然有程序從管道的讀端讀取資料,那麼管道中剩餘的資料都被讀取之後,再次read将會傳回0,就像讀到檔案結尾一樣。也就是說,寫端不會寫,讀端讀完之後就會再等着寫端去寫,但是寫端關閉了啊,不會寫了,是以就出現上面說的情況。這就展現出了管道的同步機制。

2. 如果有指向管道寫端的檔案描述符沒有關閉,(管道寫端的引用計數大于0)而持有管道寫端的程序也沒有向管道中寫資料,這時有程序管道讀端讀資料,那麼管道中剩餘的資料都被讀取後,再次read會阻塞,直到管道中有資料可讀了才讀取資料并傳回。通俗講就是,讀端讀資料,一直讀,但是寫端不寫了,而且寫端并沒有關閉,是以這時讀端就會一直等着寫端去寫。這就造成了阻塞式等待。

3.如果所有指向管道讀端的檔案描述符都關閉了(管道讀端的引用計數為0),這時有程序向管道的寫端寫資料,那麼該程序會收到SIGPIPE,通常會導緻程序異常終止。是以程序就會異常退出了。

4.如果有指向管道讀端的檔案描述符沒關閉(管道讀端的引用計數大于0)而持有管道讀端的程序也沒有從管道中讀取資料,這時有程序向管道寫端寫資料,那麼在管道寫滿時再寫将會阻塞,直到管道中有了空位置才寫入并傳回,也就是管道的同步機制。

實作方法二:命名管道

匿名管道存在一個很大的缺陷,隻能用于具有親緣關系的程序間通信,是以提出了FIFO提出後,該限制得到了克服,FIFO不同于匿名管道之處在于它提供了一個與路徑與之關聯,以FIFO的檔案形式存儲于檔案系統中,命名管道是一個裝置檔案,是以,即使程序之間不存在血緣關系,隻要可以通路該路徑,就能通過FIFO互相通信,值得注意的是命名管道是先進先出。

在linux下FIFO的建立有兩種方式:

1.在shell下互動地建立一個命名管道。(使用mknod或mkfifo指令【建議使用】)

2.在程式中使用系統函數建立命名管道。

#include <sys/types.h>
#include <sys/stat.h>
int mknod(const char* path, mode_t mode, dev_t dev);
int mkfifo(const char* path, mode_t mode);
           

函數參數中的path為建立的命名管道的路徑名,mod為建立命名管道的模式,指明其存取權限,dev為裝置值,該值檔案建立的種類,它隻在建立裝置檔案時才會用到。這兩個函數帶哦用成功傳回0,失敗都傳回-1.

下面用mknod函數建立一個命名管道

umask();//重置管道的存取權限
if(mknod("/tmp/fifo",S_IFIFO|)==-)
{
    perror("mknod error");
    exit();
}
//函數mkfifo的使用代碼
umask();
if(mkfifo("/tmp/fifo",S_IFIFO|)==- )
{
    perror("mkfifo error");
    exit();
}
//"S_IFIFO|0666"緻命建立一個管道的存取權限為0666
           

命名管道的使用和匿名管道基本相同,隻是在使用命名管道之前首先要使用open函數打開,因為命名管道是存在于硬碟上的檔案,而管道是存在于記憶體中的特殊檔案。

需要注意,使用open的幾點:

1. 調用open()打開命名管道可能會被阻塞,但是如果同時用讀寫方式(O_RDWR)打開,則一定不會造成阻塞。

2. 如果以制度方式(O_RDONLY)打開,則調用open()函數的程序将會被阻塞直到有寫才能打開管道。

3. 同樣,以寫方式(O_WRONLY)打開也會阻塞直到有讀方式打開管道。

下面用代碼實作一下FIFO管道通信。

servser.c //寫端
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#define _PATH_ "/tmp/file.tmp"
#define _SIZE_ 100
//寫端
int main()
{
    umask();
    int ret = mkfifo(_PATH_,  | S_IFIFO);
    if(ret == -)
    {
        printf("mkfifo error\n");
        return ;
    }
    int fd = open(_PATH_, O_WRONLY);
    if(fd < )
    {
        printf("open error!\n");
    }
    char buf[_SIZE_];
    memset(buf, '\0', sizeof(buf));
    while()
    {
        scanf("%s", buf);
        int ret = write(fd, buf, strlen(buf)+);
        if(ret < )
        {
            printf("write error\n");
            break;
        }
        if(strncmp(buf, "quit", ) == )
        {
            break;
        }
    }
    close(fd);
    return ;
}

client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#define _PATH_ "/tmp/file.tmp"
#define _SIZE_ 100
//讀端
int main()
{
    int fd = open(_PATH_, O_RDONLY);
    if(fd < )
    {
        printf("open file error\n");
        return ;
    }
    char buf[_SIZE_];
    memset(buf, '\0', sizeof(buf));
    while()
    {
        int ret = read(fd, buf, sizeof(buf));
        if(ret <= )
        {
            printf("read end or error\n");
            break;
        }
        printf("%s\n", buf);
        if(strncmp(buf, "quit", ) == )
        {
            break;
        }
    }
    close(fd);
    return ;
}
           

下邊是運作結果:

程式間通信 --管道

為了能清晰的看到不同的程序之間進行通信,我們啟動了兩個終端,通過寫端發送消息讀端可以收到。

程式間通信 --管道

總結:

     我們實作了兩種方法來進行不同的程序之間的通信,總結來說匿名管道和命名管道最大的差別就是匿名管道隻能進行有血緣關系的程序之間才能通信,而命名管道可以是兩個不相幹的程序之間通信,避免了匿名管道的局限性,而他們之所具有這樣的特點是因為他們的實作方法,匿名管道通過血親擁有相同的檔案描述符來讀取同一段緩沖區而命名管道是設定一個全局路徑,兩個程序都可以看到,用這個路徑名來辨別一個IPC通道。然而他們也有一些其他的缺點,比如管道是有容量的,程序之間的通信是單向的,是以後邊我們還會學習XSI IPC來實作程序間通信。

繼續閱讀