Linux進程通信 之 管道

 

 

管道可用於具有親緣關係進程間的通信,有名管道克服了管道沒有名字的限制,因此,除具有管道所具有的功能外,它還允許無親緣關係進程間的通信。 


管道是Linux支持的最初Unix IPC形式之一,具有以下特點:

管道是半雙工的,數據只能向一個方向流動;需要雙方通信時,需要建立起兩個管道;

只能用於父子進程或者兄弟進程之間(具有親緣關係的進程);

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

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


管道兩端用描述字fd[0]以及fd[1]來描述,管道的兩端是固定了任務的。fd[0]只能用於讀,稱其爲管道讀端;另一端則只能用於寫,由描述字fd[1]來表示,稱其爲管道寫端。


從管道中讀取數據:

如果管道的寫端不存在,則認爲已經讀到了數據的末尾,讀函數返回的讀出字節數爲0;

當管道的寫端存在時,如果請求的字節數目大於PIPE_BUF,則返回管道中現有的數據字節數,如果請求的字節數目不大於PIPE_BUF,則返回管道中現有數據字節數(此時,管道中數據量小於請求的數據量);或者返回請求的字節數(此時,管道中數據量不小於請求的數據量)。

如果管道沒有數據,且管道的寫端口是打開狀態,則讀操作被阻塞直到有數據寫入爲止。


向管道中寫入數據:

對於設置了阻塞標誌的寫操作:

當要寫入的數據量不大於PIPE_BUF時,linux將保證寫入的原子性。如果此時管道空閒緩衝區不足以容納要寫入的字節數,則進入睡眠,直到當緩衝區中能夠容納要寫入的字節數時,纔開始進行一次性寫操作。

當要寫入的數據量大於PIPE_BUF時,linux將不再保證寫入的原子性。FIFO緩衝區一有空閒區域,寫進程就會試圖向管道寫入數據,寫操作在寫完所有請求寫的數據後返回。

對於沒有設置阻塞標誌的寫操作:

當要寫入的數據量大於PIPE_BUF時,linux將不再保證寫入的原子性。在寫滿所有FIFO空閒緩衝區後,寫操作返回。

當要寫入的數據量不大於PIPE_BUF時,linux將保證寫入的原子性。如果當前FIFO空閒緩衝區能夠容納請求寫入的字節數,寫完後成功返回;如果當前FIFO空閒緩衝區不能夠容納請求寫入的字節數,則返回EAGAIN錯誤,提醒以後再寫;


管道的主要侷限性正體現在它的特點上:

只支持單向數據流;

只能用於具有親緣關係的進程之間;

沒有名字;

管道的緩衝區是有限的(管道制存在於內存中,在管道創建時,爲緩衝區分配一個頁面大小);

管道所傳送的是無格式字節流,這就要求管道的讀出方和寫入方必須事先約定好數據的格式,比如多少字節算作一個消息(或命令、或記錄)等等;


有名管道以FIFO的文件形式存在於文件系統中。這樣,即使與FIFO的創建進程不存在親緣關係的進程,只要可以訪問該路徑,就能夠彼此通過FIFO相互通信


 

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, const char **argv)
{
    int len;
    pid_t pid;
    int pfd[2];
    char buffer[1024] = {0};

    if (pipe(pfd) < 0) {
        printf("pipe error\n");
        exit(0);
    }

    if ((pid = fork()) < 0) {
        printf("fork error\n");
        exit(0);
    }

    if (pid == 0) {      /* 子進程 */
        write(pfd[1], "hello\n", 6);
        close(pfd[1]);
        sleep(2);
    } else {               /* 父進程 */
        close(pfd[1]);
        while ((len = read(pfd[0], buffer, 1023)) > 0) {
            buffer[len] = '\0';
            printf("len = %d, %s", len, buffer);
        }
        printf("read done\n");
        wait(pid);      /* 等待子進程退出才能看到效果 */
    }
    return 0;
}


1 只有管道寫端的引用計數變成0,纔會關閉管道得寫端.

2 進程結束後,自動關閉打開的文件
3 如果父進程先退出,而且沒有調用wait等待子進程, 那麼子進程就會變成僵死進程


 

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, const char **argv)
{
    int len;
    pid_t pid;
    int pfd[2];
    char buffer[1024] = {0};

    char *envp[] = {"PATH=/tmp", "QUERY_STRING=m=18&n=19", NULL};
   
    if (pipe(pfd) < 0) {
        printf("pipe error\n");
        exit(0);
    }

    if ((pid = fork()) < 0) {
        printf("fork error\n");
        exit(0);
    }

    if (pid == 0) {      /* 子進程 */
        close(pfd[1]);
        while ((len = read(pfd[0], buffer, 1023)) > 0) {
            buffer[len] = '\0';
            printf("len = %d, %s", len, buffer);
        }
    } else {               /* 父進程 */
        write(pfd[1], "hello\n", 6);
        close(pfd[1]);
        wait(pid);
    }
    return 0;
}

 1 如果父進程不關閉管道的寫端, 即便子進程關閉了管道的寫端, 還是會阻塞在read上


 

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, const char **argv)
{
    int len;
    pid_t pid;
    int pfd[2];
    char buffer[1024] = {0};

    if (pipe(pfd) < 0) {
        printf("pipe error\n");
        exit(0);
    }

    if ((pid = fork()) < 0) {
        printf("fork error\n");
        exit(0);
    }

    if (pid == 0) {      /* 子進程 */
            len = read(pfd[0], buffer, 10);
            printf("## %s", buffer);
            len = write(pfd[1], "from child hello\n", 17);
    } else {               /* 父進程 */
        len = write(pfd[1], "from parent hello\n", 18);
        sleep(1);
        len = read(pfd[0], buffer, 20);
        printf("@@ %s", buffer);
        close(pfd[1]);
        close(pfd[0]);
        wait(pid);
    }
    return 0;
}

  1 使用一個管道進行雙向通信是不行的, 會產生混亂, 要想使用管道實現雙向通信,必須創建兩個管道


 

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

int main()   
{   
    int fd1[2],fd2[2],cld_pid,status;   
    char buf[200], len;   

    if (pipe(fd1) == -1) {// 創建管道1   
        printf("creat pipe1 error\n");   
        exit(1);   
    }   
    if (pipe(fd2) == -1) {// 創建管道2   
        printf("creat pipe2 error\n");   
        exit(1);   
    }   

    if ((cld_pid=fork()) == 0) {//子進程   
        close(fd1[1]); // 子進程關閉管道1的寫入端   
        close(fd2[0]); // 子進程關閉管道1的讀出端   

        //子進程讀管道1   
        len = read(fd1[0],buf,sizeof(buf));   
        printf("%s",buf);   

        //子進程寫管道2   
        strcpy(buf,"hi, father, this is your son!\n");   
        write(fd2[1],buf,strlen(buf));   

        exit(0);   
    }   
    else {//父進程
        close(fd1[0]); // 父進程關閉管道1的讀出端   
        close(fd2[1]); // 父進程關閉管道2的寫入端   

        //父進程寫管道1   
        strcpy(buf,"hey, son, I'm your father\n");   
        write(fd1[1],buf, strlen(buf));   

        //父進程讀管道2   
        len = read(fd2[0],buf,sizeof(buf));   
        printf("%s",buf);   
        exit(0);   
    }  
}

 

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