【Linux篇章】进程通信黑科技:匿名管道的暗码传递与命名管道的实名使命

发布于:2025-04-22 ⋅ 阅读:(19) ⋅ 点赞:(0)

本文主要探讨进程间通信中的匿名管道和命名管道。将介绍匿名管道如何凭借其隐蔽性在进程间实现数据传输,以及命名管道因具备可命名的特性所带来的独特通信优势;同时会进行相关示例效果演示;代码编写;带读者一步步理解所谓的匿名管道和命名管道等。

    羑悻的小杀马特.-CSDN博客

  欢迎拜访:羑悻的小杀马特.-CSDN博客

本篇主题:秒懂百科之匿名管道与命名管道

制作日期:2025.04.21

隶属专栏:linux之旅

目录

一·何为进程间通信:

1.1进程间通信⽬的:

1.2进程如何进行通信:

1.3进程间通信分类:

二·何为管道:

2.1·匿名管道:

2.1.1简单介绍:

 2.1.2匿名管道五大特性:

2.1.3管道通信四种情况:

2.1.4 匿名管道之父子进程通信模拟实现(父读子写):

2.1.5基于匿名管道实现简单版本进程池:

 进程池代码实现:

task.hpp:

propool.hpp:

main.cc:

运行效果: 

进程池实现的小bug: 

 bug解决方法:

法一:

 法二:

2.2·命名管道:

2.2.1如何创建命名管道:

2.2.1.1 指令创建并完成通信:

2.2.1.2代码创建并完成通信:

命名管道打开规则:

命名管道与匿名管道区别:

①两个进程之间进行写读操作:

代码实现:

Makefile:

server.cc:

client.cc:

封装后代码展示:

combine.hpp:

server.cc:

client.cc:

②通过两进程交流完成文件内容转移操作:

代码实现:

combine.hpp:

测试一下:


首先,我们还是先介绍一下什么是进程通信。

一·何为进程间通信:

谈到通信;毫不陌生;肯定是两个进程甚至多个之间进行信息等交流。

那么为什么要进行进行通信呢?

1.1进程间通信⽬的:

下面就是进程通信可以用来:

数据传输:⼀个进程需要将它的数据发送给另⼀个进程。

资源共享:多个进程之间共享同样的资源。

通知事件:⼀个进程需要向另⼀个或⼀组进程发送消息,通知它(它们)发⽣了某种事(如进程终⽌时要通知⽗进程)。

进程控制:有些进程希望完全控制另⼀个进程的执⾏(如Debug进程),此时控制进程希望能够拦截另⼀个进程的所有陷⼊和异常,并能够及时知道它的状态改变。

那么进程可以通过什么进行通信:

1.2进程如何进行通信:

管道    System V进程间通信  POSIX进程间通信  ;可以通过这三种方式进行通信;而本篇我们所介绍的就是进行管道通信。

1.3进程间通信分类:

管道: 匿名管道pipe 与命名管道(本篇所讲)。

System V IPC:System V 消息队列    System  V共享内存     System V 信号量。

POSIX  IPC:消息队列  共享内存  信号量   互斥量  条件变量  读写锁。


二·何为管道:

谈到通信,而我们本篇所讲的就是管道通信(都需要有亲缘关系的进程之间进行)。

管道是Unix中最古⽼的进程间通信的形式; 我们把从⼀个进程连接到另⼀个进程的⼀个数据流称为⼀个“管道”;也就是说把它们抽象成管道方便理解。

那么进程通信的本质是什么先让不同的进程看到同一份资源(共享同一份内存)然后就称之为我们进行通信的条件。

对于管道:我们可以把它理解成一种特殊的文件;简单来说就是它不会向磁盘刷新数据;只供进程在内存级别进行交流使用(只由操作系统自己维护)。

2.1·匿名管道:

2.1.1简单介绍:

匿名管道;很明显就是没有名字的管道(用于父子进程之间通信)。

先谈如何创建匿名管道:

int pipe(int fd[2]):

fd:⽂件描述符数组,其中fd[0]表⽰读端, fd[1]表⽰写端。

返回值:成功返回0,失败返回错误代码。

 这里比如我们一个进程创建一个匿名管道:它传入的数组;创建成功后得到的fd[0]==3;fd[1]==4(在没有打开其他自定义文件下) ;因为系统默认每个进程都打开标准输入标准输出标准错误。

然后就是我们根据拿到的这个返回值作为读还是写进这个管道了。

 下面清晰一张图;了解一下这个过程:

因此我们可以得出结论:创建子进程是写时拷贝;但是文件不属于进程;故只是改变它的指向。

 那么;我们知道了它是用于父子进程之间通信的;下面粗略说一下过程:

也就是我们父进程先创建这个匿名管道(可千万不是先fork子进程);然后再fork;这里假设我们父读子写;那么我们就要把附近的写端也就是fd[1]关闭而子进程的读端fd[0]关闭。

 下面看一下操作图:

 2.1.2匿名管道五大特性:

1·匿名管道只能用来进行有血缘关系的进程之间通信(如父子进程)

2· 管道文件自带同步与互斥机制(命名管道也如此)需要保证同时性;比如只有写完才能让它读(类似打电话一样)。

3·管道是面向字节流的: 以字节为单位进行操作。

生命周期随进程:进程退出,管道释放。

管道是半双⼯的,数据只能向⼀个⽅向流动;需要双⽅通信时,需要建⽴起两个管道;

也就是任何时刻只能一个读一个写(如果两个能同时读或写才是全双工)。

这里为了方便理解;我们可以把这个匿名管道当成一个单向的数据流动管子理解即可(一个往里塞数据;一个读出)。 

当然了我们也可以实现双向通信;不过要借助两个管道了;下面看图:

这里只不过在另一个管道交换了父子进程的读写方式而已。 

2.1.3管道通信四种情况:

这里匿名管道还是命名管道都遵循这个原则。

1.写的慢,读的快: 那么此时读端就要进行阻塞而等待写端写入(同步性)那么read就会在这一直等着不动。

2·写的快,读的慢:那么就会写满;然后写就要阻塞;等到读端读一些使得管道有空间才进行写操作;write阻塞不动。

3·写端关闭;而读端还在读;此时读端从管道读不到任何内容就会读到文件结尾也就是read返回0。

4·读关闭;继续写:此时写是没有意义的;因为通信的另一端已经不存在了;因此OS会杀死写端进程;并发送13 号信号:SIGPIPE。

SIGPIPE 是一个信号常量,其值通常为 13。信号是一种软件中断机制,用于通知进程发生了某种特定的事件。SIGPIPE 信号主要与管道(包括匿名管道和命名管道)以及网络套接字通信相关。

下面了解一下即可: 

 1·当没有数据可读时:

 O_NONBLOCK disable:read调⽤阻塞,即进程暂停执⾏,⼀直等到有数据来到为⽌。

 O_NONBLOCK enable:调⽤返回-1,errno值为EAGAIN。

2·当管道满的时候:
O_NONBLOCK disable:write调⽤阻塞,直到有进程读⾛数据。

O_NONBLOCK enable:调⽤返回-1,errno值为EAGAIN。

下面再普及个知识点:

当要写⼊的数据量不⼤于PIPE_BUF时,linux将保证写⼊的原⼦性。

当要写⼊的数据量⼤于PIPE_BUF时,linux将不再保证写⼊的原⼦性。

一般ubuntu的匿名管道大小是64kb;这里就可以这么理解:以4kb也就是块为单位基础;当小于它是就按照4kb原则去读;大于它就全读走。

2.1.4 匿名管道之父子进程通信模拟实现(父读子写):

下面我们所实现的就是利用pipe函数以及fork来完成父子间通信;让子进程利用循环把自己的信息写入管道然后父进程去读走然后给我们打印出来(子关闭读端;父关闭写端)。

父进程读:

void fread(){
    int n=10;
    char bf[1024]={0};
    while(n--){
        read(fds[0],bf,sizeof(bf)-1);
        cout<<"父进程收到的子进程信息:"<<bf<<endl;
    }

子进程写:

void cwrite(){
    int n=10;
    char bf[1024]={0};
    while(n--){
        snprintf(bf,sizeof(bf),"我是子进程 : pid:%d count:%d",getpid(),n);
        write(fds[1],bf,strlen(bf));
       sleep(1);
    }
}

总代码: 

Makefile:

pp:pipe.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -rf pp

pipe.cc:

#include<iostream>
#include <unistd.h>
#include <sys/types.h>
 #include <unistd.h>
 #include <cstdio>
 #include<cstring>
 #include <sys/wait.h>
using namespace std;
int fds[2]={0};
void cwrite(){
    int n=10;
    char bf[1024]={0};
    while(n--){
        snprintf(bf,sizeof(bf),"我是子进程 : pid:%d count:%d",getpid(),n);
        write(fds[1],bf,strlen(bf));
       sleep(1);
    }
}
void fread(){
    int n=10;
    char bf[1024]={0};
    while(n--){
        read(fds[0],bf,sizeof(bf)-1);
        cout<<"父进程收到的子进程信息:"<<bf<<endl;
    }
}

int main(){
   
   int m=pipe(fds);
   if(m<0) cerr<<"pipe 错误"<<endl;
   //else{ cout<<fds[0]<<" "<<fds[1]<<endl;}
   pid_t pid=fork();
   //子进程:
   if(pid==0){
     close(fds[0]);
     cwrite();
     close(fds[1]);
     exit(0);//这里子进程完成后别忘记退出否则后面就要执行了
   }
   //父进程:
   close(fds[1]);
   fread();
waitpid(pid, nullptr, 0);
close(fds[0]);
  
return 0;
 
}

测试效果:

2.1.5基于匿名管道实现简单版本进程池:

 基于父进程分配任务给子进程去执行:

老样子还是先描述后组织;我们以面向对象的方式来对它进行封装。

把父进程创建的所有匿名管道当成一个类进行封装;最后在组织起来再封装成一个管理管道的类;最后就是由进程池这个主类进行控制即可。

下面看一张图来粗略理解进程池实现的轮廓;之后辅助代码实现:

需要实现的四个类:

1·任务集合(封装产生任务码;执行任务;插入任务等);

2·匿名管道(存入父的写fd与subpid;用来写入管道;关闭管道;回收管道另一端的子进程);

3·管道集合(随机挑选管道;插入管道;关闭所有管道写端;回收所有子进程);

4·进程池主体(执行准备;运行;停止工作)。

这里为了方便回收子进程故我们设计的是如果子进程读到0也就是对应父进程写端关闭那么子进exit(这里由于考虑到写时拷贝以及引用计数的问题;后续改进进程池会讲到)。

整体设计思路:首先父进程进行构建管道(完成管道插入);让子进程准备就绪;然后父进程执行运行开始分配任务让对应子进程进行执行操作;最后父进程关闭写端;最后依次回收子进程。(匿名 管道这里取5个;执行10次任务)

 进程池代码实现:

task.hpp:

#pragma once
#include <iostream>
#include <vector>
#include <ctime>
using namespace std;
 typedef void(*tt)();//函数指针
 void PrintLog()
{
    std::cout << "我是一个打印日志的任务" << std::endl;
}

void Download()
{
    std::cout << "我是一个下载的任务" << std::endl;
}

void Upload()
{
    std::cout << "我是一个上传的任务" << std::endl;
}

class task_set{

  public:
   task_set(){
     srand(time(0));
   }
      void Register(tt t){
          _ts.emplace_back(t);//构建任务数组
      }
      int getcode(){
        return rand()%_ts.size();
      }
      void Excute(int code){
        _ts[code]();
      }
      ~task_set(){}
  private:
   vector<tt> _ts;
};

propool.hpp:

#pragma once
#include <cstdlib> 
#include <unistd.h>
#include<string>
#include <sys/wait.h>
#include "task.hpp"
class channel{

 public:
   channel(pid_t pid,int fd):_pid(pid),_fd(fd){
      _channel_name="[子进程:"+to_string(_pid)+"---管道:"+to_string(_fd)+"]";
   }
    bool send(int code){

      int n = write(_fd, &code, sizeof(code));
        if (n == sizeof(code))  return true;
         else return false;      
    }
    void channel_close() {
        close(_fd);
    }
    void channel_wait(){
      pid_t wp=waitpid(_pid,nullptr,0);
    }
     string get_name(){
        return _channel_name;
     }

   ~channel(){}

 private:
   pid_t _pid;
   int _fd;
   string _channel_name;
};

class channel_set
{
  public:

     channel_set(){}
       void Insert(pid_t pid ,int fd){
           _cls.emplace_back(pid,fd);
       }

       channel& Select(){
          auto &c=_cls[_next];
          _next++;
          _next%=_cls.size();
          return c;
       }

       void close_channels(){
         for(int i=0;i<_cls.size();i++){
            _cls[i].channel_close();
         }
      }

       void wait_subprocess(){
      for(int i=0;i<_cls.size();i++){
           _cls[i].channel_wait();
         }
       }
 

         void sub_close_allw(){
         for(int i=0;i<_cls.size();i++){
            _cls[i].channel_close();
             }
         }
     
  private:
   vector<channel> _cls;
   int _next;
};

class propool{
 public:
      propool(int x):_pipesize(x){
       _tt.Register(&PrintLog);
       _tt.Register(& Download);
       _tt.Register(&Upload); 
      }
       void work(int fd){
        while(1){
          int code;
           int n= read(fd,&code,sizeof(code));
           if(n>0){
                if(n!=sizeof(code)) continue;
                cout<<"子进程:"<<getpid()<<" 获得的任务码:"<<code<<endl;
                _tt.Excute(code);
           }
           else if(n==0) {
            cout<<"子进程:"<<getpid()<<" 正常退出"<<endl;
                break;
           }
           else {
            cout<<"读取错误"<<endl;
              break;
           }
        }
       }
      bool Start(){
        for(int i=1;i<=_pipesize;i++){
        int fd[2];
        int n=pipe(fd);
        if(n<0)  return false;
        pid_t pid=fork();
        if(pid<0) return false;
        else if(pid==0){
          //在这之前把继承的父进程的所有写端关掉(这里并不会把自己的写端给关掉;因为写时拷贝):
          _ct.sub_close_allw();
           close(fd[1]);
           work(fd[0]);
           exit(0);

        }
        else{
            close(fd[0]);
            _ct.Insert(pid,fd[1]);
        }
        }
        return 1;
      }
       
     void run(){
      int code= _tt.getcode();
        cout<<"父进程发布任务,任务码:"<<code<<endl;
        auto&c=_ct.Select();
        cout<<"父进程选择的管道:"<<c.get_name()<<endl;
        cout<<"将要把任务分配给子进程执行"<<endl;
        c.send(code);

     }
       void stop(){
           cout<<"即将关闭管道"<<endl;
           _ct.close_channels();
           cout<<"父进程已经关闭所有管道"<<endl;
           cout<<"即将回收所有子进程"<<endl;
           _ct.wait_subprocess();
           cout<<"所有子进程已完成回收"<<endl;
        

       }
      ~propool(){}
 private:
 task_set _tt;
 channel_set _ct;
  int _pipesize;
};

main.cc:

#include "propool.hpp"
int main(){
    propool pp(5);
    pp.Start();
    int cnt = 10;
    while(cnt--)
    {
        pp.run();
        sleep(2);
    }
     pp.stop();
    return 0;
}

运行效果: 

这里截取了一部分:

进程池实现的小bug: 

但是这里会有个bug:

为了不应该是父进程关闭一个管道;然后回收另一端的子进程;然后依次类推呢?这里是全关后全回收;因此我们按照这个需求改了一下;发现运行时就出现了问题:

 原来是:

下面按照我们一个管道关闭;对应回收一个子进程:

下面来测试一下看看会发生什么:

这又是啥情况呢?

这就涉及到父进程fork后子进程进行的拷贝+对文件有引用计数设计的原因了;

分析:

 那么为啥之前的写法(全关全回收)就可以:

这样是ok的;因为当我们全部都关闭父的写端的话;那么最后一个子进程它先会识别;它的管道的父的写端关了;因此直接读到0;子进程exit---->依次向上推;倒数第二个管道它之前的引用计数为3个此时变成两个然后变一个(最后的子进程exit了;只剩下它的读端了);那么同理接着这个字进程也会挂掉 剩下的同理。

为什么关一个回收一个(上面的写法)就不行呢?

假设我们把第一个管道关掉那么;但是它的引用计数不为0(剩下的子进程对于它的写端仍开着):那么此时这个文件就还是有写端的因此读端不会到0;子一直读;阻塞。

 bug解决方法:

法一:

那么如何解决;我们就根据它为什么会阻塞的原因来解决:不就是拷贝给后面的子进程导致还是有些写端执行这个管道;因此我们可以发现最后一个管道它是没有子进程作为写端的;因此我们可以考虑倒着来边关闭边回收

 法二:

我们会说;按里来说正着来回收;关一个收一个比较好;那还有一个这个思路的解决方案;但是我们还是要把后面子进程指向前面管道的写端关闭;那咋进行呢?

因为父创建一个子进程;它是完全cv父进程的那些文件描述符;写端也是cv过来;因此我们可以在子进程活动的里面把对应的它之前管道写的fd关掉即可;那么从哪找-->就是管道的vector拿到然后关闭:

此时肯能会说;这不就把自己的管道的写也关了;后面又关了一次;然而错了;不要忘了写时拷贝:

这样我们就明白了此时vector中没有放子进程自己管道的写端;因此大胆关闭后面再关自己的。

 运行测试:

正常结束;因此上面我们的想法是正确的。 

那么我们匿名管道就到此为止了;下面开启命名管道: 

2.2·命名管道:

这就是有名字的管道(特殊文件名-->方便我们写或者读找到这个管道)。

管道应⽤的⼀个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。

如果我们想在不相关的进程之间交换数据,可以使⽤FIFO⽂件来做这项⼯作,它经常被称为命名管道。(可以知道每次搞匿名管道都是子进程会继承父进程的数据等;也就是不想通过fork才能交流(俩进程)非继承)。

命名管道是⼀种特殊类型的⽂件

对于命名管道其实就是对匿名管道理解修正一下即可:相当于在一个路径下搞了一个特殊的文件(缓冲区也是内存;但是不跟新磁盘);需要交流的进程只需要得到这个路径然后交流即可。

下面有个下特点需要说一下:

管道的生命周明随进程,本质是内核中的缓冲区,命名管道文件只是标识,用于让多个进程找到同一块缓冲区,删除后,之前已经打开管道的进程依然可以通信:

管道名字即文件名字只是为了让进程能找到这块缓冲区而己;已经找到删除也能找到;因为此刻已经拿到了fd(就能找到file_struct进而找到缓冲区);也就是说找到后这个管道名没用了。

2.2.1如何创建命名管道:

2.2.1.1 指令创建并完成通信:

mkfifo filename

默认在当前路径下创建命名管道:

接下来就是利用echo来写入(标准输出重定向到里面);cat来读取(标准输入重定向到里面) 。

这里我们会发现如果同时读读写打开:如果先开读那么就会阻塞;如果先是写;写完没有读走也会阻塞注;也就是只有当双方都完成了读或者写才会算执行完。 

如何用指令删除命名管道:

 使用我们的unlink +管道名

 指令创建也是很简单的了。

2.2.1.2代码创建并完成通信:

创建:

 int mkfifo(const char *filename,mode_t mode);

也就是传入文件路径+名字(如果只是文件就默认当前路径创建);和权限掩码 。

成功返回0失败就非0。 

剩下的读写就是文件操作了;后面我们会代码演示。

然后关闭管道用也是unlink:

  int unlink(const char *pathname);

 同理为0就关闭成功。

那么下面我们就说一下打开规则:

命名管道打开规则:

如果当前打开操作是为读⽽打开FIFO时:

O_NONBLOCK disable:阻塞直到有相应进程为写⽽打开该FIFO

O_NONBLOCK enable:⽴刻返回成功

 如果当前打开操作是为写⽽打开FIFO时:

O_NONBLOCK disable:阻塞直到有相应进程为读⽽打开该FIFO

 O_NONBLOCK enable:⽴刻返回失败,错误码为ENXIO

我们只需要记住当打开一个无论是谁都会阻塞住;直到另一个开启才会正常完成读或者写的传递(类似匿名管道写满或者读的快的阻塞)。 

命名管道与匿名管道区别:

匿名管道由pipe函数创建并打开而命名管道由mkfifo函数创建,打开⽤open

FIFO(命名管道)与pipe(匿名管道)之间唯⼀的区别在它们创建与打开的⽅式不同,⼀但这些⼯作完成之后,它们具有相同的语义。

下面我们就来应用一下:

①两个进程之间进行写读操作:

这里也就是我们两个进程即两个程序为server(服务端进行创建管道;然后打开并完成读操作);client(客户端进行写操作)。

代码实现:

Makefile:

.PHONY:all
all:client server
client:client.cc
	g++ -o $@ $^ -std=c++11
server:server.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f client server

server.cc:

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


int main(){
     umask(0);
    int n=mkfifo("ff",0666);
      if (n != 0)
    {
        std::cerr << "mkdir fifo error" << std::endl;
        return 1;
    }
      int x=open("ff",O_RDONLY);
      std::cout<<"open success"<<std::endl;//客户端也就是管道写端没有进入则一直堵塞
       if (x < 0)
    {
        std::cerr << "open fifo error" << std::endl;
        return 2;
    }
     while(1){
         char buff[1024]={0};
        int n= read(x,buff,sizeof(buff)-1);
      if(n>0) std:: cout<<"客户说:"<<buff<<std::endl;
      else if(n==0){
          std::cout<<"client exit"<<std::endl;
          break;
     }
     
     else {std::cout<<"read error"<<std::endl;}
     }
     int u=unlink("./ff");
     if(u==0) std::cout<<"成功删除管道"<<std::endl;
     else std::cout<<"unlink error"<<std::endl;
    
     close(x);
    

}

client.cc:

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



int main(){
      int x=open("ff",O_WRONLY);
       if (x < 0)
    {
        std::cerr << "open fifo error" << std::endl;
        return 2;
    }
     while(1){
          std::string mess;
         std:: getline(std::cin,mess);
        int n= write(x,mess.c_str(),mess.size());
      if(n>0) {}
     }
     close(x);
}

 其实这里也可以封装成面向对象的形式:

封装后代码展示:

combine.hpp:

#pragma once  
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include<string>
using namespace std; 
#define EXIT(e) do {\
     perror(e);\
     exit(EXIT_FAILURE);\
}while(0)

class cnpipe{
   public:
    cnpipe(const string & path,const string & name):_path(path),_name(name)
     {   _pfname=_path+"/"+_name;
        umask(0);
        int n=mkfifo(_pfname.c_str(),0666);
          if (n != 0)
        {
          EXIT("mkdir");
           
        }
        
     }
     ~cnpipe(){
        int u=unlink(_pfname.c_str());
        if(u==0) std::cout<<"成功删除管道"<<std::endl;
        EXIT("unlink");
       
     }
    private:
     
   string _path;
    string _name;
    string _pfname;
};
class oppipe{
   

    public:
    oppipe(const string & path,const string & name):_path(path),_name(name){
        _pfname=_path+"/"+_name;
    }

       void openw(){
        _fd=open(_pfname.c_str(),O_WRONLY);
        std::cout<<"openw success"<<std::endl;//服务端也就是管道读端没有进入则一直堵塞
        if (_fd < 0)
     {
         EXIT("openw");
     }
       }
      void openr(){
        _fd=open(_pfname.c_str(),O_RDONLY);
        std::cout<<"openr success"<<std::endl;//客户端也就是管道写端没有进入则一直堵塞
         if (_fd < 0)
      {
          EXIT("openr");
      }
      }
     void pwrite(){
          while(1){
            std::string mess;
           std:: getline(std::cin,mess);
          int n=write(_fd,mess.c_str(),mess.size());
          if(n<0) EXIT("pwrite");
          }
        
       
     }
     void pread(){
         while(1){
            char buff[1024]={0};
           int n= read(_fd,buff,sizeof(buff)-1);
         if(n>0) std:: cout<<"客户说:"<<buff<<std::endl;
          else if(n==0) {cout<<"client exit"<<endl; break;}
         else  EXIT("pread");
        }
     
    }

    private:
    string _path;
    string _name;
    string _pfname;
    int _fd;

};

server.cc:

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include<string>
#include "combine.hpp"



int main(){
  cnpipe cp(".","ff");
  oppipe op(".","ff");
  op.openr();
  op.pread();


}

client.cc:

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include<string>
#include "combine.hpp"


int main(){
  oppipe op(".","ff");
  op.openw();
  op.pwrite();
}

下面运行一下:

②通过两进程交流完成文件内容转移操作:

 我们来完成这个内容的代码实现:

下面看一张清晰一下写代码思路:

代码实现:

combine.hpp:

#pragma once  
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include<string>
using namespace std; 
#define EXIT(e) do {\
     perror(e);\
     exit(EXIT_FAILURE);\
}while(0)

class cnpipe{
   public:
    cnpipe(const string & path,const string & name):_path(path),_name(name)
     {   _pfname=_path+"/"+_name;
        umask(0);
        int n=mkfifo(_pfname.c_str(),0666);
          if (n != 0)
        {
          EXIT("mkdir");
           
        }
        
     }
     ~cnpipe(){
        int u=unlink(_pfname.c_str());
        if(u==0) std::cout<<"成功删除管道"<<std::endl;
        EXIT("unlink");
       
     }
    private:
     
   string _path;
    string _name;
    string _pfname;
};
class oppipe{
   

    public:
    oppipe(const string & path,const string & name):_path(path),_name(name){
        _pfname=_path+"/"+_name;
    }

       void openw(){
        _fd=open(_pfname.c_str(),O_WRONLY);
        std::cout<<"openw success"<<std::endl;//服务端也就是管道读端没有进入则一直堵塞
        if (_fd < 0)
     {
         EXIT("openw");
     }
       }
      void openr(){
        _fd=open(_pfname.c_str(),O_RDONLY);
        std::cout<<"openr success"<<std::endl;//客户端也就是管道写端没有进入则一直堵塞
         if (_fd < 0)
      {
          EXIT("openr");
      }
      }
     void pwrite(){
        
          int x= open("a.txt",O_RDONLY);
          while(1){
            char buff[1024]={0};
           int n=read(x,buff,sizeof(buff)-1);
            if(n==0) break;
            else {
               int m=write(_fd,buff,n);
                if(m<0) EXIT("pwrite");
            }
          }
           close(x);
       
     }
     void pread(){
     
        int x=open("b.txt",O_WRONLY);
        while(1){
          char buff[1024]={0};
          int n= read(_fd,buff,sizeof(buff)-1);
          if(n==0) break;
          int m=write(x,buff,n);
         if(m<0) EXIT("pwrite");
          
        }
        close(x);
    }

    private:
    string _path;
    string _name;
    string _pfname;
    int _fd;

};

这里还是上面封装后的client和server的代码就不copy过来了。

测试一下:

a.txt:

b.txt: 

执行程序:

发现写入成功了:

这里其实我们也可以向上面匿名管道一样搞一个进程池;其实就是把上面的代码创建管道方式修改一下就好;这里就不再实现了。

匿名管道与命名管道各有千秋,匿名管道隐蔽、适用于亲缘进程,而命名管道凭借名称突破限制,跨进程通信更灵活。二者皆为进程通信的得力工具,在不同场景中发挥关键作用,推动着程序高效协作。

本篇分享匿名管道与命名管道分享到此为止;期待与下一次的Linux篇章相遇!!! 


网站公告

今日签到

点亮在社区的每一天
去签到