文章目录
- 命名管道
- 1. 见一见管道文件
- mkfifo函数
- 管道文件的使用
 
- 2. 命名管道原理
- 如何保证两个毫不相关的进程,看到的是同一个文件,并打开?
 
- 3. 用命名管道实现server&client通信
- 如何使用makefile连续生成可执行程序
- comm.hpp文件
- server.cc 服务端
- 1. 创建一个管道文件
- 2. 让读写端进程分别按照自己的需求打开文件
- 3. 整体代码
 
- client.cc 客户端
- 完整代码
 
 
 
命名管道
匿名管道只能用来进行进程间通信,让具有血缘关系的进程进行通信
 让毫不相关的进程之间进行通信,就需要采用命名管道通信
因为该文件有文件名称的,而且必须要有,所以叫做命名管道
1. 见一见管道文件
mkfifo函数
输入 man mkfifo 指令
 
 
制作一个 FIFOS ,表示命名管道
 
 
mkfifo fifo 制作一个管道 ,并命名为 fifo
 文件类型以p开头,被称为管道文件
输入 man 3 mkfifo 指令
 
 
pathname代表路径,若不带路径只有文件名,默认在当前路径下
 mode代表创建权限的模式 ,即创建文件的权限(666、664)
 成功返回0,失败返回-1
管道文件的使用

 将hello world 重定向到fifo管道中
 但是好像并不会写入
 fifo只代表一种符号,向符号写入消息并不会刷新到磁盘上,而是只会把hello world写到管道中
 但是管道文件是内存文件,所以大小不会改变
通过赋值SSH渠道,创建终端2
 在保证终端1的输出重定向 运行的情况下
 cat默认从显示器中读取
 在终端2中 使用输入重定向 将 fifo重定向到显示器中
 
 
最终在终端2中显示 hello world
 而实际上 输出重定向和输入重定向 的启动都是进程,并且毫不相关
2. 命名管道原理

 要打开对应的文件,就会在操作系统内创建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连续生成可执行程序
 
 
若这样创建makefile,只会执行server可执行程序
 server是从上到下扫描遇到的第一个真正的目标文件
 makefile从上到下扫描时,会默认执行第一组依赖关系和依赖方法
为了不让client和server成为目标文件
 
这样就可以一次生成两个可执行程序了
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函数创建管道文件
 
 
 
 
此时运行可执行程序,即可生成fifo管道文件
 
 
权限变为664 ,可是在comm.hpp中设置的权限为666
 mode最终是要与umask进行操作的
 
 
手动将掩码置为0后,即可解决权限被修改的问题
手动删除fifo后,再次运行
 
此时权限还是666,没有被修改
2. 让读写端进程分别按照自己的需求打开文件
 
 
将文件描述符内容打印到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 客户端
由于在服务端创建了管道文件,所以在客户端不用创建管道文件
 
 
直接打开文件即可,以写方式打开文件
 
 
为了避免输入的单词有空格存在
 输入 man fgets 指令
 
 
从指定的流中获取字符串,并规定字符串的大小
因为有两个可执行程序存在,所以需要两个终端
 
 
当终端2没有运行server时,没有管道文件存在,而终端1运行server后,终端1中出现管道文件
 
 
当终端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;
}

















![[pgrx开发postgresql数据库扩展]2.安装与开发环境的搭建](https://img-blog.csdnimg.cn/img_convert/5532f1f571da5d6ccc54a556a30a4d5f.webp?x-oss-process=image/format,png)

