Linux 管道

本文详细介绍了管道在进程通信中的作用,包括管道的特点、分类(无名管道和有名管道)、使用方法(如pipe函数、fork、read/write操作),以及如何处理阻塞和非阻塞情况。还涵盖了创建和使用命名管道(FIFO)的步骤和注意事项。

管道的概念

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

管道的特点

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);

}

敬请期待下集!!! 

### Linux 管道使用教程 #### 一、基本概念 管道Linux 中一种重要的进程间通信机制,能够将一个命令的标准输出作为下一个命令的标准输入。这种特性使得多个简单工具组合起来完成复杂任务成为可能[^1]。 #### 二、无名管道 无名管道主要用于有亲缘关系的进程间的通讯,即父子进程或者兄弟进程之间。其特点是单向传输数据,并且只能由一对进程访问。创建无名管道通常通过 `pipe()` 函数实现,在调用此函数前建立好管道,则 fork 后子进程中也会拥有该管道副本并可对其进行操作[^4]。 ```c #include <unistd.h> int pipe(int pipefd[2]); // 返回值:成功返回0;失败返回-1 ``` 当执行上述代码后会得到两个文件描述符组成的数组`pipefd[]`,其中第一个元素代表读取端而第二个则是写入端。对于父/子进程来说可以根据需求关闭不需要的那一端来确保不会发生死锁现象。 #### 三、有名管道(FIFO) 相比起无名管道,FIFO 不仅可以在不相关联的进程间传递消息,而且支持全双工模式(尽管大多数情况下还是采用半双工的方式工作)[^3]。要创建 FIFO 文件需借助 mkfifo 命令或是在程序内部利用相应 API 完成: ```bash mkfifo my_fifo_file ``` 之后就可以像对待普通文件一样打开它来进行读写了: ```c #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int fd = open("my_fifo_file", O_WRONLY); write(fd,"hello world\n",strlen("hello world\n")); close(fd); /* 或者 */ int fd = open("my_fifo_file",O_RDONLY); char buf[80]; read(fd,buf,sizeof(buf)); printf("%s",buf); close(fd); ``` #### 四、应用实例 - 统计当前系统中的进程数量 这里给出一个简单的例子展示如何运用 shell 内置功能配合管道达成特定目的——计算正在运行着多少个进程: ```shell ps aux | wc -l ``` 这条指令先列出所有活动进程(`ps aux`)再将其交给单词计数工具处理(`wc -l`),最终得出总数目。 #### 五、注意事项 虽然管道提供了便捷高效的 IPC 方案之一,但在某些场景下或许 TCP/IP 协议栈更为合适,尤其是在跨主机甚至互联网级别的分布式环境中[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

iot_Ming

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

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

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

打赏作者

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

抵扣说明:

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

余额充值