【Linux】进程间通信——命名管道

简介: 【Linux】进程间通信——命名管道

命名管道

匿名管道只能用来进行进程间通信,让具有血缘关系的进程进行通信

让毫不相关的进程之间进行通信,就需要采用命名管道通信


因为该文件有文件名称的,而且必须要有,所以叫做命名管道


1. 见一见管道文件

mkfifo函数

输入 man mkfifo 指令


b1daa3aac7f343c0ac00fdef7b9ca961.png


制作一个 FIFOS ,表示命名管道

bcf8c519e5a44438b02f06c810927fe7.png


mkfifo fifo 制作一个管道 ,并命名为 fifo

文件类型以p开头,被称为管道文件


输入 man 3 mkfifo 指令


5772720013b94f9dbb7c8f08437d0a2b.png

pathname代表路径,若不带路径只有文件名,默认在当前路径下

mode代表创建权限的模式 ,即创建文件的权限(666、664)

成功返回0,失败返回-1


管道文件的使用


将hello world 重定向到fifo管道中

但是好像并不会写入

fifo只代表一种符号,向符号写入消息并不会刷新到磁盘上,而是只会把hello world写到管道中

但是管道文件是内存文件,所以大小不会改变


通过赋值SSH渠道,创建终端2

在保证终端1的输出重定向 运行的情况下

cat默认从显示器中读取

在终端2中 使用输入重定向 将 fifo重定向到显示器中


2ee73463a5124bcf80187156defbba54.png

最终在终端2中显示 hello world

而实际上 输出重定向和输入重定向 的启动都是进程,并且毫不相关


2. 命名管道原理


1aeca598e08f42f08ff7a30714555e0c.png

要打开对应的文件,就会在操作系统内创建struct file对象,struct file对象有自己的缓冲区

由于0 1 2 分别被占用,所以3指向struct file对象


若有一个毫不相关的进程,也打开磁盘中的文件,操作系统内部就不会再创建struct file对象,

会直接把struct file对象的地址填入新建立进程对应的下标里

在struct file对象中存在一个引用计数默认为1 ,当新创建一个进程时,引用计数就会变成2

此时两个进程指向同一份文件


目的是让两个进程之间进行通信,所以就不应该把数据刷到磁盘上,

应该把磁盘文件改为内存级的,不会进行刷盘,把它命名为管道文件


如何保证两个毫不相关的进程,看到的是同一个文件,并打开?

文件的唯一性,使用路径表示的

让不同的进程通过文件路径+文件名看到同一个文件,并打开,就是看到了同一个资源


3. 用命名管道实现server&client通信


在vscode中,分别创建server.cc文件和client.cc文件以及makefile


如何使用makefile连续生成可执行程序

13fd0939ae584167a9a69cb71f33313d.png

若这样创建makefile,只会执行server可执行程序

server是从上到下扫描遇到的第一个真正的目标文件

makefile从上到下扫描时,会默认执行第一组依赖关系和依赖方法


为了不让client和server成为目标文件


450e7eef68464437b393b147c11cd07b.png

这样就可以一次生成两个可执行程序了


comm.hpp文件

建立一个公共头文件 comm.hpp,在内部创建公共的路径以及mode

(以hpp结尾.cpp的实现代码混入.h头文件当中,定义与实现都包含在同一文件)

#pragma once
#include<iostream>
#include<string>
#define NUM 1024
using namespace std;
const string fifoname="./fifo";//管道名字为当前路径创建的fifo
mode_t mode=0666;//默认权限为0666

这样 server文件和client文件就会调用同一份文件路径了


server.cc 服务端

1. 创建一个管道文件

创建server.cc文件,使用mkfifo函数创建管道文件


5dbaa2d2a36a4572ba63ade4247e524e.png

2475bb6ce51640e383b975971e0b8dc4.png

此时运行可执行程序,即可生成fifo管道文件


2d992311163c4873b8eebeb1fdd8e267.png


权限变为664 ,可是在comm.hpp中设置的权限为666

mode最终是要与umask进行操作的


f1362977a3aa46d9827eef07bbe7dc01.png


手动将掩码置为0后,即可解决权限被修改的问题


手动删除fifo后,再次运行


f2fb151c508841049e9c839b0d5d98af.png


此时权限还是666,没有被修改


2. 让读写端进程分别按照自己的需求打开文件

ea37f1588e75402f9cb591ca127cc4a0.png

将文件描述符内容打印到buffer中

分为三种情况

若返回>0,则读取成功,而系统并不知道buffer是一个字符串,而我们自己知道,所以要在结尾加上\0

若返回==0,说明读到文件结尾,当写端关闭时,读端才会读到文件结尾

若返回<0,说明读取失败,则返回错误码

3. 整体代码

//服务端
#include<iostream>
using namespace std;
#include<sys/stat.h>
#include<sys/types.h>
#include<cerrno>
#include<cstring>
#include<fcntl.h>
#include<unistd.h>
#include"comm.hpp"//公共路径
int main()
{
    umask(0);//将当前进程的umask掩码设为0
  //创建管道文件,只需要创建一次
  int n=mkfifo(fifoname.c_str(),mode);
  if(n!=0)//创建失败
  {
    //失败就返回错误码
   cout<<errno<<":"<<strerror(errno)<<endl;
   return 1;
  }
   cout<<"create fifo success"<<endl;
   //2.让服务端直接开启管道文件
   int rfd=open(fifoname.c_str(),O_RDONLY);
   //第二个参数代表读
   //以读方式打开文件
   if(rfd<0)//创建失败
   {
     //失败就返回错误码
     cout<<errno<<":"<<strerror(errno)<<endl;
     return 2;
   }
   cout<<"open fifo success,begin"<<endl;
   // 3.正常通信
   char buffer[NUM];
   while(true)
   {
      buffer[0]=0;
    //rfd作为文件描述符(0/1/2)
      ssize_t n=read(rfd,buffer,sizeof(buffer)-1);//将rfd的内容读到buffer中
      if(n>0)//读取成功
      {
       buffer[n]='\0';
       cout<<"client#"<<buffer<<endl;
      }
      else if(n==0)//读到文件结尾为0
      {
          //写端关闭
          cout<<    "cilent quit,me too"<<endl;
          break;
      }
      else //读取失败
      {
         cout<<errno<<":"<<strerror(errno)<<endl;
         break;
      }
   }
   //关闭不要的fd
  close(rfd);
  unlink(fifoname.c_str());//删除文件fifo
    return 0;
}

client.cc 客户端

由于在服务端创建了管道文件,所以在客户端不用创建管道文件


bb8a014837424d5fafbcc46f4e090609.png

直接打开文件即可,以写方式打开文件


fe1f3858df8746f3b7b673b647ab706b.png


为了避免输入的单词有空格存在

输入 man fgets 指令


912dc6ebef66486790cfa36f33e91640.png


从指定的流中获取字符串,并规定字符串的大小


因为有两个可执行程序存在,所以需要两个终端


b2ce6c6104164d4ebc9482a637eb02ae.png


当终端2没有运行server时,没有管道文件存在,而终端1运行server后,终端1中出现管道文件


c6fbadc7a3c24898bb0a780a474757cf.png


当终端1运行client时,输入对应的信息,终端2中会自动显示出来

client端可以将信息发送给server端


完整代码

//客户端
#include<iostream>
using namespace std;
#include<sys/stat.h>
#include<sys/types.h>
#include<cerrno>
#include<cstring>
#include<fcntl.h>
#include<unistd.h>
#include<cstdio>
#include<cassert>
#include"comm.hpp"//公共路径
using namespace std;
int main()
{
   //不需要创建管道文件,打开文件即可
   int wfd=open(fifoname.c_str(),O_WRONLY);//以写的方式打开文件
   if(wfd<0)//说明打开失败
   {
     cout<<errno<<":"<<strerror(errno)<<endl;
     return 0;
   }
   //进行常规通信
   char buffer[NUM];
    while(true)
    {
        cout<<"请输入你的消息# ";
        char *msg=fgets(buffer,sizeof(buffer)-1,stdin);//将标准输入流的数据写入buffer中
        assert(msg);//检查是否为空
        (void)msg;//保证rlease模式发布依旧被使用
        //fgets会读取回车 即\n
        buffer[strlen(buffer)-1]=0;//12345\n  把\n位置覆盖为\0
        ssize_t n=write(wfd,buffer,strlen(buffer));
        assert(n>=0);
    }
   close(wfd);//关闭文件描述符
    return 0;
}


相关文章
|
2天前
|
消息中间件 算法 Linux
【Linux】详解如何利用共享内存实现进程间通信
【Linux】详解如何利用共享内存实现进程间通信
|
2天前
|
Linux
【Linux】命名管道的创建方法&&基于命名管道的两个进程通信的实现
【Linux】命名管道的创建方法&&基于命名管道的两个进程通信的实现
|
2天前
利用命名管道实现任意进程间的通信
利用命名管道实现任意进程间的通信
|
2天前
|
Linux 数据库
linux守护进程介绍 | Linux的热拔插UDEV机制
linux守护进程介绍 | Linux的热拔插UDEV机制
linux守护进程介绍 | Linux的热拔插UDEV机制
|
2天前
|
Unix Linux 调度
linux线程与进程的区别及线程的优势
linux线程与进程的区别及线程的优势
|
2天前
|
Linux 调度 C语言
|
2天前
|
存储 安全 Linux
【Linux】详解进程通信中信号量的本质&&同步和互斥的概念&&临界资源和临界区的概念
【Linux】详解进程通信中信号量的本质&&同步和互斥的概念&&临界资源和临界区的概念