管道 --(linux系统编程)

本文详细介绍了管道(Pipe)和FIFO(命名管道)在进程间通信中的应用,包括匿名管道的单工和半双工使用,以及FIFO的创建与打开。通过示例代码展示了如何进行读写操作,并探讨了它们的特点和适用场景,如父子进程间的单向和双向通信。此外,还提到了文件描述符的概念及其在Linux系统中的管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、简介

1.1 分类:

  • 匿名管道
  • FIFO管道/命名管道

1.2 查看管道命令

  • man 7 pipe

二、匿名管道

2.1 单工管道

程序进程与shell命令行进程单项通信。

1. 打开管道

FILE* popen(const char *command(命令行字符串),const char* open_mode(“r”只读、“w”只写));

  • 返回值:
NULLNULL
文件描述符打开失败

2. 读取

size_t fread(void* buffer(接收数据的内存地址), size_t size(读取的每个数据项的字节数), size_t count(数据项个数), FILE* stream(输入流));

  • 返回值
>count正数
出错读取的数据项个数

3. 写入

size_t fwrite(const void* buffer(写入数据的内存地址),size_t size(写入数据项的字节数,size_t count(写入数据项的个数),FILE* stream(目标文件指针));

  • 返回值
>ocunt正数
出错写入的数据项个数

4. 关闭管道

int pclose(FILE* stream(文件描述符));

  • 返回值
-10
失败成功

示例!!!

#include<iostream>
#include<unistd.h>
using namespace std;

//打开管道 popen("命令","w")
//读fread()/写fwrite()
//关闭管道 pclose()

int main(){
#ifdef WPIPE
       //管道写操作命令必须从终端获取数据 scanf()/cin
       FILE* f=popen("wc","w");
       if(NULL==f){
          perror("popen error");
          return EXIT_FAILURE;
       }
       string line;
       getline(cin,line);
       int n=fwrite(line.c_str(),sizeof(char),line.size(),f);
       if(n>line.size(){
          perror("fwrite error");
          return EXIT_FAILURE;
        }
        pclose(f);
#endif //WPIPE

        FILE* f = popen("pstree","r");
        if(NULL==f){
            perror("popen error");
            return EXIT_FAILURE;
         }
         char buffer[1024]={0};
         fread(buffer,sizeof(char),1024,f);
         cout<<buffer<<endl;
         pclose(f);
         
         return EXIT_SUCCESS;
         }

本质

  • 启动shell和命令两个进程,从命令进程中读/写文件流。
  • 解决exec和system无法返回输出数据问题

特点

  • 方便使用系统自带功能,并且可以执行比较复杂Shell
  • 默认启动两个进程,效率较低。

管道和文件读写对比

操作管道文件
打开popen()fopen()
关闭pclose()fclose()

2.2 半双工管道

1. 创建管道int pipe(int filedes[2]); 其中filedes[0]为读,filedes[1]为写

  • 返回值
-10
失败成功

2. 读取ssize_t read(int fd文件描述符, const void* buf读取数据的内存单元, size_t count)

  • 返回值
-10正数
出错无数据读取的字节数

3. 写入ssize_t write(int fd文件描述符, void* buf写入数据的内存单元, size_t count写入文件指定的字节数)

-1正数
出错写入的字节数

4.控制int fcntl(int fd文件描述符, int cmd( F_GETFL:获取文件描述符状态;F_SETFL:设置文件描述符状态; ), long arg ( O_NONBLOCK:非阻塞; O_BLOCK:阻塞))

把文件描述符改为非阻塞的fcntl(filedes,F_SETFL,O_NONBLOCK);

5. 关闭管道close(filedes)

示例!!!

#include <iostream>
#include <unistd.h>
#include <fcntl.h> //fcntl()
using namespace std;
 
#define BUFSIZE 4
 
int main(){
    int fds[2];
    pipe(fds); // fds[0]读取管道 fds[1]写入管道
    fcntl(fds[0],F_SETFL,O_NONBLOCK);
    //while(true){
    if(0!=fork()){
        for(;;){
        cout << "parent [" << getpid() << "] send:"; 
        string str;
        cin >> str;
        write(fds[1],str.c_str(),str.size());
        usleep(100000);
        }
    }else{
        for(;;){
        char buffer[BUFSIZE] = {0};
        int n = read(fds[0],buffer,BUFSIZE);
        if(-1==n){
            usleep(10000);
        }else{
            cout << "child [" << getpid() << "] recv:" << buffer << endl;
        }
        }
    }
    close(fds[0]);
    close(fds[1]);
}
#include <iostream>
#include <algorithm>
#include <cstring>
#include <sstream>
#include <unistd.h>
#include <fcntl.h>
using namespace std;
 
#define BUFSIZE 4
 
int main(){
    int fds[2];
    int fds2[2];
    pipe(fds); // fds[0]读取管道 fds[1]写入管道
    pipe(fds2); // fds[0]读取管道 fds[1]写入管道
    fcntl(fds[0],F_SETFL,O_NONBLOCK);
    fcntl(fds2[0],F_SETFL,O_NONBLOCK);
    //while(true){
    if(0!=fork()){
        close(fds[0]);
        close(fds2[1]);
        for(;;){
           cout << "parent [" << getpid() << "] send:"; 
           string str;
           getline(cin,str);
           write(fds[1],str.c_str(),str.size()+1);
 
           int n;
           ostringstream oss;
           do{
             char buffer[BUFSIZE] = {0};
             n = read(fds2[0],buffer,BUFSIZE);
             oss << buffer;
           }while(-1!=n || oss.str().empty());
           cout << "parent [" << getpid() << "] recv:" << oss.str() << endl;
        }
    }else{
        close(fds[1]);
        close(fds2[0]);
        for(;;){
          ostringstream oss;
          int n;
          do{
            char buffer[BUFSIZE] = {0};
            n = read(fds[0],buffer,BUFSIZE);
            if(-1 != n) oss << buffer;
          }while(-1!=n || oss.str().empty());
 
          string str = oss.str();
          cout << "child [" << getpid() << "] recv:" << str << endl;
 
          reverse(str.begin(),str.end());
          write(fds2[1],str.c_str(),str.size()+1);
         
          // 在C/C++字符串通信注意以下两点
          // 1. 接收字符串的buffer要清空
          // 2. 字符串后的\0是否传输。
          // 3. buffer有时要循环使用
        }
    }
    close(fds[0]);
    close(fds[1]);
    close(fds2[0]);
    close(fds2[1]);
}

三、 FIFO管道/命名管道

1. 创建命名管道int mkfifo(pathname文件路径,文件必须不存在, mode模式(如0644))

  • 返回值
0非零
成功失败
注:
  1. 管道文件通常在/tmp目录下创建。
  2. 管道文件大小通常是0
示例!!!
#include <stdio.h>
#include <unistd.h>

int main(){
    if(-1 == mkfifo("/tmp/test",0644)){
        perror("mkfifo error");
        return 1;
    }
}

2. 打开FIFO文件 int open(const char* path文件路径, int mode模式);

  • mode:
  1. O_RDONLY 阻塞只读
  2. O_RDONLY | O_NONBLOCK 非阻塞只读
  3. O_WRONLY 阻塞只写
  4. O_WRONLY | O_NONBLOCK 非阻塞只写

返回值

-1其他
失败文件描述符

示例!!!

  • 阻塞读
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
using namespace std;
 
int main(){
    int fd = open("/tmp/fifo",O_RDONLY);
    char buffer[BUFSIZ] = {0};
    read(fd,buffer,BUFSIZ);
    cout << buffer << endl;
    close(fd);
}
  • 阻塞写
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
using namespace std;
 
int main(){
    string str;
    getline(cin,str);
    int fd = open("/tmp/fifo",O_WRONLY);
    write(fd,str.c_str(),str.size()+1);
    close(fd);
}
  • 写非阻塞
    注:
  1. 只需要在open()添加O_NONBLOCK
  2. 写open()非阻塞,读open()阻塞的情况。读open()需要先执行,否则,写open()会出现No such device or address。
#include <iostream>
#include <fcntl.h> // open() O_WRONLY O_NONBLOCK
#include <unistd.h>
using namespace std;

int main(){
    // 打开命名管道
    int fd = open("/tmp/fifo",O_WRONLY|O_NONBLOCK);
    if(-1 == fd){
        perror("open error");
        return EXIT_FAILURE;
    }else{
        cout << "filo open ok" << endl;
    }
    
    // 读取字符串
    string str;
    getline(cin,str);
    
    // 写入管道
    write(fd,str.c_str(),str.size()+1);
    
    // 关闭管道
    close(fd);
    return EXIT_SUCCESS;
}
  • 读非阻塞

注:

1.只需要在open()添加O_NONBLOCK
2.写open()阻塞,读open()非阻塞的情况。读read()需要处理写open()未执行(read()返回0)和读不到数据(写open()打开但是没有写数据,read()返回-1)的情况。

#include <iostream>
#include <unistd.h>
#include <fcntl.h> // open() O_RDONLY O_NONBLOCK
using namespace std;
#define BUFSIZE 258

int main(){
    // 打开命名管道
    int fd = open("/tmp/fifo",O_RDONLY|O_NONBLOCK);
    if(-1 == fd){
        perror("open error");
        return EXIT_FAILURE;
    }else{
        cout << "filo open ok" << endl;
    }
    
    // 读取字符串
    char buffer[BUFSIZE] = {0};
    int n = -1;
    while(n<=0){
        n=read(fd,buffer,BUFSIZE);
    }
    // 打印读取的字符串
    cout << buffer << endl;
    
    // 关闭管道
    close(fd);
    return EXIT_SUCCESS;
}

特点

  • 可以是非亲缘进程之间
  • FIFO首先会阻塞在open(),等待读写文件的文件描述符都打开。接着阻塞在read()/write()操作,读写操作需要同时执行。

案例

  • FIFO工具箱

四、通信分类

  • 只写单工:
    只写单工
  • 只读单工:

只读单工

  • 半双工:

半双工

  • 全双工:

全双工

类型创建、打开关闭
单工popen()pclose()fread()fwrite()
半双工pipe()/open()close()read()write()
FIFO半双工mkfifo()/open()close()/unlink()read()write()
全双工socketpair()close()read()write()

4.1 单进程管道

在这里插入图片描述
管道通常用于进程间通信

4.2 父子进程单向管道

4.2.1 概念图解

父子进程管道

父子进程管道

在这里插入图片描述
父进程关闭fd[0] 子进程关闭fd[1]

在这里插入图片描述

父子进程单向管道
### 4.2.2 原理图解

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

4.3 父子进程双向管道

在这里插入图片描述

五、文件描述符

5.1 Linux文件读写与标准C的文件读写

文件描述符

文件描述符文件流
数据int整数FILE指针
标准POSIXANSI C
打开openfopen
关闭closefclose
readfread
writefwrite
定位lseekfseek
  • 文件流是文件描述符之上的封装。文件流通过增加缓冲区减少读写系统调用次数来提高读写效率。在进程的用户空间封装的FILE结构,以提高可移植性和效率

5.2 文件描述符原理

Linux内核使用三个关联的数据结构,表示打开的文件。在这里插入图片描述
在这里插入图片描述

  • 打开不同的文件
    在这里插入图片描述
  • 打开相同的文件
    在这里插入图片描述
  • 父子进程共享文件
    在这里插入图片描述

5.3 命令lsof

lsof(list open files):列出当前系统打开文件

  • 列名含义
列名含义
COMMAND进程的名称
PID进程标识符
USER进程所有者
FD文件描述符,应用程序通过文件描述符识别该文件。如cwd、txt等
TYPE文件类型,如PIPE、DIR、REG等
DEVICE指定磁盘的名称
SIZE文件的大小
NODE索引节点(文件在磁盘上的标识)
NAME打开文件的确切名称
  • 常用方法
命令作用
sof 文件名查看文件打开信息
lsof -d 文件描述符查看文件描述符信息
lsof -p PID查看进程PID打开的文件信息

5.3 文件描述符复制

分类文件描述符文件号
标准输入STDIN_FILENO0
标准输出STDOUT_FILENO1
标准出错信息STDERR_FILENO2
内核为每个进程创建的文件描述符。
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main(){
    char str[1024];
    scanf("%s",str);
    printf("%s\n",str);

    read(STDIN_FILENO,str,sizeof(str));
    write(STDOUT_FILENO,str,strlen(str));
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值