Linux高级编程_30_管道

文章目录

管道

作用:

用于进程间通信

  • 信号只能携带少量数据(可以理解为不带数据)
  • 管道可以携带大量数据

分类:

  • 无名管道(管道) 作用于有血缘关系的进程中
  • 有名管道 作用 于 没有血缘关系的进程中

前置知识:

复制文件描述符

文件描述符有 : 0 标准输入 1 标准输出 2 标准错误

dup

作用: 复制已有文件描述符

c 复制代码
#include <unistd.h>
int dup(int oldfd);
参数:
	所需复制的文件描述符
返回值
	复制得到的文件描述符
功能:从文件描述符表中,寻找一个最小可能的文件描述符(通过返回值返回)作为
oldfd复制

示例:

c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
//  dup()  拷贝文件描述符
int main(int argc, char const *argv[])
{
    int fd = open("./test.txt",O_APPEND | O_RDWR | O_CREAT,0666);
    int new_fd = dup(fd);  //将久的文件描述符作为参数  来获得新文件描述符 
    write(new_fd,"hello",10);  //往new_fd这个文件描述符修饰的文件中写hello写入长度为6
    close(new_fd);
    close(fd);
    return 0;
}

示例2:

c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
    // fd = 3
    int fd = open("./test.txt", O_RDWR | O_APPEND);
    // 最小可用文件描述符1
    close(1);
    // nfd = 1
    int nfd = dup(fd);
    // write(1,"内容",长度);
    printf("nfd = %d\n", nfd);
    return 0;
}
dup2 【推荐使用】
c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
//   dup2()   拷贝文件描述符  【最常用】
int main(int argc, char const *argv[])
{
    int fd = open("./test.txt",O_APPEND | O_RDWR);
    int new_fd = 1;
    dup2(fd,new_fd);
    printf("new_fd = %d\n",new_fd);
    return 0;
}

无名管道

无名管道 又名 管道 开发实际中说管道一般就是说的无名管道

概述:

c 复制代码
作用于有血缘关系的进程中
属于:
	半双工
补充
	单工:指数据传输只支持数据在一个方向上传输
	双工:指二台通讯设备之间,允许有双向的资料传输
	全双工:允许二台设备间同时进行双向数据传输。一般的电话、手机就是全双工的系统,因为在讲话时同时也可以听到对方的声音。
	半双工:允许二台设备间进行双向数据传输,但不能同时进行。因此同一时间只允许一设备传送资料,若另一设备要传送资料,需等原来传送资料的设备传送完成后再处理。
	基于内存
			特点:
                   1,数据只能从管道的一端写入,从另一端读出。
                   2,写入管道中的数据遵循先入先出的规则。
                   3,管道所传送的数据是无格式的,这要求管道的读出方与写入方必须事先约定好数据的格式,如多少字节算一个消息等
                   4,管道在内存中对应一个缓冲区。不同的系统其大小不一定相同,liunx系统64kb
				  5,从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写更多的数据
				  6,管道没有名字,只能在具有公共祖先的进程之间使用。
				  7,基于内存
				  8,半双工
                       
  补充:
如管道可用于一个进程和其子孙进程之间的通信。第一个进程创建管道,然后创建子进程,接着子进程再创建第一个进程的孙子进程。
    管道通常用于两个兄弟进程之间的通信------它们的父进程创建了管道,并创建两个子进程。
    总结:在当前进程中创建管道

pipe函数

作用:

创建无名管道

语法:

c 复制代码
#include <unistd.h>
int pipe(int fd[2]);
参数:
	fd 为 int 型数组的首元素地址,其存放了管道的文件描述符 fd[0]、fd[1]。
	fd[0]为读而打开,fd[1]为写而打开管道。
		0读1写
返回值:
		成功:返回 0
		失败:返回-1

示例:

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <wait.h>
#include <sys/types.h>
int main(int argc, char const *argv[])
{
    // 让子进程A给子进程B发送消息
    // 1,创建无名管道
    int fd[2] = {0};
    pipe(fd);
    // 2,创建子进程
    int i = 0;
    for (i = 0; i < 2; i++)
    {
        int pid = fork();
        if (pid == 0)
        {
            break;
        }
    }
    // 3,分类
    if (i == 0)
    {
        // 子进程A:发送消息给子进程B
        // 因为子进程A是发送消息的,所有该进程中读的文件描述符无用,顾关闭
        // 0,读,1,写
        close(fd[0]);
        char *info = "你好,我是子进程A\n";
        write(fd[1], info, strlen(info));
        close(fd[1]);
    }
    else if (i == 1)
    {
        // 子进程B:接收子进程A发送的消息
        // 因为子进程B是接收消息的,所有其进程中写的文件描述符无用,顾关闭
        close(fd[1]);
        char buf[200] = {0};
        // 注意:读会阻塞当前进程,直到管道中有数据可以读取
        read(fd[0], buf, 200);
        printf("子进程B:%s", buf);
        close(fd[0]);
    }
    else if (i == 2)
    {
        // 当前进程:回收子进程
        while (1)
        {
            if (waitpid(-1, NULL, WNOHANG) == -1)
            {
                break;
            }
        }
    }
    return 0;
}

示例2:无读有写,可以写入

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <wait.h>
#include <sys/types.h>
int main(int argc, char const *argv[])
{
    // 让子进程A给子进程B发送消息
    // 1,创建无名管道
    int fd[2] = {0};
    pipe(fd);
    // 2,创建子进程
    int i = 0;
    for (i = 0; i < 2; i++)
    {
        int pid = fork();
        if (pid == 0)
        {
            break;
        }
    }
    // 3,分类
    if (i == 0)
    {
        // 子进程A:发送消息给子进程B
        // 因为子进程A是发送消息的,所有该进程中读的文件描述符无用,顾关闭
        // 0,读,1,写
        close(fd[0]);
        char *info = "你好,我是子进程A\n";
        write(fd[1], info, strlen(info));
        close(fd[1]);
    }
    else if (i == 1)
    {
    }
    else if (i == 2)
    {
        // 当前进程:回收子进程
        while (1)
        {
            if (waitpid(-1, NULL, WNOHANG) == -1)
            {
                break;
            }
        }
    }
    return 0;
}

示例3:有读无写,读端会阻塞

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <wait.h>
#include <sys/types.h>
int main(int argc, char const *argv[])
{
    // 让子进程A给子进程B发送消息
    // 1,创建无名管道
    int fd[2] = {0};
    pipe(fd);
    // 2,创建子进程
    int i = 0;
    for (i = 0; i < 2; i++)
    {
        int pid = fork();
        if (pid == 0)
        {
            break;
        }
    }
    // 3,分类
    if (i == 0)
    {
    }
    else if (i == 1)
    {
        // 子进程B:接收子进程A发送的消息
        // 因为子进程B是接收消息的,所有其进程中写的文件描述符无用,顾关闭
        close(fd[1]);
        char buf[200] = {0};
        // 注意:读会阻塞当前进程,直到管道中有数据可以读取
        read(fd[0], buf, 200);
        printf("子进程B:%s", buf);
        close(fd[0]);
    }
    else if (i == 2)
    {
        // 当前进程:回收子进程
        while (1)
        {
            if (waitpid(-1, NULL, WNOHANG) == -1)
            {
                break;
            }
        }
    }
    return 0;
}

示例4:缓存区大小测试与缓冲区内容已满继续写入会阻塞

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <wait.h>
#include <sys/types.h>
int main(int argc, char const *argv[])
{
    int fd[2] = {0};
    pipe(fd);
    int pid = fork();
    if (pid == 0)
    {
        // 子进程写
        close(fd[0]);
        int i = 0;
        while (1)
        {
            char buf[1024];
            write(fd[1], buf, 1024);
            i++;
            printf("已写入%dkb\n", i);
        }
    }
    else if (pid > 0)
    {
        wait(NULL);
    }
    return 0;
}

示例5:一个写入的信息只能被读取一次

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <wait.h>
#include <sys/types.h>
int main(int argc, char const *argv[])
{
    int fd[2];
    pipe(fd);
    int i = 0;
    for (i = 0; i < 2; i++)
    {
        int pid = fork();
        if (pid == 0)
        {
            break;
        }
    }
    if(i == 0)
    {
        //写1次
        close(fd[0]);
        char *buf = "写入的内容\n";
        write(fd[1],buf,strlen(buf));
        close(fd[1]);
    }
    else if(i == 1)
    {
        //读两次
        close(fd[1]);
        char buf[100] = {0};
        read(fd[0],buf,100);
        printf("第一次读取到的内容:%s\n",buf);
        read(fd[0],buf,100);
        printf("第二次读取到的内容:%s\n",buf);
        close(fd[0]);
    }
    else if(i == 2)
    {
        //回收子进程
        while(waitpid(-1,NULL,WNOHANG) != -1);
    }
    
    return 0;
}

示例6:当所有进程中读端关闭,写端将收到SIGPIPE的信号

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <wait.h>
#include <sys/types.h>
void fun()
{
    printf("收到SIGPIPE信号\n");
}
int main(int argc, char const *argv[])
{
    int fd[2];
    pipe(fd);
    int i = 0;
    for (i = 0; i < 2; i++)
    {
        int pid = fork();
        if (pid == 0)
        {
            break;
        }
    }
    if (i == 0)
    {
        signal(SIGPIPE,fun);
        //写入
        close(fd[0]);
        while(1)
        {
            char *buf = "hello\n";
            sleep(1);
            write(fd[1],buf,strlen(buf));
        }
        close(fd[1]);
    }
    else if(i == 1)
    {
        //读一次
        close(fd[1]);
        for (int j = 0; j < 5; j++)
        {
            char buf[1024] = {0};
            read(fd[0],buf,1024);
            printf("读取到的内容:%s\n",buf);
        }
        
        close(fd[0]);
        printf("读端关闭\n");
        _exit(0);
    }
    else if(i == 2)
    {
        //回收
        close(fd[0]);
        while(waitpid(-1,NULL,WNOHANG) != -1);
    }
    return 0;
}

实现: ps -A | grep bash

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
  //要求:使用代码实现 ps -A | grep bash
  int fd[2] = {0};  // 0 读  1 写
  pipe(fd);
  
  int pid = vfork();
  if (pid == 0)
  {
      close(fd[0]);
      //ps -A
      /*
          因为ps -A最终的结果会输出的控制台
          控制台输出对应的文件描述符为1
          此时将我们管道的写端复制文件描述符为1
          此时表示输出的文件描述1就是我们管道的写端
          此时ps -A程序执行完毕后,将其输出的内容写入到了我们的管道中
       */
      dup2(fd[1],1);
      execlp("ps","./ps","-A",NULL);
      close(fd[1]);
  }
  else if(pid > 0)
  {
      //grep bash
      close(fd[1]);
      /*
          grep bash是从标准输入中读取数据
          我们将标准输入改为我们管道的读端
          此时grep bash就是我们管道的读端读取数据
          管道读端的数据就是管道写段写入的数据
          即ps -A输出的内容
       */
      dup2(fd[0],0);
      execlp("grep","./grep","bash",NULL);
      close(fd[0]);
      wait(NULL);
  } 
  return 0;
}

有名管道:

概述

c 复制代码
又名:命名管道(FIFO)
特点:
	1、半双工,数据在同一时刻只能在一个方向上流动。
	2、写入 FIFO 中的数据遵循先入先出的规则。
	3、FIFO 所传送的数据是无格式的,这要求 FIFO 的读出方与写入方必须事先约定好数据的格式,如多少字节算一个消息等。
	4、FIFO 在文件系统中作为一个特殊的文件而存在,但 FIFO 中的内容却存放在内存中。
	5、管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。
	6、从 FIFO 读数据是一次性操作,数据一旦被读,它就从 FIFO 中被抛弃,释放空间以便写更多的数据。
	7、当使用 FIFO 的进程退出后,FIFO 文件将继续保存在文件系统中以便以后使用。
	8、FIFO 有名字,不相关的进程可以通过打开命名管道进行通信。

mkfifo 函数

作用:创建有名管道

语法

c 复制代码
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
	参数:
		pathname:文件名
		mode:文件操作模式,一般用0666(所有用户可读可写)
	返回值:
			成功:0
			失败:-1,一般失败是因为存在与pathname名相同的文件

读写特点

1、open打开管道 不指定 O_NONBLOCK (阻塞)

1、open 以只读方式打开 FIFO 时,要阻塞到某个进程为写而打开此 FIFO

2、open 以只写方式打开 FIFO 时,要阻塞到某个进程为读而打开此 FIFO。

3、open 以只读、只写方式打开 FIFO 时会阻塞,调用 read 函数从 FIFO 里读数据时 read 也会阻塞。

4、通信过程中若写进程先退出了,则调用 read 函数从 FIFO 里读数据时不阻塞;若写进程又重新运行,则调用

​ read 函数从 FIFO 里读数据时又恢复阻塞。

5、通信过程中,读进程退出后,写进程向命名管道内写数据时,写进程也会(收到SIGPIPE 信号)退出。

6、调用 write 函数向 FIFO 里写数据,当缓冲区已满时 write 也会阻塞。

2、open打开管道 指定O_NONBLOCK (非阻塞)

1、先以只读方式打开:如果没有进程,已经为写而打开一个 FIFO, 只读 open 成功,并且 open 不阻塞。

2、先以只写方 式打开:如果没有进程,已经为读而打开一个 FIFO,只写 open 将出错返回-1。

3、read、write 读写命名管道中读数据时不阻塞。

4、通信过程中,读进程退出后, 写进程向命名管道内写数据时,写进程也会(收到SIGPIPE 信号)退出。

3、 注意:open函数以可读可写方式打开FIFO 文件时的特点:

1、open不阻塞。

2、调用read函数从FIFO里读数据时read会阻塞。

3、调用write函数向FIFO里写数据,当缓冲区已满时write也会阻塞

实现有名管道的聊天

c 复制代码
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <wait.h>
// 实现 张十一 与 张优秀 之间的通信
int main(int argc, char const *argv[])
{
    // 1.创建xxz给zy发信息的管道
    mkfifo("./xxz_to_zy", 0666);
    // 2.创建zy给xxz发信息的管道
    mkfifo("./zy_to_xxz", 0666);
    // 3.创建子进程 一个用来发信息 一个用来收信息
    int i = 0;
    for (i = 0; i < 2; i++)
    {
        int pid = fork();
        if (pid == 0)
        {
            break;
        }
    }
    //子进程1 用来发信息
    if (i == 0)
    {   //文件描述符  0标准输入   1标准输出  2标准错误  
        int fd = 0;
        //如果有XXZ这个宏,当前用户就是 张优秀     
        #ifdef XXZ          
            fd = open("./xxz_to_zy", O_RDWR); //此时是张优秀给张十一发消息  以可读、可写的方式打开
        #else
            fd = open("./zy_to_xxz", O_RDWR); //此时是张十一给张优秀发消息     
        #endif
        //开始发信息   
        while (1)
        {
            // 用来存放信息 大小为300字节
            char info[300] = {0};  
            // 从标准输入中获取信息   最多获取300个字节
            fgets(info, 300, stdin);
            //打印展示 
            printf("我:%s", info);
            //清除脏数据 也就是按的回车上去的\n
            info[strlen(info) - 1] = 0;        
            //写进文件 消息
            write(fd,info,strlen(info));
            //给定结束的条件  使用 比较字符串是否相等
            if(strcmp(info,"88") == 0 )
            {
                break;
            }
        }
        close(fd);   //关闭文件描述符
    }
    else if (i == 1)//子进程2 用来收信息
    {
        int fd = 0; 
        //接收信息  
        #ifdef XXZ
            fd = open("./zy_to_xxz",O_RDWR);   //读取 张十一发来的信息
        #else
            fd = open("./xxz_to_zy",O_RDWR);   //读取 张优秀发来的信息
        #endif
        //开始读取信息
        while(1)
        {
            // 用来存放信息 大小为300字节
            char info[300] = {0};
            read(fd,info,300);
            //再定义宏用来决定谁来说话
            #ifdef XXZ
                printf("张优秀说:%s\n",info);
            #else
                printf("张十一说:%s\n",info);
            #endif
            //给定结束的条件  使用 比较字符串是否相等    这里是因为传过来的数据本身就是剔除了\n的  所以这里直接比较字符串是否相等即可
            if(strcmp(info,"88") == 0 )
            {   
                break;
            }
        }
        close(fd);   //关闭文件描述符
    }
    else if (i == 2)// 当前是父进程 用来回收子进程
    {
        while (waitpid(-1, NULL, WNOHANG) != -1);
    }
    return 0;
}

无名管道与有名管道的区别?

  • 1 无名管道 只能在有血缘关系的进程之间通讯

有名管道 既可以在有血缘关系的进程间通讯也能在无血缘关系的进程间通信

  • 2 无名管道没有对应的文件 有名管道有对应的文件
  • 3 但是有名管道中对应的文件不会存储其管道中的数据 数据是存储在内存中
相关推荐
乌恩大侠6 小时前
【Xcode Command Line Tools】安装指南
macos·fpga开发·c
一丝晨光7 小时前
C++、Ruby和JavaScript
java·开发语言·javascript·c++·python·c·ruby
zaim116 小时前
计算机的错误计算(一百一十四)
java·c++·python·rust·go·c·多项式
想躺平的做题家1 天前
Linux高级编程_29_信号
开发语言·c·信号
一丝晨光2 天前
不同语言的注释和数组
java·开发语言·javascript·c++·c·注释·数组
想躺平的做题家2 天前
Linux高级编程_27_系统调用
linux·c
冷白白3 天前
【C++】单例模式
开发语言·c++·单例模式·c
笑非不退3 天前
VSCode 中配置 C/C++ 环境的步骤
c++·c
一丝晨光4 天前
void类型
java·开发语言·javascript·c++·c#·go·c