进程间通信(管道文件)

进程间通信

  • 进程间具有独立性,那么如果想要进程之间进行信息交互,应该怎么办?

    如果想要两个进程之间交互,就需要这两个进程能够访问同一份资源(文件,内存块……)

    资源的不同决定了不同的通信方式,在这里采用管道文件

管道

匿名管道

管道的原理

父进程创建子进程,子进程相关的数据结构(task_struck ,mm_struct,files_struct)继承自父进程(就是把父进程的数据结构拷贝一份),那么在父进程的files_struct中的指针数组和子进程的files_struct中的指针数组指向同一批文件,那么这些文件就是同一份资源,也就是管道文件,但是管道文件的数据只存储在内核缓冲区中,不会向磁盘中刷新

image-20230103191818904

管道的特点

管道传输数据是单向的

制作一个匿名管道

1、父进程分别用读和写的方式打开文件
2、创建子进程
3、根据需要关闭父子进程的写端或读端

管道的代码

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

#define NUM 1024

using namespace std;

int main()
{
 int pipefd[2]={0};
 if(pipe(pipefd)!=0)		//父进程以读和写的方式打开管道文件
 {
     return 1;
 }

 pid_t id=fork();			//创建子进程
 if(id<0)
 {
     cout<<"error"<<endl;
 }
 else if(id==0)
 {
     //子进程关闭读端
     close(pipefd[0]);
     //向管道文件写入
     char mag[NUM];
     char tmp[]="hello world";
     int cnt=5;
     while(cnt--)
     {
         sprintf(mag,"时间戳:%ld,%s\n",time(nullptr),tmp);
         write(pipefd[1],mag,strlen(mag));
         sleep(1);
     }
     close(pipefd[1]);	//写完,关闭写端
     exit(1);			//子进程退出
 }
 else 
 {
     //父进程关闭写端
     close(pipefd[1]);
     //读出
     char mag[NUM];
     while(1)
     {
         memset(mag,0,sizeof(mag));
         int judge=read(pipefd[0],mag,sizeof(mag)-1);
         mag[strlen(mag)]='\0';
         if(judge>0)
         {
             printf("父进程读取子进程成功:%s\n",mag);
         }
         else if(judge==0)
         {
             printf("父进程读取结束\n");
             break;
         }
         else
         {
             exit(2);
         }
     }
     close(pipefd[0]);		//关闭读端
     int status=0;
     waitpid(id,&status,0);	//父进程等待子进程
     cout<<"hello code"<<endl;
 }

 return 0;
}

动画

  1. 在上述代码中,读端并没有设置sleep(1),但是也是会休眠一秒,为什么?

    因为当父进程读取管道文件时,管道文件中没有数据,无法读取数据,阻塞等待

    等到子进程向管道中写入数据,父进程才可以读取文件,这时管道文件中有没有数据了,等一秒后,子进程再向管道中写入数据,父进程再读取,所以父进程才会和子进程一样休眠1秒。

    这就是管道内部自带的访问控制机制(同步和互斥机制)

    除此之外,如果父进程一直不读取文件,而子进程一直向管道文件中写入,如果管道文件被写满了,那么子进程写入管道文件,也会发生阻塞等待,只有父进程读取文件,管道中有空间了,才能继续写入。

  2. 在管道文件写满或为空,发生阻塞等待时,进程pcb在那个资源的等待队列上?

    在管道文件的等待队列上,等到资源就绪了,再回到运行队列。

  3. read函数的返回值

    管道的读端可以感知到写端是否关闭,因为在struct file中的文件属性有一个引用计数,引用计数记录了多少个指针指向这个文件,写端关闭,read就可以读到文件结尾,返回值为0

进程控制

我们已经实现了使用匿名管道实现父子进程的通信,接下来介绍如何使用父进程控制子进程

父进程控制单个子进程

原理:

image-20230105210023290

代码:

typedef void(*functor)();
vector<functor>functors;			//函数方法集
unordered_map<int,string> info; 	//记录编号对应的方法

void f1()
{
 cout<<"方法1"<<endl;
}

void f2()
{
 cout<<"方法2"<<endl;
}

void f3()
{
 cout<<"方法3"<<endl;
}

//加载方法
void LoadFunctors()
{
 functors.push_back(f1);
 info.insert({functors.size(),"方法1"});
 functors.push_back(f2);
 info.insert({functors.size(),"方法2"});
 functors.push_back(f3);
 info.insert({functors.size(),"方法3"});

 cout<<"方法集加载成功"<<endl;
}

int main()
{
 LoadFunctors();
 int pipefd[2]={0};
 //父进程以读和写的方式打开管道文件
 if(pipe(pipefd)!=0)
 {
     cerr<<"pipe error"<<endl;
     return 1;
 }
 pid_t id=fork();
 if(id==0)
 {
     //子进程关闭写端
     close(pipefd[1]);
     int tag=0;

     while(1)
     {
         //从管道文件中读出父进程写入的任务编号
         int dia=read(pipefd[0],&tag,4);
         if(dia==4)
         {
             //执行方法
             if(tag<functors.size())
             {
                 cout<<"子进程执行方法: ";
                 functors[tag]();
             }
             else
             {
                 cout<<"error"<<endl;
             }
         }
         else if(dia==0)
         {
             //方法执行结束
             cout<<"方法执行结束"<<endl;
             break;
         }
         else
         {
             //读取失败
         }
     }
     //子进程关闭读端
     close(pipefd[0]);
     //子进程退出
     exit(0);
 }
 else
 {
     //父进程关闭读端
     close(pipefd[0]);
     srandom(time(nullptr));
     while(1)
     {
         int tag=random()%functors.size();
         //向管道文件中写入任务编号
         write(pipefd[1],&tag,4);
         cout<<"父进程指派任务:编号:"<<tag+1<<"任务为:"<<info[tag+1]<<endl;
         sleep(2);
     }
 }
 return 0;
}

动画

父进程控制多个子进程

原理:

image-20230105210335413

代码:

typedef void(*functor)();
vector<functor>functors;
unordered_map<int,string> info; 
vector<pair<int,int>> free_info;

void work(int fd)
{
 while(1)
 {
     int tag=0;
     int dia=read(fd,&tag,sizeof(tag));
     if(dia==4)
     {
         cout<<"pid: "<<getpid()<<" 执行任务: ";
         functors[tag]();
     }
     else if(dia==0)
     {
         cout<<"pid: "<<getpid()<<" 任务执行结束"<<endl;
         break;
     }
     else
     {
         //DO NOTHING
     }
 }
}

void send(vector<pair<int,int>> _info)
{
 srand(time(nullptr));
 while(1)
 {
     int tag1=random()%_info.size();
     int tag2=random()%functors.size();

     write(_info[tag1].second,&tag2,sizeof(tag2));
     sleep(1);
     cout<<"父进程分配给子进程 pid: "<<_info[tag1].first<<"任务编号: "<<tag2+1<<endl;
 }
}

int main()
{
 //加载方法集
 LoadFunctors();
 //循环创建多个子进程
 int cnt=3;
 for(int i=0;i<cnt;i++)
 {
     int pipefd[2]={0};
     pipe(pipefd);
     pid_t id=fork();
     if(id==0)
     {
         //关闭写端
         close(pipefd[1]);
         //子进程,执行任务
         work(pipefd[0]);
         close(pipefd[0]);
         exit(0);
     }
     //父进程关闭读端
     close(pipefd[0]);
     //保存子进程pid,父进程的写端文件描述符
     free_info.push_back(pair<int,int>(id,pipefd[1]));


 }
 //父进程分配任务
 send(free_info);
 //关闭父进程写端,回收子进程
 for(int i=0;i<cnt;i++)
 {
     int status=0;
     close(free_info[i].second);
     waitpid(free_info[i].first,&status,0);
 }
 return 0;
}

动画

管道特征总结

  • 匿名管道只能用来进行具有血缘关系的进程之间,进行进程间通信,常用于父子进程
  • 管道只能单向通信,半双工
  • 管道自带同步机制(管道文件满,就不能再向管道文件中写入;管道文件空,就不能从管道文件中读出)
  • 管道是面向字节流的(先写入的字符,先被读出,没有格式边界,需要用户来定义区分内容的边界)
  • 管道的生命周期————管道是文件,进程退出了,曾经打开的文件会随着进程一起退出

命名管道

匿名管道只能使具有血缘关系的进程进行通信,那毫不相干的进程如何进行通信?

使用命名管道

命名管道原理

进程通信的本质就是不同的进程看到同一份资源,匿名管道是通过创建子进程,继承符号表来实现;而命名管道则是通过打开磁盘上的同一份文件(命名管道),向这个文件写入,读出,同样这个数据不会向磁盘中写入,磁盘中的命名管道文件只是符号。

int mkfifo(const char *pathname(路径), mode_t mode(权限));
//创建命名管道文件(创建命名管道文件时,命名管道文件不能已存在)

comm.h

#pragma once

#include<iostream>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<cstdio>
#include<cstring>
#include<unistd.h>
#include<cerrno>

#define IPC_PATH "./.fifo_1"
#define NUM 1024

Clientfifo.cpp

//客户端
#include "comm.h"
using namespace std;

int main()
{
    //打开管道文件
    int pipefd=open(IPC_PATH,O_WRONLY);
    //写入
    char line[NUM];
    while (1)
    {
        cout<<"pid: "<<getpid()<<"客户端输入: ";
        fgets(line,NUM,stdin);
        line[strlen(line)-1]='\0';
        write(pipefd,line,strlen(line));
        sleep(1);
    }
    close(pipefd);
    return 0;
}

Serverfifo.cpp

//服务器端
#include "comm.h"
using namespace std;

int main()
{
    //创建命名管道文件
    if(mkfifo(IPC_PATH,0600)!=0)
    {
        cerr<<"fifo error"<<endl;
        exit(1);
    }
    //打开文件
    int pipefd = open(IPC_PATH,O_RDONLY);
    //读取数据
    char line[NUM];
    while(1)
    {
        int dia = read(pipefd,line,NUM);
        if(dia>0)
        {
            line[dia]='\0';
            cout<<"pid: "<<getpid()<<" 服务器端 读取:"<<line<<endl;
        }
        else if(dia==0)
        {
            cout<<"读取结束"<<endl;
            break;
        }
        else
        {
            //do nothing
        }
    }
    close(pipefd);
    unlink(IPC_PATH);
    return 0;
}

动画

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值