进程间的通信(一)管道
进程间的通信(一)管道
什么是管道
管道是Unix中最古老的进程间通信的形式。
我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
管道分为匿名管道(pipe)和命名管道(mkfifo)
匿名管道
什么是匿名管道
在Linux中,匿名管道(pipe)是一种用于进程间通信的机制,特别适用于具有亲缘关系的进程(如父子进程)之间的通信。匿名管道实际上是由内核管理的一块缓冲区,通过让不同的进程都能访问同一块缓冲区来实现进程间通信。
匿名管道的特点
•单向通信:管道是一个只能单向通信的通信信道。数据只能从一个方向流动,即从一个进程写入,然后从另一个进程读取。
•面向字节流:管道是面向字节流的,这意味着数据以字节为单位进行传输。
•父子通信:匿名管道主要用于父子进程或具有亲缘关系的进程之间的通信。
•自带同步机制:管道具有原子性写入的特性,并且自带同步机制。
创建一个匿名管道
#include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码
创造匿名管道
int main()
{
int pipefd[N] = {0};
int n = pipe(pipefd);
}
特别适用于具有亲缘关系的进程(如父子进程)之间的通信
例如,父进程可以首先创建匿名管道,然后创建子进程。子进程通过管道的写端向管道中写入数据,而父进程则通过管道的读端从管道中读取数据,并且父子进程关掉不用的描述符,这种方式实现了父子进程之间的数据传递。
代码如下
int main()
{
int pipefd[N] = {0};
int n = pipe(pipefd); //创造管道
if (n < 0)
return 1;
// child -> w, father->r,这里子进程负责写,父进程负责读
pid_t id = fork(); //创造子进程
if (id < 0) //创造子进程失败
return 2;
if (id == 0)
{
// child
close(pipefd[0]); //子进程关掉读端
// IPC code
Writer(pipefd[1]); //Writer函数为写入函数
close(pipefd[1]); //写完后关掉子进程写端文件描述符
exit(0); //子进程退出
}
// father
close(pipefd[1]); //父进程关掉写端
// IPC code
Reader(pipefd[0]); //Reader函数为读取函数
close(pipefd[0]); //读完后关掉父进程读端文件描述符
int status = 0;
pid_t rid = waitpid(id, &status, 0); //回收子进程
return 0;
}
匿名管道读写规则
O_NONBLOCK 指的是是否是非阻塞读取或写入
•当没有数据可读时
O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
•当管道满的时候
O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
如果所有管道写端对应的文件描述符被关闭,则read返回0
如果所有管道读端对应的文件描述符被关闭,则write操作会产生信SIGPIPE,进而可能导致write进程退出
命名管道
什么是命名管道
在Linux中,命名管道(named pipe)也被称为FIFO(First In First Out,先进先出)文件,它允许没有亲缘关系的进程之间进行通信。与匿名管道不同,命名管道在文件系统中有一个对应的文件名,因此任何进程都可以通过文件名来访问它。
命名管道的特点
•命名和持久性:命名管道在文件系统中有一个唯一的名称,并且可以在多个进程之间共享和持久存在,直到显式地被删除。
•双向通信:虽然通常用作单向的数据流,但命名管道在技术上支持双向通信。两个进程可以同时打开同一个命名管道,一个用于写入,另一个用于读取。
•阻塞与非阻塞:命名管道的读写操作可以是阻塞的或非阻塞的,这取决于打开管道时设置的标志。
•字节流:命名管道传输的数据是字节流,与匿名管道类似。
创建一个命名管道
1,命名管道可以从命令行上创建,命令行方法是使用下面这个命令:
mkfifo [OPTION]... NAME...
OPTION:可选参,用于指定一些操作选项,下面详细介绍。
NAME:必选参,用于指定要创建的有名管道的名称。
其中mkfifo 命令的常用选项如下:
-m 权限:设置管道的权限,其格式与 chmod 命令相同。
-Z 文件类型:指定创建管道的 SELinux 上下文。
--help:显示帮助信息。
--version:显示版本信息。
2,命名管道也可以从程序里创建,相关函数有:
#include <sys/types.h>, #include <sys/stat.h>
函数功能:创建有名管道。
原型:
int mkfifo(const char*filename,mode_t mode)。
参数1(filename):是将要在文件系统中创建的一个专用文件。
参数2(mode):用来规定FIFO的读写权限。
函数返回值:成功返回0,失败返回-1。
创造命名管道
int main(int argc, char *argv[])
{
mkfifo("p2", 0644);
return 0;
}
它允许没有亲缘关系的进程之间进行通信
例如,假设有两个进程A和B,它们想要通过命名管道进行通信。可以首先使用mkfifo命令创建一个命名管道,然后进程client和server分别打开该命名管道,一个用于写入,另一个用于读取。进程client向管道中写入数据,进程server从管道中读取数据,从而实现了进程间的通信。
大致代码如下
comm.hpp
class Init
{
public:
Init()
{
// 创建管道
int n = mkfifo(FIFO_FILE, MODE);
}
~Init()
{
int m = unlink(FIFO_FILE);
}
};
client.cc
int main()
{
int fd = open(FIFO_FILE, O_WRONLY); //以写方式打开命名管道
//向管道中写入数据
string line;
while(true)
{
cout << "Please Enter@ ";
getline(cin, line);
write(fd, line.c_str(), line.size());
}
close(fd);
return 0;
}
server.cc
int main()
{
// 打开管道
int fd = open(FIFO_FILE, O_RDONLY); // 等待写入方打开之后,自己才会打开文件,向后执行, open 阻塞了!
//从管道中读取数据
while (true)
{
char buffer[1024] = {0};
int x = read(fd, buffer, sizeof(buffer));
if (x > 0)
{
buffer[x] = 0;
cout << "client say# " << buffer << endl;
}
else if (x == 0)
{
log(Debug, "client quit, me too!, error string: %s, error code: %d", strerror(errno), errno);
break;
}
else
break;
}
close(fd);
return 0;
}
命名管道的打开规则
•如果当前打开操作是为读而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
O_NONBLOCK enable:立刻返回成功
•如果当前打开操作是为写而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
匿名管道与命名管道的区别
• 匿名管道由pipe函数创建并打开。
• 命名管道由mkfifo函数创建,打开用open
• FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。