Linux_2 管道(任务间的通信)

目录

1. 认识无名管道

2. 无名管道大小测试

3. 无名管道练习

4. 无名管道_两个管道双向传输 

5. 有名(命名)管道 

总结


任务间的通信与同步(7种)

管道    信号  信号量    互斥锁  消息队列   共享内存   socket套接字

1. 认识无名管道

管道相关的关键概念

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

(1)管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
(2)只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)
(3)单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。
(4)数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

管道的创建:

#include <unistd.h>
int pipe(int fd[2])
                         //Return 0 on success,or -1 on error

       该函数创建的管道的两端处于一个进程中间,在实际应用中没有太大意义,因此,一个进程在由pipe()创建管道后,一般再fork一个子进程,然后通过管道实现父子进程间的通信(因此也不难推出,只要两个进程中存在亲缘关系,这里的亲缘关系指的是具有共同的祖先,都可以采用管道方式来进行通信)。

管道的读写规则:

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

从管道中读取数据:

 (1)如果管道的写端不存在,则认为已经读到了数据的末尾,读函数返回的读出字节数为0;
 (2)当管道的写端存在时,如果请求的字节数目大于PIPE_BUF,则返回管道中现有的数据字节数,如果请求的字节数目不大于PIPE_BUF,则返回管道中现有数据字节数(此时,管道中数据量小于请求的数据量);或者返回请求的字节数(此时,管道中数据量不小于请求的数据量)。
注:(PIPE_BUF在 include/linux/limits.h 中定义,不同的内核版本可能会有所不同。Posix.1要求PIPE_BUF至少为 512字节,red hat 7.2中为4096)。

向管道中写入数据:

(1)向管道中写入数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,那么写操作将一直阻塞。 
(2)管道中的读端关闭,此时向管道写入数据的进程将收到内核传来的SIFPIPE信号,应用程序可以处理该信号,也可以忽略(默认动作则是应用程序终止)。

当管道中父进程没有写入数据时,而子进程使用read读管道中的数据时,read将一直阻塞,等待管道中写入数据

NAME : pipe ,  pipe2   ----- creat pipe
pip1.c

/*
        parent process : write pipe
        child  process : read  pipe
*/
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>

int main(void)
{
      int fd[2];
      int pid;
      if(pipe(fd) == -1)         //创建管道,创建错误返回-1,正确返回0
              perror("pipe");
      pid = fork();      // 创建进程
      if(pid > 0)  // 父进程         fd[0] 读 fd[1] 写
      {
            close(fd[0]);               //关闭管道读端
            sleep(5);
            write(fd[1], "ab", 2);      //写管道写数据(2为写入字符为两个字节大小)
            while(1);
      }
      else if(pid == 0)    // 子进程复制了管道
      {   
            char ch[2];   // 用于接收数据
            printf("child process is waiting for data: \n");
            close(fd[1]);         // 关闭管道写端
            read(fd[0], ch, 2);   // 存放到ch,2个字节大小
            printf("read from pipe: %s\n", ch);
      } 
      return 0;
}

读函数read 
ssize_t read ( int  fd, void   * buf,  size_t nbyte) 
read函数是负责从fd中读取内容. 成功时,read返回实际所读的字节数,如果返回的值是0,表示已经读到文件的结束了.
小于0表示出现了错误. 如果错误为EINTR说明读是由中断引起的,  如果是ECONNREST表示网络连接出了问题.

写函数write 
ssize_t write ( int  fd, const   void   * buf, size_t nbytes) 
write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节数.失败时返回-1. 并设置errno变量. 在网络程序中,当我们向套接字文件描述符写时有俩种可能.  
1)write的返回值大于0,表示写了部分或者是全部的数据.  
2)返回的值小于0,此时出现了错误.我们要根据错误类型来处理.  如果错误为EINTR表示在写的时候出现了中断错误.  
如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接).

2. 无名管道大小测试

管道的容量是有限的:管道的存储能力为65536字节

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

int main(void)
{
      pid_t pid;
      int fd[2];
      
      if(pipe(fd) == -1)
               perror("pipe");
      pid = fork();       // 创建子进程
      
      if(pid == 0)        // child process : write to the pipe
      {                   // 子进程不断写入管道*
           char ch = '*';
           int n = 0;

           close(fd[0]); // 关闭读端
           while(1)
           {               // 地址
               write(fd[1], &ch, 1);
               printf("count =  %d\n", ++n);
           }
      }  
      else if(pid > 0)         //parent process : wait until child process is over
      {
           waitpid(pid, NULL, 0); // 父进程等待子进程结束
      }
}

结果:。。。count = 65536 (字节)

3. 无名管道练习

子进程把键盘输入的数据写入管道,父进程读取数据

pip_w&r_.c

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

int main(void)
{
      pid_t pid;
      int fd[2];
      
      if(pipe(fd) == -1)
               perror("pipe");
      pid = fork();       //创建子进程
      
      if(pid == 0)             //child process : write to the pipe
      { 
           char tmp[100];  // 用于存储数据

           close(fd[0]);
           while(1)
           {
               printf("parent processs is waiting for you message\n");
               scanf("%s", tmp)                 // 等待键盘输入数据
               write(fd[1], tmp, sizeof(tmp));  // 将数据写入管道     
           }
      }  
      else if(pid > 0)         //parent process : 读取子进程的输入
      {
           char tmp[100];
           close(fd[1]);

           while(1)  // 只要管道有内容就会读取
           {
                 read(fd[0], tmp, sizeof(tmp));            //读取
                 printf("read from pipe : %s\n", tmp);    // 输出
           }
      }
}

4. 无名管道_两个管道双向传输 

two_pip.c

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/wait.h>
#include <string.h>
#include <ctype.h>
int main(void)
{
      pid_t pid;
      int fd[2];
      int fd2[2];    // 两个描述符
      
      // 判断是否成功
      if(pipe(fd) == -1)
               perror("pipe");
      if(pipe(fd2) == -1)
               perror("pipe");
      
      pid = fork();       //创建子进程
      
      if(pid == 0)        //child process :
      { 
           char tmp[100];

           close(fd[1]);    //关闭fd管道的写   
           close(fd2[0]);   //关闭fd管道的读
           
           while(1)
           {
                memset(tmp, '\0', sizeof(tmp));     // 对tmp做清空处理
                read(fd[0], tmp, sizeof(tmp));      // 子进程等待去读管道
                
                for( i = 0; i < sizeof(tmp); i++)   // 有数据时,read由阻塞变为非阻塞
                         tmp[i] = toupper(tmp[i]);  // 将所有的字符变为大写toupper
                write(fd2[1], tmp, sizeof(tmp));    // 写入管道fd2
           }
      }  
      else if(pid > 0)         //parent process : 读取子进程的输入
      {
           char tmp[100];
           close(fd[0]);
           close(fd[1]);
           while(1)
           { 
                 memset(tmp, '\0', sizeof(tmp));
                 gets(tmp);                      //父进程由键盘输出
                 write(fd[1], tmp, sizeof(tmp)); //向子进程写入小写
                 
                 memset(tmp, '\0', sizeof(tmp));
                 read(fd2[0], tmp, sizeof(tmp));     //读取子进程传过来的大写
                 printf("after change : %s \n", tmp)
           }
      }
}

memset 函数

 代码示例

#include <stdio.h>
#include <string.h>
 
int main ()
{
   char str[50];
 
   strcpy(str,"This is string.h library function");
   puts(str);
 
   memset(str,'$',7);
   puts(str);
   
   return(0);
}

//  This is string.h library function
//  $$$$$$$ string.h library function

5. 有名(命名)管道 

  • 匿名管道,由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道(FIFO),也叫命名管道、FIFO 文件。
  • 有名管道(FIFO)不同于匿名管道之处在于它提供了一个路径名与之关联,以 FIFO 的文件形式存在于文件系统中,并且其打开方式与打开一个普通文件是一样的,这样即使与 FIFO 的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过 FIFO 相互通信,因此,通过 FIFO 不相关的进程也能交换数据。
  • 一旦打开了 FIFO,就能在它上面使用与操作匿名管道和其他文件的系统调用一样的 I/O 系统调用了(如read()、write() 和 close())。与管道一样,FIFO 也有一个写入端和读取端,并且从管道中读取数据的顺序与写入的顺序是一样的。FIFO 的名称也由此而来:先入先出
  • 有名管道(FIFO) 和匿名管道(pipe)有一些特点是相同的,不一样的地方在于:

      1. FIFO 在文件系统中作为一个特殊文件存在,但 FIFO 中的内容却存放在内存中。

      2. 当使用 FIFO 的进程退出后,FIFO 文件将继续保存在文件系统中以便以后使用。

      3. FIFO 有名字,不相关的进程可以通过打开有名管道进行通信。

非亲缘关系进程之间进行管道通信

NAME : mkfifo ,  mkfifoat  -  make a FIFO(先入先出) special  file(a named pipe)

模式(model):写权限4,读权限2,可执行权限1  (主要读写)

函数功能:创建一个FIFO文件,用于进程之间的通信。pathname就是路径名,mode是该文件的权限。返回值表示是否成功,返回0表示成功,返回-1表示失败,失败原因在errno中。(建立FIFO的时候,要求不存在这样的FIFO)。

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

// 函数功能:创建一个FIFO文件,用于进程之间的通信。pathname就是路径名,mode是该文件的权限。返回值表示是否成功,返回0表示成功,返回-1表示失败,失败原因在errno中。(建立FIFO的时候,要求不存在这样的FIFO)。

read_named_pipe.c

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

int main(void)
{
    int ret;
    int fd;
    char buf[100];

    ret = mkfifo("my_fifo", 666);     // 创建文件,权限
    if(ret != 0)
            perror("mkfifo");
    
    printf("prepare reading from the pip :\n");
    
    fd = open("my_fifo",  0_RDWR);
    if(fd == -1)
            perror("open");

    while(1)
    {
           memset(buf, '\0', sizeof(buf));
           read(fd, buf, sizeof(buf));
           printf("read from named pipe : %s\n", buf);
           sleep(1);
    }
    return 0; 
    
}

编译  :gcc read_named_pipe.c  -o  read

重新开一个终端:

write_named_pipe.c

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

int main(int argc, char *argv[])
{
    int fd;
    char buf[100];
    
    fd = open("my_fifo",  0_WRONLY);
    if(fd == -1)
            perror("open");

    if(argc == 1)
    {
          printf("please send something to the named pipe :\n");
          exit(EXIT_FAILURE);
    }

    strcpy(buf, argv[1]);
    write(fd, buf, sizeof(buf));
    printf("write to the pipe : %s\n", buf);
    return 0; 
    
}

编译:gcc  write_named_pipe.c  -o  write

          ./write hello morning  

总结

有名管道  (可以在任意两个进程之间通信)
无名管道(只能在父子进程之间进行通信)(有一个读端和一个写端,缺一不可)
    管道写端关闭,则读端返回值为0;
    管道读端关闭,则写端会产生异常(会收到信号SIGPIPE)
    管道为空, 那么读会阻塞
    管道写满, 那么写会阻塞

1.有名管道和无名管道的区别:有名管道可以在任意两个进程之间通信;无名管道只能在父子进程之间进行通信
2.写入管道的数据在哪里? 在内存中(不是在磁盘上)
3.管道的通信方式: 半双工(数据可以从a到b,也可以从b到a,但是某一时刻只能选择其中一个)
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值