IPC——管道

概述html

管道通訊分爲無名管道、有名管道數組

管道通訊的本質緩存

不論是有名管道,仍是無名管道,它們的本質其實都是同樣的,它們都是內核所開闢的一段緩存空間。進程間經過管道通訊時,本質上就是經過共享操做這段緩存來實現,只不過操做這段緩存的方式,是以讀寫文件的形式來操做的。ide

 

無名管道函數

如何操做無名管道post

以讀寫文件的方式操做無名管道ui

1)有讀寫用的文件描述符(API部分講)
2)讀寫時會用write、read等文件IO函數。url

爲何叫無名管道spa

既然能夠經過「文件描述符」來操做管道,那麼它就是一個文件(管道文件),可是無名管道文件比較特殊,它沒有文件名,正是由於沒有文件名,全部被稱爲無名管道。指針

看下open的原型

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);

返回值是文件描述符,或者-1(此時errno被設置)

無名管道的例子說明獲取文件描述符未必非得使用open函數

注意⚠️:

man手冊查詢pipe函數的時候,形參多是int *pipefd。可是這種寫法不太直觀,因此通常寫成int pipe(int pipefd[2])。那麼問題來了,int[2]類型和int*類型同樣嗎?

這要分狀況,對於函數參數,他倆沒區別

其餘狀況是有區別的,舉個數組指針的例子。

int ar[10]={0}               

int (*p)[10]=&ar           √

int **p=&ar                  ✘

最後一句話是錯的,緣由就是int*和int[10]類型是不同的。

無名管道特色

無名管道只能用於親緣進程之間通訊。

因爲沒有文件名,所以進程沒辦法使用open打開管道文件,從而獲得文件描述符,因此只有一種辦法,那就是父進程先調用pipe建立出管道,並獲得讀寫管道的文件描述符。而後再fork出子進程,讓子進程經過繼承父進程打開的文件描述符,父子進程就能操做同一個管道,從而實現通訊。

API

PIPE原型

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

功能

建立一個用於親緣進程(父子進程)之間通訊的無名管道(緩存),並將管道與兩個讀寫文件描述符關聯起來。無名管道只能用於親緣進程之間通訊。

參數

緩存地址,緩存用於存放讀寫管道的文件描述符。從這個參數的樣子能夠看出,這個緩存就是一個擁有兩個元素的int型數組。

1)元素[0]:裏面放的是讀管道的讀文件描述符
2)元素[1]:裏面放的是寫管道的寫文件描述符。

特別須要注意的是,這裏的讀和寫文件描述符,是兩個不一樣的文件描述符。

從這裏你們也能夠看出,並非全部的文件描述符,都是經過open函數打開文件獲得的。這裏無名管道的讀、寫文件描述符,就是直接在建立管道時獲得的,與open沒有任何關係。並且這裏也根本沒辦法使用open函數,由於open函數須要文件路徑名,無名管道連文件名都沒有,因此說根本就沒辦法使用open來打開文件,返回文件描述符。

返回值

成功返回0,失敗則返回-1,而且errno被設置。

父子進程 藉助無名管道 單向通訊

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <strings.h>
 5 #include <signal.h>
 6 
 7 void print_err(char *estr)
 8 {
 9     perror(estr);
10     exit(-1);
11 }
12 
13 int main(void)
14 {
15     int ret = 0;
16     int pipefd[2] = {0};//用於存放管道的讀寫文件描述符
17     
18     ret = pipe(pipefd);
19     if(ret == -1) print_err("pipe fail");
20 
21     ret = fork();
22     if(ret > 0)
23     {    
24         close(pipefd[0]);    
25         while(1)
26         {
27             write(pipefd[1], "hello", 5);                
28             sleep(1);
29         }
30     }
31     else if(ret == 0)
32     {
33         close(pipefd[1]);
34         while(1)
35         {
36             char buf[30] = {0};
37             bzero(buf, sizeof(buf));
38             read(pipefd[0], buf, sizeof(buf));
39             printf("child, recv data:%s\n", buf);
40         }    
41     }
42 
43     return 0;
44 }

父子進程 藉助無名管道 雙向通訊

雙向通訊使用一個管道行不行?

不行,因爲繼承關係,父子進程都有讀文件描述符,父進程發給子進程的消息,子進程不必定能收到,由於可能被父進程搶讀了。

解決辦法

使用2個管道,每一個管道負責一個方向的通訊

父進程建立2個管道,有4個文件描述符。子進程繼承父進程的文件描述符,父子進程加起來有8個文件描述符。

實現代碼

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <strings.h>
 5 #include <signal.h>
 6 
 7 void print_err(char *estr)
 8 {
 9     perror(estr);
10     exit(-1);
11 }
12 
13 int main(void)
14 {
15     int ret = 0;
16     //[0]:讀文件描述符
17     //[1]:寫文件描述符
18     int pipefd1[2] = {0};//用於存放管道的讀寫文件描述符
19     int pipefd2[2] = {0};//用於存放管道的讀寫文件描述符
20     
21     ret = pipe(pipefd1);
22     if(ret == -1) print_err("pipe fail");
23     ret = pipe(pipefd2);
24     if(ret == -1) print_err("pipe fail");
25 
26     ret = fork();
27     if(ret > 0)
28     {    
29         close(pipefd1[0]);
30         close(pipefd2[1]);
31         char buf[30] = {0};
32         while(1)
33         {
34             write(pipefd1[1], "hello", 5);                
35             sleep(1);
36 
37             bzero(buf, sizeof(buf));
38             read(pipefd2[0], buf, sizeof(buf));
39             printf("parent, recv data:%s\n", buf);
40         }
41     }
42     else if(ret == 0)
43     {
44         close(pipefd1[1]);
45         close(pipefd2[0]);
46         char buf[30] = {0};
47         while(1)
48         {
49             sleep(1);    
50             write(pipefd2[1], "world", 5);
51 
52             bzero(buf, sizeof(buf));
53             read(pipefd1[0], buf, sizeof(buf));
54             printf("child, recv data:%s\n", buf);
55         }    
56     }
57 
58     return 0;
59 }

代碼裏,父子進程中write都寫在了read前面。write是非阻塞函數,父子進程中只須要保證至少一個write在前就不會使父子進程阻塞。  若是父子進程read都在write前,則父子進程都會因read而阻塞

 

有名管道

無名管道由於沒有文件名,被稱爲了無名管道,一樣的道理,有名管道之因此叫「有名管道」,是由於它有文件名。也就是說當咱們調用相應的API建立好「有名管道」後,會在相應的路徑下面看到一個叫某某名字的「有名管道文件」。

有名管道特色

①可以用於非親緣進程之間的通訊

由於有文件名,因此進程能夠直接調用open函數打開文件,從而獲得文件描述符,不須要像無名管道同樣,必須在經過繼承的方式才能獲取到文件描述符。因此任何兩個進程之間,若是想要經過「有名管道」來通訊的話,無論它們是親緣的仍是非親緣的,只要調用open函數打開同一個「有名管道」文件,而後對同一個「有名管道文件」進行讀寫操做,便可實現通訊。

②讀管道時,若是管道沒有數據的話,讀操做一樣會阻塞(休眠)

③當進程寫一個全部讀端都被關閉了的管道時,進程會被內核返回SIGPIPE信號

 

有名管道使用步驟

①進程調用mkfifo建立有名管道
②open打開有名管道
③read/write讀寫管道進行通訊

對於通訊的兩個進程來講,建立管道時,只須要一我的建立,另外一個直接使用便可。爲了保證管道必定被建立,最好是兩個進程都包含建立管道的代碼,誰先運行就誰先建立,後運行的發現管道已經建立好了,那就直接open打開使用。

 

API

mkfifo原型 

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

功能

建立有名管道文件,建立好後即可使用open打開。

若是是建立普通文件的話,咱們可使用open的O_CREAT選項來建立,好比:open("./file", O_RDWR|O_CREAT, 0664);

可是對於「有名管道」這種特殊文件,這裏只能使用mkfifo函數來建立。

參數

1)pathname:被建立管道文件的文件路徑名。

2)mode:指定被建立時原始權限,通常爲0664(110110100),必須包含讀寫權限。

參考:umask、setuid、setgid、sticky bit、chmod、chown 中umask

   Linux——文件 中umask

 返回值

成功返回0,失敗則返回-1,而且errno被設置。

 

 有名管道單項通訊

單獨啓動2個進程通訊

p1.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>


#define FIFONAME1 "./fifo1"
#define FIFONAME2 "./fifo2"

void print_err(char *estr)
{    
    perror(estr);
    exit(-1);
}    

int creat_open_fifo(char *fifoname, int open_mode)
{    
    int ret = -1;
    int fd = -1;    
    
    ret = mkfifo(fifoname, 0664);
    //若是mkfifo函數出錯了,可是這個錯誤是EEXIST,不報這個錯誤(忽略錯誤)
    if(ret == -1 && errno!=EEXIST) print_err("mkfifo fail");    
    
    fd = open(fifoname, open_mode);
    if(fd == -1) print_err("open fail");

    return fd;
}

void signal_fun(int signo)
{
    //unlink();
    remove(FIFONAME1);
    exit(-1);
}
    
int main(void)
{
    char buf[100] = {0};
    int ret = -1;
    int fd1 = -1;
    signal(SIGINT, signal_fun);
    fd1 = creat_open_fifo(FIFONAME1, O_WRONLY);
    while(1)
    {
        bzero(buf, sizeof(buf));
    scanf("%s", buf);
    write(fd1, buf, sizeof(buf));    
    }
    return 0;
}
View Code

p2.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>


#define FIFONAME1 "./fifo1"
#define FIFONAME2 "./fifo2"

void print_err(char *estr)
{
        perror(estr);
        exit(-1);
}

int creat_open_fifo(char *fifoname, int open_mode)
{
        int ret = -1;
        int fd = -1;

        ret = mkfifo(fifoname, 0664);
        //若是mkfifo函數出錯了,可是這個錯誤是EEXIST,不報這個錯誤(忽略錯誤)
        if(ret == -1 && errno!=EEXIST) print_err("mkfifo fail");

        fd = open(fifoname, open_mode);
        if(fd == -1) print_err("open fail");

        return fd;
}

void signal_fun(int signo)
{
        //unlink();
        remove(FIFONAME1);
        exit(-1);
}

int main(void)
{
    char buf[100] = {0};
    int ret = -1;
    int fd1 = -1;
    signal(SIGINT, signal_fun);
    fd1 = creat_open_fifo(FIFONAME1, O_RDONLY);
    while(1)
    {
        bzero(buf, sizeof(buf));
    read(fd1,buf,sizeof(buf));
        printf("%s\n", buf);
    }
    return 0;
}
View Code

這裏須要注意一點把signal註冊信號處理函數放到creat_open_fifo函數以前的緣由是:先讓系統知道怎麼處理Ctrl+C硬件中斷。要否則creat_open_fifo在前的話,阻塞在mkfifo上,系統還不知道怎麼處理Ctrl+C這個硬件中斷信號。也就無法刪除有名管道文件。這裏其實OS應該是處理了,OS的處理就是默認處理方式。即乾死當前進程,可是沒有刪除管道文件。

若是creat_open_fifo在signal以前,會出現進程被幹死了,可是有名管道文件沒有被刪除的狀況。

參考:Linux有名管道的 阻塞VS非阻塞 讀寫

有名管道雙向通訊

使用一個有名管道是沒法實現雙向通訊的,道理同無名管道,即存在搶讀問題。

單獨啓動2個進程

p1.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>


#define FIFONAME1 "./fifo1"
#define FIFONAME2 "./fifo2"

void print_err(char *estr)
{    
    perror(estr);
    exit(-1);
}    

int creat_open_fifo(char *fifoname, int open_mode)
{    
    int ret = -1;
    int fd = -1;    
    
    ret = mkfifo(fifoname, 0664);
    //若是mkfifo函數出錯了,可是這個錯誤是EEXIST,不報這個錯誤(忽略錯誤)
    if(ret == -1 && errno!=EEXIST) print_err("mkfifo fail");    
    
    fd = open(fifoname, open_mode);
    if(fd == -1) print_err("open fail");

    return fd;
}

void signal_fun(int signo)
{
    //unlink();
    remove(FIFONAME1);
    remove(FIFONAME2);
    exit(-1);
}

int main(void)
{
    char buf[100] = {0};
    int ret = -1;
    int fd1 = -1;
    int fd2 = -1;


    fd1 = creat_open_fifo(FIFONAME1, O_WRONLY);
    fd2 = creat_open_fifo(FIFONAME2, O_RDONLY);
    
    while(1)
    {
        bzero(buf, sizeof(buf));
        scanf("%s", buf);
        write(fd1, buf, sizeof(buf));    
        
        read(fd2, buf, sizeof(buf));
    }
    return 0;
}
View Code

p2.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>


#define FIFONAME1 "./fifo1"
#define FIFONAME2 "./fifo2"

void print_err(char *estr)
{    
    perror(estr);
    exit(-1);
}    

int creat_open_fifo(char *fifoname, int open_mode)
{    
    int ret = -1;
    int fd = -1;    
    
    ret = mkfifo(fifoname, 0664);
    //若是mkfifo函數出錯了,可是這個錯誤是EEXIST,不報這個錯誤(忽略錯誤)
    if(ret == -1 && errno!=EEXIST) print_err("mkfifo fail");    
    
    fd = open(fifoname, open_mode);
    if(fd == -1) print_err("open fail");

    return fd;
}

void signal_fun(int signo)
{
    //unlink();
    remove(FIFONAME1);
    remove(FIFONAME2);
    exit(-1);
}

int main(void)
{
    char buf[100] = {0};
    int ret = -1;
    int fd1 = -1;
    int fd2 = -1;


    fd1 = creat_open_fifo(FIFONAME1, O_RDONLY);
    fd2 = creat_open_fifo(FIFONAME2, O_WRONLY);
    
    while(1)
    {
        bzero(buf, sizeof(buf));
        scanf("%s", buf);
        read(fd1, buf, sizeof(buf));
        printf("recv:%s\n", buf);
        write(fd2, buf, sizeof(buf));    
    }
    return 0;
}
View Code

 這2個代碼體驗及其糟糕,p2;裏面read在write以前,read會阻塞p2。這也是沒辦法避免的,read和write放一塊就會出問題。解決之道

代碼

p1.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>


#define FIFONAME1 "./fifo1"
#define FIFONAME2 "./fifo2"

void print_err(char *estr)
{    
    perror(estr);
    exit(-1);
}    

int creat_open_fifo(char *fifoname, int open_mode)
{    
    int ret = -1;
    int fd = -1;    
    
    ret = mkfifo(fifoname, 0664);
    //若是mkfifo函數出錯了,可是這個錯誤是EEXIST,不報這個錯誤(忽略錯誤)
    if(ret == -1 && errno!=EEXIST) print_err("mkfifo fail");    
    
    fd = open(fifoname, open_mode);
    if(fd == -1) print_err("open fail");

    return fd;
}

void signal_fun(int signo)
{
    //unlink();
    remove(FIFONAME1);
    remove(FIFONAME2);
    exit(-1);
}
        
int main(void)
{
    char buf[100] = {0};
    int ret = -1;
    int fd1 = -1;
    int fd2 = -1;


    fd1 = creat_open_fifo(FIFONAME1, O_WRONLY);
    fd2 = creat_open_fifo(FIFONAME2, O_RDONLY);

    ret = fork();
    if(ret > 0)
    {
        signal(SIGINT, signal_fun);
        while(1)
        {
            bzero(buf, sizeof(buf));
            scanf("%s", buf);
            write(fd1, buf, sizeof(buf));    
        }
    }
    else if(ret == 0)
    {
        while(1)
        {
            bzero(buf, sizeof(buf));
            read(fd2, buf, sizeof(buf));
            printf("%s\n", buf);
        }
    }

    return 0;
}    
    



    
    
    
View Code

p2.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>


#define FIFONAME1 "./fifo1"
#define FIFONAME2 "./fifo2"

void print_err(char *estr)
{    
    perror(estr);
    exit(-1);
}    

int creat_open_fifo(char *fifoname, int open_mode)
{    
    int ret = -1;
    int fd = -1;    
    
    ret = mkfifo(fifoname, 0664);
    //若是mkfifo函數出錯了,可是這個錯誤是EEXIST,不報這個錯誤(忽略錯誤)
    if(ret == -1 && errno!=EEXIST) print_err("mkfifo fail");    
    
    fd = open(fifoname, open_mode);
    if(fd == -1) print_err("open fail");

    return fd;
}

void signal_fun(int signo)
{
    //unlink();
    remove(FIFONAME1);
    remove(FIFONAME2);
    exit(-1);
}
        
int main(void)
{
    char buf[100] = {0};
    int ret = -1;
    int fd1 = -1;
    int fd2 = -1;


    fd1 = creat_open_fifo(FIFONAME1, O_RDONLY);
    fd2 = creat_open_fifo(FIFONAME2, O_WRONLY);


    ret = fork();
    if(ret > 0)
    {
        signal(SIGINT, signal_fun);
        while(1)
        {
            bzero(buf, sizeof(buf));
            read(fd1, buf, sizeof(buf));
            printf("recv:%s\n", buf);
        }
    }
    else if(ret == 0)
    {
        while(1)
        {    
            bzero(buf, sizeof(buf));
            scanf("%s", buf);
            write(fd2, buf, sizeof(buf));
            
        }
    }

    return 0;
}    
    



    
    
    
View Code

注意:父子進程的buf是不同的,這得益於子進程繼承父進程。

處理Ctrl+C硬件中斷,只有父進程作了掃尾工做(即刪除管道文件),而後父進程正常終止(調用exit(-1))。子進程採用默認處理方式,即被OS直接乾死。

 

 

 網狀通訊

每個節點想象成一個進程

不論是無名管道、仍是有名管道,實現網狀通訊都很困難

相關文章
相關標籤/搜索