命名管道
匿名管道只能用来进行进程间通信,让具有血缘关系的进程进行通信
让毫不相关的进程之间进行通信,就需要采用命名管道通信
因为该文件有文件名称的,而且必须要有,所以叫做命名管道
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; }