一、管道概述:

    在Linux中,管道是一种使用非常频繁的通信机制。从本质上说,管道也是一种文件,但是它和一般的文件有所不同,管道可以克服使用文件进程通信的两个问题,具体表现为:

    1.限制管道的大小:

        实际上,管道是一个固定大小的缓冲区。在Linux中,该缓冲区的大小为1页,即4K字节,使得它的大小不像文件那样不加检验的增长。

        使用单个固定缓冲区也会带来问题,比如在写管道时可能变满,当这种情况发生时,随后对管道的write()调用将默认地被阻塞,等待某些数据读出,以便腾出足够的空间供write()调用写。

    2.读取进程也可能工作的比写进程块:

        当所有当前进程数据已被读取时,管道变空。当这种情况发生时,一个随后的read()调用将默认地被阻塞,等待某些数据被写入,这解决了read()调用返回文件结束的问题。

注意:从管道读数据是一次性操作,数据一旦被读,它就从管道中被抛弃,释放空间以便写入更多的数据。

二、管道使用方法

    1、使用管道符“|”

        在Linux系统中,由管道连接起来的进程可以自动运行,就如同在他们有一个数据流一样。例如:

       我们要使用sort命令来排序ps的输出:

            不使用管道方法:$ ps > psout.txt   $ sort psout.txt > pssort.out

            使用管道方法:$ ps | sort > pssort.out

       我们要在屏幕上看到他们

            我们要使用第三个线程:$ ps | sort |more

        使用管道数并没有一个量的限制。如果我们要看到正在运行的处理shell以外不同名字的进程,我们可以使用下面的命令:$ ps -xo comm |sort | unip | grep -v sh |more ,在这个命令中,使用了ps的输出,将这个输出以字母的顺序进行排序,使用unip来解压线程,使用grep -v sh在移除名为sh的进程,最后在屏幕上显示结果。

    2、C语言通过管道进行进程间通信

      管道分为有名管道(fifo)和无名管道(FIFO),他们都是通过内核缓冲区按先进先出的方式进行数据传输,管道的一段顺序的写入数据,另一端顺序的读入数据读写的位置都是自动增加,数据只读一次,之后就被释放。在缓冲区写满时,则由相应的规则控制读写进程进入等待队列,当空的缓冲区有写入数据或满的缓冲区有数据读出时,就唤醒等待队列中的写进程继续写。

       无名管道:无名管道没有名字,对于管道中使用的文件描述符没有路径名,也就是不存在任何意义上的文件,它们只是在内存中跟某一个索引节点相关联的两个文件描述符。

        (1)、无名管道是半双工的,数据只能在一个方向上移动,也就是对单个管道来讲,只能进行读或者写。

        (2)、无名管道只能在具有公共祖先的进程间通信,即或是父子关系、或是在兄弟关系进程间通信。

        (3)、Linux下使用pipe(int fd[2])函数创建一个无名半双工管道,管道两端可分别用描述字fd[0]以及fd[1]来描述需要注意的是,管道的两端是固定了任务的。即一端只能用于读,有描述符fd[0]表示,称其为管道读端;另一端则只能用于写,由描述符fd[1]来表示,称其为管道写端。如果试图从管道写端读取数据、或者向管道读端写入数据都将导致错误发生。一般文件的I/O函数都可以用于管道,如read、write、close等等。

    注意:在进程读写管道时,对一个管道进行读写操作后名,read函数返回为0,有两种意义,一种是管道中无数据并且写入端已经关闭。另一种是管道中无数据,写入端依然存活。

    

        int main(int args,char *argv[])
        {
            int fd[2];//管道描述符
            char buf[MAXSIZE];//存放管道收发的数据
            memset(buf,0,sizeof(buf));
            int len;//记录长度
            if(pipe(fd)==-1)//创建无名管道
            {
                printf("error is %s",strerror(errno));
                exit(1);
            }
            int fd=fork();
            if(fd==0)
            {
                close(fd[1]);
                while((len=read(fd[0],buf,sizeof(buf)))>0)
                {
                    write(STDOUT_FILENO,buf,strlen(buf));
                }
                close(fd[0]);
            }
            if(fd>0)
            {
                close(fd[0]);
                read(STDIN_FILENO,buf,sizeof(buf));
                write(fd[1],buf,strlen(buf));
                close(fd[1]);
                waitpid(pid,NULL,0);
            }
            return 0;
        }

    有名管道:有名管道是持久稳定的,它们存在于文件系统中,可以用于不同的进程间通信。

        (1)、创建方式:

              a.通过路径名指出,在文件系统中可见

                  $ mkfifo [option] name

                 创建一个名为name的有名管道。

               b.通过函数mkfifo函数创建

                 int mkfifo(const char *pathname,mode_t mode);

                 函数成功:创建一个名为pathname的有名管道,返回0

                 函数失败:返回0,并设置errno

                

              int main()
              {
                if(mkfifo("fifo1",0666)==-1)
                {
                  printf("create fifo error:%s",strerror(errno));
                }
                else
                {
                  printf("success");
                }
                return 0;
              }

              c.删除fifo

                int unlink(const char *pathname);

                函数执行成功返回0,否则返回-1,并设置errno

            

              int main(int args,char *argv[])
              {
                if(args==1)
                   return 0;
                int i=unlink("fifo1");
                if(i==-1)
                {
                    printf("%s",strerror(errno));
                }
                return 0;
              }

        (2)、FIFO遵循先进先出规则:

               a.对管道从开始处返回数据

               b.对管道写则把数据添加到末尾

               c.不支持lseek()等文件定位操作

        (3)、操作

               a.打开和关闭FIFO

                int open(const char *pathname,int flags);

                 int close(int fd);

                 FIFO的两端使用前都要打开,open()中如果参数flags为O_RDONLY将阻塞open()调用,一直到另一个进程为写入数据打开FIFO为止。相同的,参数O_WRONLY也导致阻塞一直未读出数据打开FIFO为止。

        读FIFO:

                 int main(int args,char *argv[])
                 {
                     int len=0;
                     char buf[MaxSize];
                     memset(buf,0,sizeof(buf));
                     if((fd=open("fifo1",O_RDONLY))<0)
                         exit(1);
                     while((len=read(fd,buf,sizeof(buf)))>0)
                     {
                         write(STDOUT_FILENO,buf,strlen(buf));
                         memset(buf,0,sizeof(buf));
                      }
                      close(fd);
                     return 0;
                 }

         写FIFO:

                int main(int args,char *argv[])
                {
                    int len=0;
                    char buf[100];
                    memset(buf,0,sizeof(buf));
                    if((fd=open("fifo1",O_ERONLY))<0)
                        exit(1)
                    while(1)
                    {
                        read(STDIN_FILENO,buf,sizeof(buf));
                        if(buf[0]=='0')
                        {
                            break;
                        }
                        write(fd,buf,strlen(buf));
                    }
                    return 0;
                }

        有名管道和无名管道:

            向管道中写入数据时,Linux将不保证写入的原子性,管道缓冲区有一空闲区域,写进程就会试图向管道写入数据。如果读进程不读完??管道缓冲区的数据,那么写操作将一直阻塞。

        无名管道存在着两个严重的缺点:

        第一,无名管道只能用于连接具有共同祖先的进程;

        第二,无名管道是依附进程而临时存在的。所以有了有名管道的出现FIFO。有名管道继承了无名管道的所有特性和优点之外,还摒弃了无名管道的两个缺点。