進程間通信之管道(用水管思維理解)

Nothing is ever a setback. If anghing, it just motivate you for what is next.--------沒有挫折這回事,他若具有任何意義,那只是激勵你再下一個挑戰中表現更好.

1,前景回顧

進程間的通信肯定是與進程相關,小黑在之前寫過相關的博客,有興趣的或者對這方面這知識有點生疏的同學可以移步:

https://blog.csdn.net/weixin_46027505/article/details/104812719

https://blog.csdn.net/weixin_46027505/article/details/105141592

2,管道真的就是管道

  • 在介紹管道前有必要了解一下3種通信方式。

1, 單工通信:傳輸方向只有一個方向,單工通信只有一根數據線,它也只在一個方向上進行,如打印機、電視機等。比如:電視機,廣播
2, 半雙工通信:可以雙向通信,但只能輪流傳輸,也只有一根數據線,不同於單工通信的是這根數據線即可作爲發送又可作爲接收,雖數據可在兩個方向上傳送,但通信雙方不能同時收發數據。比如:對講機
3, 全雙工通信:可以同時雙向傳輸數據,數據的發送和接收用兩根不同的數據線,通信雙方在同一時刻都能進行發送和接收,發送和接收同時進行,沒有延遲。比如視頻聊天

2.1 管道通俗理解

  • 爲什麼這種通信機制叫做管道呢? 因爲它的功能真的就是生活中管道(水道)的作用。

我們把供水商比作一個進程空間,我們自己家是另外一個進程空間,管道就是用來送水的,水就是我們進程間通信傳輸的數據,往水管裏注水就相當於向文件寫入數據
這裏需要記住,管道是單向的、先進先出的,這個容易理解,就像供水商給你送水時,你不能向這個水管注水。

2.2 爲什麼管道是半雙工通信?

由於Linux一個命令只能完成一個功能,所以一個複雜點的任務需要好幾個進程協同完成,第一個進程處理結果需交給第二個進程,然後一次交給第三個,等等,像流水線完成某個商品的生產一樣,這個過程只需要數據單向往下傳輸,所以設計的時候做成了半雙工。
當然也可以使用管道實現雙工通信,需要兩個管道。

2.3 管道特點

管道是,它把一個進程的輸出和另一個進程的輸入連接在一起。

1, 一個進程(寫進程)在管道的尾部寫入數據,另一個進程(讀進程)從管道的頭部讀出數據。
管道是固定讀端和寫端的。寫入的內容每次都添加在管道緩衝區的末尾,並且每次都是從緩衝區的頭部讀出數據。

2, 數據被一個進程讀出後,將被從管道中刪除,其它讀進程將不能再讀到這些數據。

3, 管道提供了簡單的流控制機制,進程試圖讀空管道時,進程將阻塞。同樣,管道已經滿時,進程再試圖向管道寫入數據,進程將阻塞

4,管道對於管道兩端的進程而言,就是一個文件,但它不是普通的文件,它不屬於某種文件系統,而是自立門戶,單獨構成一種文件系統,並且只存在與內存中。

5, 向管道中寫入數據時,linux將不保證寫入的原子性,管道緩衝區一有空閒區域,寫進程就會試圖向管道寫入數據。如果讀進程不讀走管道緩衝區中的數據,那麼寫操作將一直阻塞。

6, 只有在管道的讀端存在時,向管道中寫入數據纔有意義。否則,向管道中寫入數據的進程將收到內核傳來的SIFPIPE信號,應用程序可以處理該信號,也可以忽略(默認動作則是應用程序終止)。

  • 當然管道有兩種類型。
    1,無名管道:這個只能用於父子進程,這個你可以理解成,你和你爸爸分家了,這個管道就是用來你們兩家送水用的。因爲這個管道你和爸爸
    2,命名管道:可以用於同一系統中,任何進程間通信。

後面馬上會介紹到。

3,無名管道(有時會直接叫做管道,不要混淆)

管道是UNIX系統IPC的最古老的形式,所有的UNIX系統都提供此種通信機制。管道的實質是一個內核緩衝區,進程以先進 先出(FIFO, First In First Out)的方式從緩衝區存取數據:管道一端的進程順序地將進程數據寫入緩衝區,另一端的進程則順 序地讀取數據,該緩衝區可以看做一個循環隊列,讀和寫的位置都是自動增加的,一個數據只能被讀一次,讀出以後再緩衝區都 不復存在了。當緩衝區讀空或者寫滿時,有一定的規則控制相應的讀進程或寫進程是否進入等待隊列,當空的緩衝區有新數據寫 入或慢的緩衝區有數據讀出時,就喚醒等待隊列中的進程繼續讀寫。

3.1 侷限性

(1)半雙工,數據只能在一個方向流動,現在有些系統可以支持全雙工管道,但是爲了最佳的可移植性,應認爲系統 不支持全雙工管道;

(2)無名管道只能用於具有父子進程關係通信;

#3 3.2 無名管道的創建
管道可以看成是一種特殊的文件,對於它的讀寫也可以使用普通的read、write 等函數。但是它不是普通的文件,並不 屬於其他任何文件系統,並且只存在於內存中。
管道是通過調用pipe函數創建的。

#include <unistd.h>
int pipe(int fd[2]); 
  • 返回值:若成功,返回0,若出錯,返回-1.
    經由參數fd返回的兩個文件描述符:
    fd[0]只爲讀而打開(用來在管道里取水)
    fd[1]只爲寫而打開 (用來在管道里注水)
    fd[1]的輸出是fd[0]的輸入

4,用無名管道實現單進程通信

其實管道就是文件,下面的示例代碼就是自己給自己發送信息然後讀出來。
在這裏插入圖片描述

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>


#define MSG "my name is xuxiaohei"

int main(int argc, char **argv)
{
    int     pipe_fd[1];
    int     rv;
    char    buf[512];


   
    if (pipe(pipe_fd) < 0)
    {
        printf("Create pipe failure: %s\n", strerror(errno));
        return -1;
    }

    memset(buf, 0, sizeof(buf));
        
    if (write(pipe_fd[1], MSG, strlen(MSG)) < 0)
    {
        printf(" write data to pipe failure: %s\n", strerror(errno));
        return -2;
    }
    
    rv = read(pipe_fd[0], buf, sizeof(buf));
    if (rv < 0)
    {
            printf(" read from pipe failure: %s\n", strerror(errno));
            return -3;
    }
    printf("process read %d bytes data from pipe: \"%s\"\n", rv, buf);

      

    return 0;
}

在這裏插入圖片描述

5,用無名管道實現父子進程通信

下面編寫一個例程,用於父進程給子進程方向發送數據。首先父進程創建管道之後fork(),這時子進程會繼承父進程所有打開的 文件描述符(包括管道),這時對於一個管道就有4個讀寫端(父子進程各有一對管道讀寫端),如果需要父進程往子進程裏寫 數據,則需要在父進程中關閉讀端,在子進程中關閉寫端;而如果需要子進程往父進程中寫數據,則可以在父進程關閉寫端,然 後子進程中關閉讀端。
在這裏插入圖片描述

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>

#define MSG_STR "This message is from father: Hello, my son!"

int main(int argc, char **argv)
{
    int     pipe_fd[1];
    int     rv;
    int     pid;
    char    buf[512];
    int     wstatus;

    printf("before creat pipe\n");
    if (pipe(pipe_fd) < 0)
    {
        printf("Create pipe failure: %s\n", strerror(errno));
        return -1;
    }


    printf("before fork\n");
    if ((pid = fork()) < 0)
    {
        printf("Create child process failure: %s\n", strerror(errno));
        return -2;
    }
    else if (pid == 0)
    {
        printf("son start read message from father\n");
        close(pipe_fd[1]);
        memset(buf, 0, sizeof(buf));
        rv = read(pipe_fd[0], buf, sizeof(buf));
        if (rv < 0)
        {
            printf("Child process read from pipe failure: %s\n", strerror(errno));
            return -3;
        }
        printf("Child process read %d bytes data from pipe: \"%s\"\n", rv, buf);
        return 0;
    }




    close(pipe_fd[0]);
    if (write(pipe_fd[1], MSG_STR, strlen(MSG_STR)) < 0)
    {
        printf("Parent process write data to pipe failure: %s\n", strerror(errno));
        return -3;
    }


    printf("father finish write and wait son read \n");
    wait(&wstatus);


    return 0;
}

關閉管道的一端     
(1)當讀一個寫端被關閉的管道時,在所有數據都被讀取後,read返回0,表示文件結束;     
(2)當寫一個讀端被關閉的管道時,則產生信號SIGPIPE,如果忽略該信號或者捕捉該信號並從其處理程序返回,則 wirte返回-1.

在這裏插入圖片描述

如果想要子進程給父進程發信息

在這裏插入圖片描述

6,有名管道FIFO(或者叫做命名管道)

當然,有名管道同樣是半雙工的。

前面講到的未命名的管道只能在兩個具有親緣關係的進程之間通信,
通過命名管道(Named PiPe)FIFO,不相關的進程也能 交換數據。

它提供一個路徑名與之關聯,以FIFO的文件形式存在於文件系統中。這樣,即使與FIFO的創建進程不存在親緣關係的進程,只要可以訪問該路徑,就能夠彼此通過FIFO相互通信(能夠訪問該路徑的進程以及FIFO的創建進程之間),它在磁盤上有對應的節點,但 沒有數據塊——換言之,只是擁有一個名字和相應的訪問權限,通過mknode()系統調用或者mkfifo()函數來建立的。一旦建 立,任何進程都可以通過文件名將其打開和進行讀寫,而不侷限於父子進程,當然前提是進程對FIFO有適當的訪問權。當不再被 進程使用時,FIFO在內存中釋放,但磁盤節點仍然存在。

因此,通過FIFO不相關的進程也能交換數據。值得注意的是,FIFO嚴格遵循先進先出(first in first out),對管道及FIFO的讀總是從開始處返回數據,對它們的寫則把數據添加到末尾。它們不支持諸如lseek()等文件定位操作。

7,無名管道和有名管道的區別

兩者之間最大的區別就是

有名在任意兩個進程間可以通信
無名只能在父子進程間通信

關於命名管道小黑另外寫一篇博客實現一個小應用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章