文章目录
- 命名管道
- 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;
}