在文章开始之前,推荐一篇值得阅读的好文章!感兴趣的也可以去看一下,并关注作者!
如果你对机器学习感兴趣的话,今天的推荐文章可以帮你提高机器学习的效率。
文章链接:https://cloud.tencent.com/developer/article/2466294
通过这篇文章,可以帮助你深入的了解提升机器学习模型表现的三大核心技术。
🏠 大家好,我是Yui_,一位努力学习C++/Linux的博主~💬
在 Unix/Linux 系统中,管道(Pipe)是一种重要的进程间通信(IPC,Inter-Process Communication)机制。除了前面介绍的匿名管道(Anonymous Pipe),系统还提供了命名管道(Named Pipe),通常称为 FIFO(First In, First Out)。命名管道通过一个在文件系统中存在的路径名来标识,使得不相关的进程之间也能通过它进行通信。
命名管道是一种特殊类型的文件,它在文件系统中有一个明确的名称,可以被多个进程打开和访问。与匿名管道不同,命名管道不局限于具有亲缘关系的进程(如父子进程),任何具有访问权限的进程都可以通过命名管道进行通信。
<font color=red>我们可以把命名管道看成”挂名“的匿名管道,把匿名管道加入文件系统中,但仅仅是挂个名而已,目的是为了人其他进程也能看到也看到这个文件(文件系统中的文件可以被所有的进程看到)</font>
注意:
<font color=gold>命名管道虽然能在文件系统被看到,但是它是没有Data block的,也就它不会存储在磁盘中,是一个内存文件。所以命名管道这个特殊文件大小为0</font>
在程序中创建。
在命令行创建:
mkfifo mypipe
效果如下:
ubuntu@VM-20-9-ubuntu:~/pipeTest/namePipe$ mkfifo mypipe
ubuntu@VM-20-9-ubuntu:~/pipeTest/namePipe$ ls -l
total 0
prw-rw-r-- 1 ubuntu ubuntu 0 Nov 15 19:56 filename
prw-rw-r-- 1 ubuntu ubuntu 0 Nov 15 20:21 mypipe
为了在C程序中创建命名管道,我们需要用到的函数也是mkfifo
。
<font color=red>mkfifo:</font>
头文件:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
格式:
int mkfifo(const char *pathname, mode_t mode);
参数介绍:
pathname
mode
- 管道文件的权限(类似于文件的权限),是一个 mode_t
类型值。
- 常见值:
- 例如 0666
,允许所有用户读写。
- mode
会受到 进程的 umask
设置 的影响。
为了防止mode的设置被umask影响,可以事先将umsak设置为0,umask(0)
.
返回值:
成功:0
失败:-1,并设置error
下面我来实现一个进程向另一个进程发送信息:
客户端:client.cc
//客户端向服务端发送消息
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <cstring>
#include <fcntl.h>
#include "common.hpp"
using namespace std;
int main()
{
int mf = mkfifo(pipePath,md);
if(mf<0)
{
perror("mkfifo");
exit(1);
}
//打开管道
int fd = open(pipePath,O_WRONLY);//以写方式打开
if(fd < 0)
{
perror("open");
exit(1);
}
//char data[SIZE];
string data;
while(true)
{
//开始写入数据
cout<<"cilent message# ";
getline(cin,data);
if(data == "exit")
break;
ssize_t n = write(fd,data.c_str(),data.size());
if(n<0)
{
perror("write");
exit(1);
}
}
//退出客户端
cout<<"exit"<<endl;
close(fd);
return 0;
}
服务端:server.cc
//服务端接受客户端的信息
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <cstring>
#include <fcntl.h>
#include "common.hpp"
using namespace std;
int main()
{
//打开管道
int fd = open(pipePath,O_RDONLY);//以读的方法
if(fd < 0)
{
perror("open");
exit(1);
}
char data[SIZE];
while(true)
{
int n = read(fd,data,sizeof(data)-1);
if(n<0)
{
perror("read");
exit(1);
}
else if(n == 0)
{
//写端关闭
cout<<"写端关闭"<<endl;
break;
}
data[n] = 0;
cout<<"client say:"<<data<<endl;
}
close(fd);
if (unlink(pipePath) == -1) {
perror("unlink failed");
return 1;
}//关闭管道
return 0;
}
公共文件:common.hpp
#pragma once
#define SIZE 1024
//存储管道的路径+名字
const char* pipePath = "./myPipe";
//也可以用绝对路径,当然相对路径更简单
//设置mask
mode_t md = 0666;
效果图:
再次回到文件系统:当重复多次打开一个文件时,并不会费力的打开多次,而是在第一次的基础上对struct_file
结构体中的引用计数自增1,所以对于同一个文件,不同的进程打开了,看到的就是同一个。
这也是管道实现的本质:<font color=red>让不同的进程看到同一块空间。</font>
因为命名管道适用于独立的进程IPC
,所以无论是读端还是写端,进程A
,进程B
为其分配的文件描述符都是3。
我们知道如果是匿名管道,因为是依靠继承才看到同一文件的,所以读写端的fd
是不一样的。
pipe
函数创建出来了;而命名管道需要先通过mkfifo
函数创建,然后再通过open
打开使用。fd
被重复继承的情况;而命名管道就不会出现这种情况。3.1.2 命名管道和匿名管道的联系SIGPIPE
终止写端进程。IPC
,也就是一个进程读取文件中的内容然后写进管道当中,然后另一个进程在通过管道将数据读出保存到新的文件,如此一来就是实现了一个进程的文件拷贝功能。
这也是在网络上下载应用的方式,因为下载应用的本质就是下载文件,我们将服务器看作写端,自己的电脑看作读端,那么下载这个动作的本质就是IPC
,不过是在网络层面实现的。
公共区域common.hpp
:服务端server.cc
/**
* 服务端通过命名管道将本地文件拷贝到客户端
* 先打开命名管道,再打开需要被拷贝的文件,将文件通过命名管道发送给另一个程序
*
*/
#include "common.hpp"
int main()
{
//创建命名管道
if(mkfifo(pipePath,md) == -1)
{
perror("mkfifo");
exit(1);
}
//打开管道
int fd = open(pipePath,O_WRONLY);
if(fd == -1)
{
perror("open");
exit(1);
}
//打开需要被拷贝的文件
FILE* fp = fopen(filePath,"r");
if(fp == nullptr)
{
perror("fread");
exit(1);
}
//将文件传给管道
char buf[SIZE];
size_t bytes = 0;
while(true)
{
size_t n = fread(buf,1,sizeof(buf),fp);
if(n>0)
{
//写入管道
ssize_t sst = write(fd,buf,n);
if(sst == -1)
{
perror("write");
close(fd);
fclose(fp);
exit(1);
}
bytes+=n;
}
// 检查是否到达文件末尾或出错
if (n < sizeof(buf)) {
if (feof(fp)) {
break; // 文件读取完毕
}
if (ferror(fp)) {
perror("fread");
fclose(fp);
close(fd);
exit(1);
}
}
}
std::cout<<"传递字节数"<<bytes<<std::endl;
fclose(fp);
close(fd);
unlink(pipePath);
return 0;
}
客户端client.cc
//客户端接受服务端的文件
/**
* 客户端接受服务端的文件
* 打开命名管道,开始读取服务端传递给客户端的信息
*/
#include "common.hpp"
int main()
{
//打开管道,读取管道数据
int fd = open(pipePath,O_RDONLY);
if(fd == -1)
{
perror("open");
exit(1);
}
//将读取的数据打印到显示屏
char buf[SIZE];
ssize_t n = read(fd,buf,sizeof(buf)-1);
if(n == -1)
{
perror("read");
exit(1);
}
std::cout<<"文件内容:\n"<<buf<<"读取字节数:"<<n<<std::endl;
close(fd);
return 0;
}
运行结果:
此时服务端是写端,客户端是读端,实现的是下载服务;当服务端是读端,客户端是写端是,实现的就是上传服务,搞两条管道就能实现模拟简单的数据双向传输服务。
注意:创建管道文件后,无论先启动读端还是启动写端,都要堵塞式的等待另一方进行交互。
作为匿名管道的兄弟,命名管道具备匿名管道的大部分特性,使用方法也基本一致,不过二者在创建和打开方式上各有不同:匿名管道简单,但只能用于具有血缘关系进程间通信,命名管道虽麻烦些,但适用于所有进程间通信场景。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。