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直接干死。

 

 

 网状通讯

每个节点想象成一个进程

不论是无名管道、仍是有名管道,实现网状通讯都很困难

相关文章
相关标签/搜索