文章目录
- 命名管道
- 一、命名管道的原理
- 二、命名管道的创建
- 命令行中创建
- 程序中创建 - mkfifo函数:
- 三、命名管道的使用
- 命名管道实现server&client通信
- 四、匿名管道与命名管道的区别和联系
命名管道
如果涉及到在文件系统中创建一个有名的管道,那么就是在使用命名管道。
一、命名管道的原理
- 管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
- 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
- 命名管道是一种特殊类型的文件
注意:
普通文件是很难做到通信的,即便做到通信也无法解决一些安全问题。
命名管道和匿名管道一样,都是内存文件,只不过命名管道在磁盘有一个简单的映像,但这个映像的大小永远为0,因为命名管道和匿名管道都不会将通信数据刷新到磁盘当中。
二、命名管道的创建
命令行中创建
在命令行中,可以使用mkfifo
命令创建命名管道。mkfifo
命令的语法如下:
mkfifo [OPTION]... NAME...
其中,OPTION
是可选的参数,而NAME
是要创建的命名管道的名称。以下是一个简单的示例:
mkfifo fifo
这会在当前目录下创建一个名为 fifo
的命名管道。命名管道的创建后,可以像文件一样对待,可以通过其他命令或程序来读取和写入,但是。
程序中创建 - mkfifo函数:
mkfifo
函数是一个库函数,它是由C标准库提供的,而不是直接调用底层操作系统的系统调用。在C语言中,可以使用mkfifo
系统调用来在程序中创建命名管道。该系统调用的函数原型如下:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
pathname
参数是命名管道的路径和名称。mode
参数是权限位,用于指定文件权限。
当然,实际上创建出来文件的权限值还会受到umask(文件默认掩码)的影响,实际创建出来文件的权限为:mode&(~umask)。umask的默认值一般为0002,如果我们设置mode值为0666时,则实际创建出来文件的权限为0664。
三、命名管道的使用
命名管道实现server&client通信
实现服务端(server)和客户端(client)之间的通信之前,我们需要先让服务端运行起来,让服务端运行后创建一个命名管道文件,然后客户端再以读的方式打开该命名管道文件,之后服务端就可以从该命名管道当中读取客户端发来的通信信息了。
服务端的代码,server.cpp
如下:
#include "comm.h"
using namespace std;
int main()
{
int n = mkfifo(FILENAME, 0666);
if (n < 0)
{
cerr << "mkfifo failed, errno: " << errno << ", errstring:" << strerror(errno) << endl;
return 1;
}
cout << "mkfifo success..." << endl;
int rfd = open(FILENAME, O_RDONLY);
if (rfd < 0)
{
cerr << "errno: " << errno << ", errstring:" << strerror(errno) << endl;
return 2;
}
cout << "open fifo success..." << endl;
char buffer[1024];
while (true)
{
ssize_t s = read(rfd, buffer, sizeof(buffer) - 1);
if (s > 0)
{
buffer[s] = '\0';
cout << "Client say# " << buffer << endl;
}
}
close(rfd);
cout << "close fifo success..." << endl;
return 0;
}
客户端的代码,如下:
#include "comm.h"
using namespace std;
int main()
{
int wfd = open(FILENAME, O_WRONLY);
if (wfd < 0)
{
cerr << "errno: " << errno << ", errstring:" << strerror(errno) << endl;
return 1;
}
string message;
while (true)
{
cout << "Please Enter# ";
getline(cin, message);
ssize_t s = write(wfd, message.c_str(), message.size());
if (s < 0)
{
cerr << "errno: " << errno << ", errstring:" << strerror(errno) << endl;
return 2;
}
}
close(wfd);
std::cout << "close fifo success..." << std::endl;
return 0;
}
让客户端和服务端包含同一个头文件,该头文件当中提供这个共用的命名管道文件的文件名和共用的头文件。
// comm.h
#pragma once
#include <cerrno>
#include <cstdio>
#include <string>
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FILENAME "myfifo" //让客户端和服务端使用同一个命名管道
先将服务端进程运行起来,之后我们就能在客户端看到这个已经被创建的命名管道文件:
使用ps axj搭配grep来查询这两个进程:
ps axj | head -1 && ps axj| grep -E '\./server|\./client' | grep -v grep
发现这两个进程确实是两个毫不相关的进程,因为它们的PID和PPID都不相同。也就证明了,命名管道是可以实现两个毫不相关进程之间的通信的。
有个小问题是,如果跑完一次想要再运行,命名管道文件mypipe还依然存在,会引发报错程序直接退出:
优化一下server.cpp:
#include "comm.h" using namespace std; bool MakeFifo() { int n = mkfifo(FILENAME, 0666); if (n < 0) { cerr << "mkfifo failed, errno: " << errno << ", errstring:" << strerror(errno) << endl; return false; } cout << "mkfifo success..." << endl; return true; } int main() { Start: int rfd = open(FILENAME, O_RDONLY); if (rfd < 0) // 优化:如果open失败,就创建fifo管道文件,然后回到start再open { cerr << "errno: " << errno << ", errstring:" << strerror(errno) << endl; if (MakeFifo()) goto Start; else return 1; } cout << "open fifo success..." << endl; char buffer[1024]; while (true) { ssize_t s = read(rfd, buffer, sizeof(buffer) - 1); if (s > 0) { buffer[s] = '\0'; cout << "Client say# " << buffer << endl; } } close(rfd); cout << "close fifo success..." << endl; return 0; }
四、匿名管道与命名管道的区别和联系
谈区别之前,先提一下系统调用 open
、read
、write
等系统调用,它们对于这两种管道都有几乎相同的行为,可能会发生阻塞等待的情况如下:
open
系统调用:
- 打开管道的读端或写端时,如果另一端尚未被打开,打开调用可能会阻塞等待。
- 当打开读端时,通常期望有其他进程打开相应的写端,否则读端打开可能会一直阻塞,直到写端被打开。
- 当打开写端时,通常期望有其他进程打开相应的读端,否则写端打开可能会一直阻塞,直到读端被打开。
read
系统调用:
- 当从管道中读取数据时,如果管道为空,
read
调用可能会阻塞等待,直到有数据可读。- 如果管道的写端已经关闭,并且没有数据可供读取,
read
调用将返回0,表示已经读到了文件末尾(EOF)。
write
系统调用:
- 当向管道中写入数据时,如果管道满了,
write
调用可能会阻塞等待,直到有空间可写。- 如果管道的读端已经关闭,而且没有进程读取数据,
write
调用可能会导致信号SIGPIPE
被发送给写入进程。
梳理两种管道的区别:
FIFO(命名管道)与pipe(匿名管道)之间最大的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。
-
匿名管道(Anonymous Pipe):
- 匿名管道是通过调用
pipe
函数创建的,通常用于父子进程之间的通信。 - 匿名管道没有文件系统中的名字,只能用于相关进程之间的通信。
- 读端和写端的打开行为以及数据的传输与上述描述一致。
- 当读取所有数据后,进程试图继续读取时,
read
返回0,表示已经读到了文件末尾(EOF)。
- 匿名管道是通过调用
-
命名管道(Named Pipe,Linux下称为FIFO):
- 命名管道是通过调用
mkfifo
函数创建的,通常用于无关联进程之间的通信。 - 命名管道在文件系统中有一个名字,可以被多个进程通过这个名字进行访问。
open
、read
、write
等系统调用的行为与上述描述基本一致。- 与匿名管道相似,当读取所有数据后,进程试图继续读取时,
read
返回0。
- 命名管道是通过调用
需要注意,对于命名管道,当所有写端都被关闭而读端仍然打开时,后续的读取操作可能会阻塞等待。这是因为当所有写入端关闭后,读取端可能仍在等待写入端的数据,直到有新的数据写入或者读取端被关闭。这与匿名管道有些许不同。