Linux 管道

管道的概念

管道是一种进程通信的方式,管道是一个载体,用于传输数据的通道

管道的特点

1.管道的读写端是固定的

2.管道是半双工的同一时间只能执行一侧读一侧写

3.管道是处于内核中的一种特殊的文件,占用一定的内存空间(管道就是一个文件,存在于内存中,作为数据的缓冲区)

管道的分类

1.无名管道(匿名管道) --> 有亲属关系的进程通信

2.有名管道(命名管道) --> 没有亲属关系的进程通信

无名管道

特性

半双工,数据在同一时刻只能在一个方向上流动

数据只能从管道的一端写入,从另一端读出

写入管道中的数据遵循先入先出的规则(FIFO)(类似于队列)

管道所传输的数据是无格式的,这要求管道的读出方式必须是先约定好数据的格式,如多少字节算一个消息

管道是一个文件,只不过不属于某个系统,只存在于内存中

管道在内存中对应一个缓冲区,不同的系统其大小不一定相同

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

管道没有名字,只能在具有公共祖先的进程之间使用

获取管道文件描述符函数

#include  <unistd.h>
int pipe(int filedes[2]);
功能:经由参数 filedes 返回两个文件描述符
参数:
    filedes 为 int 类型数组的首地址,其存放了管道的文件描述符fd[0], fd[1]
    filedes[0]为读而打开,filedes[1]为写而打开管道
    filedes[0]是输出,feledes[1]是输入
返回值:
    成功:返回0
    失败:返回-1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <wait.h>
​
#define BUF_SIZE 1024
​
int main(int argc, char const *argv[])
{
    //创建pipe函数所需要的参数
    int fd[2] = {0};
    //检测 pipe 函数的返回值变量
    int ret = 0;
    //创建一个变量用来接收fork函数的返回值
    pid_t  pid;
    //创建一个数组用来作为读取函数的数据缓冲区
    char rev_buf[BUF_SIZE] = {0};
    //创建管道
    ret = pipe(fd);
    
    //判断管道是否创建成功
    if(ret == 0)
    {
        printf("pipe is ok\n");
    }
    
    //调用父子进程函数
    pid = fork();
    //通过返回值判断父子进程是否创建成功
    if(pid < 0)
    {
        perror("fork error :");
    }
    
    //给子进程添加功能实现
    if(pid == 0)
    {
        //读操作,函数参数为文件描述符,存放读取数据的内存首地址,读取到的字节个数
        if(read(fd[0], rev_buf, BUF_SIZE) < 0)
        {
            perror("read error :");
            exit(-1);
        }
        printf("rev_buf-->%s\n",rev_buf);
      
       exit(1);
    }
    //给父进程添加功能实现
    else
    {
        close(fd[0]);
        //写操作,函数参数为文件描述符,写入的数据,写入数据的长度
        if( write(fd[1],"hello world 2303",strlen("hello world 2303"))<0)
        {
             perror("write error:");
             exit(-1);
        }
        wait(NULL);
    }
    
    return 0;
}
​

实现原理

    利用无名管道实现进程间的通信,都是父进程创建无名管道,然后再创建子进程,子进程继承父进程的无名管道的文件描述符,然后父子进程通过读写无名管道实现通信
    从管道中读取数据的特点:
    1.默认用 read 函数从管道中读取数据是阻塞的
    2.调用 write 函数向管道里写数据,当缓冲区已满时 write 也会阻塞
    3.通信过程中,读端口全部关闭后,写进程向管道内写数据时,写进程会(收到 SIGPIPE 信号)退出
管道中的数据一旦读出,在管道内部就不存在了

管道的阻塞函数

#include  <unistd.h>
#include <fcntl.h>
int fcntl(文件描述符,设置文件描述符的状态,0);
功能:将文件描述符设置成阻塞状态
参数:
    文件描述符
    F_SETFL 设置文件描述符的状态
    0 设置成阻塞状态
返回值:
    成功:返回0
    失败:返回-1
​
int fcntl(fd, F_SETFL, O_NONBLOCK);
功能:将文件描述符设置成非阻塞状态
参数:
    fd   文件描述符
    F_SETFL 设置文件描述符的状态
    O_NONBLOCK 设置成非阻塞状态
返回值:
    成功:返回0
    失败:返回-1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
​
#define BUF_SIZE  1024
​
int main(int argc, char const *argv[])
{
    //创建pipe函数所需要的参
    int fd[2] = {0};
    //检测 pipe 函数的返回值变量
    int ret = 0;
    //创建一个变量用来接收fork函数的返回值
    pid_t pid = 0;
    //创建一个数组用来作为读取函数的数据缓冲区
    char rev_buf[BUF_SIZE] = {0};
    //创建管道
    ret = pipe(fd);
    //判断管道是否创建成功
    if(ret == 0)
    {
        printf("pipe is ok\n");
    }
    //调用父子进程函数
    pid = fork();
    //通过返回值判断父子进程是否创建成功
    if(pid < 0)
    {
        perror("fork error");
    }
    //给子进程添加功能实现
    if(pid == 0)
    {
        //改变管道读取数据文件描述符的状态为阻塞
        fcntl(fd[0], F_SETFL, 0);
       //fcntl(fd[0], F_SETFL, O_NONBLOCK);
        while(1)
        {
            //关闭 写管道文件描述符
            close(fd[1]);
            //读操作,函数参数为文件描述符,存放读取数据的内存首地址,读取到的字节个数
            if(read(fd[0], rev_buf, BUF_SIZE) < 0)
            {
                perror("read error");
                exit(-1);
            }
            printf("rev_buf -->%s\n", rev_buf);
        }
        //因为读取函数阻塞,无法进行到退出操作
        exit(1);
    }
    else
    {
        //挂起 3 s
        sleep(3);
        //关闭 读管道文件描述符
        close(fd[0]);
            //写操作,函数参数为文件描述符,写入的数据,写入数据的长度
            if(write(fd[1], "iotming", strlen("iotming"))< 0)
            {
                perror("write error");
                exit(-1);
            }
        //资源回收函数
        wait(NULL);
    }
    return 0;
}
​

文件描述符

  1. 文件描述符是非负整数,是文件的标识

  2. 用户使用文件描述符 (file descriptor) 来访问文件

  3. 利用 open 打开一个文件时,内核会返回一个文件描述符

  4. 每个进程都有一个文件描述符的表,进程刚被创建时,标准输入,标准输出,标准错误输出设备文件被打开,对应的文件描述符 0,1,2记录在表中

  5. 在进程中打开其他文件时,系统会返回文件描述符表中最小可用的文件描述符,并将此文件描述符记录在表中

0、1、2文件描述符

STDIN_FILENO --标准输入 0 scanf
STDOUT_FILENO --标准输出 1 printf
STDERR_FILENO --标准错误 2 error

文件描述符的复制函数

#include <unistd.h>
int dup(int oldfd);
功能:
    复制 oldfd 文件描述符,并分配一个新的文件描述符,新的文件描述符是调用进程文件描述符表中最小可用的文件描述符
参数:
    要复制的文件描述符 oldfd
返回值:
    成功:新文件描述符
    失败:返回 -1,错误代码存于 errno 中
#include <unistd.h>
int dup2(int oldfd, int newfd)
功能:
    复制一份打开的文件描述符 oldfd,并分配新的文件描述符 newfd, newfd 也标识 oldfd 所标识的文件
(newfd 是小于文件描述符最大允许值的非负整数,如果 newfd 是一个已经打开的文件描述符,则首先关闭该文件,然后再复制)
参数:
    要复制的文件描述符 oldfd
    分配的新的文件描述符 newfd
返回值:
    成功: 返回 newfd
    失败: 返回-1, 错误代码存于 errno 中
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>

#define BUF_SIZE  1024

int main(int argc, char const *argv[])
{
    //创建pipe函数所需要的参
    int fd[2] = {0};
    //检测 pipe 函数的返回值变量
    int ret = 0;
    //创建一个变量用来接收fork函数的返回值
    pid_t pid = 0;
    //创建一个数组用来作为读取函数的数据缓冲区
    char rev_buf[BUF_SIZE] = {0};
	//存放新的文件描述符
    int new_fd = 0;
    printf("write test\n");
	//写入数据函数,在文件标识符 1 中写入数据
    write(1, "hello world\n", strlen("hello world\n"));
	//复制新的文件描述符
    //new_fd = dup(STDOUT_FILENO);
    //用dup2函数将文件标识符 1 复制到文件标识符 6 中
    new_fd = dup2(STDOUT_FILENO, 6);

    printf("new_fd -->%d\n", new_fd);
    //将 数据写入新的文件标识符中
    write(new_fd ,"hello world111\n", strlen("hello world111\n"));
}

使用 dup 或者 dup2 复制文件描述符后,新文件描述符和旧文件描述符指向同一个文件,共享文件锁定,读写位置和各项权限

当关闭新的文件描述符时,通过旧文件描述符仍可操作文件

当关闭旧的文件描述时,通过新的文件描述符仍可可操作文件

有名管道

概念

命名管道主要是用于没有亲属关系的进程通信,有名管道需要我们自己创建当进程退出后,有名管道依然会存在于我们的文件系统中,有名管道必须是有名字了,后续可以通过名字进行通信

特性
  1. 半双工,数据在同一时刻只能在一个方向上流动

  2. 写入 FIFO 中的数据遵循先入先出的规则

  3. FIFO 所传送的数据是无格式的,这要求 FIFO 的读出方与写入方必须事先约定好数据的格式,如多少字节算一个消息等

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

  5. 管道在内存中对应一个缓冲区,不同的系统其大小不一定相同

  6. 从 FIFO 读数据是一次性操作, 数据一旦被读, 它就从 FIFO 中被抛弃, 释放空间以便写更多的数据

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

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

有名管道中可以很好地解决在无关进程间数据交换的要求,并且由于它们是存在于文件系统中的,这也提供了一种比匿名管道更持久稳定的通信办法。有名管道在一些专业书籍中叫做命名管道,它的特点

1.可以是无关联的进程通过 fifo 文件描述符进行数据传递

2.单向传输有一个写入端和一个读出端, 操作方式和无名管道相同

创建命名管道函数

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数:
	pathname: FIFO 的路径名+文件名;	
	mode : mode_t 类型的权限描述符
返回值:
	成功: 返回0
    失败: 如果文件已经存在, 则会出错且非返回 -1

有名管道使用步骤

  1. 使用 mkfifo() 创建 fifo 文件描述符

  2. 打开管道文件描述符

  3. 通过读写文件描述符进行单向数据传输

写入通道数据操作

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

#define BUF_SIZE  1024

int main(int argc, char const *argv[])
{
    //创建pipe函数所需要的参
    int fd[2] = {0};
    //检测 pipe 函数的返回值变量
    int ret = 0;
    //创建一个变量用来接收fork函数的返回值
    pid_t pid = 0;
    //创建一个数组用来作为读取函数的数据缓冲区
    char rev_buf[BUF_SIZE] = {0};
	//存放新的文件描述符
    int new_fd = 0;
    //创建命名管道参数为管道名跟读写权限
    ret = mkfifo("fifo_test", 0666);
    //判断命名管道是否创建成功
    if(ret == 0)
    {
        printf("mkfifo is ok\n");
    }
    else 
    {
        exit(-1);
    }
	//打开创建好的管道并用选择读写模式
    new_fd = open("fifo_test", O_RDWR);
    //打印成功返回的文件描述符
    printf("new_fd --> %d\n", new_fd);
	//给管道写入数据
    ret = write(new_fd, "hello world\n", strlen("hello world\n"));

    if (ret < 0)
    {
        perror("write error");
        exit(-2);
    }

    close(new_fd);

}

读取管道数据操作

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

#define BUF_SIZE  1024

int main(int argc, char const *argv[])
{
    //创建pipe函数所需要的参
    int fd[2] = {0};
    //检测 pipe 函数的返回值变量
    int ret = 0;
    //创建一个变量用来接收fork函数的返回值
    pid_t pid = 0;
    //创建一个数组用来作为读取函数的数据缓冲区
    char rev_buf[BUF_SIZE] = {0};
	//存放新的文件描述符
    int new_fd = 0;
    //创建命名管道参数为管道名跟读写权限
    ret = mkfifo("fifo_test", 0666);
    //判断命名管道是否创建成功
    if(ret == 0)
    {
        printf("mkfifo is ok\n");
    }
    else 
    {
        exit(-1);
    }
	//打开创建好的管道并用选择读取模式
    new_fd = open("fifo_test", O_RDONLY);
    //打印成功返回的文件描述符
    printf("new_fd --> %d\n", new_fd);

    //ret = write(new_fd, "hello world\n", strlen("hello world\n"));
    //读取 new_fd 文件标识符中的数据,存放到rev_buf数组中,读取数据的最大长度
    ret = read(new_fd, rev_buf, BUF_SIZE);
    if (ret < 0)
    {
        perror("read error");
        exit(-2);
    }
	//打印读取到的数据
    printf("rev_buf-->%s\n", rev_buf);
    close(new_fd);

}

敬请期待下集!!! 

  • 16
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

iot_Ming

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值