Linux进程间通信的方式
在Linux系统中,进程间通信(IPC)可以通过多种不同的方式实现,这些方式各有其特点和适用场景。下面是一些Linux系统中常用的进程间通信机制:
管道(Pipes)和命名管道(Named Pipes/FIFOs):
- 无名管道:主要用于父子进程或同一进程派生出的子进程之间的通信。数据单向流动。
- 命名管道(FIFO):与无名管道类似,但可以在不相关的进程之间进行通信,因为它们通过文件系统的名字进行访问。
信号(Signals):
- 信号是一种由操作系统提供的通知机制,用于通知进程发生了某种事件(如:终止(SIGTERM)、中断(SIGINT)等)。
消息队列(Message Queues):
- 消息队列允许进程发送和接收消息。不同于管道,消息队列通过关键字或标识符访问,可以实现不同进程间的复杂通信。
信号量(Semaphores):
- 主要用于进程同步,保证多个进程可以安全地共享资源,如共享内存区域。
共享内存(Shared Memory):
- 允许多个进程共享同一块内存区域,是最快的IPC方式,因为它避免了数据的复制。然而,访问共享内存通常需要配合信号量或其他同步机制来防止竞争条件。
套接字(Sockets):
- 套接字不仅可以用于网络通信,还可以用于在同一台机器上运行的不同进程之间的通信(使用UNIX域套接字)。
文件锁定(File Locking):
- 通过文件系统对文件部分或全部加锁,可以控制多个进程对文件的访问,防止数据损坏。
内存映射文件(Memory-Mapped Files):
- 通过映射文件的一部分或全部到进程的地址空间,可以实现进程间的共享数据。
每种IPC机制都有其特定的使用场景和优势。例如,消息队列、信号量和共享内存广泛用于需要高性能和复杂通信模式的系统级应用,而套接字则适用于需要网络通信以及本地进程通信的应用程序。选择合适的IPC方式取决于应用的需求、性能考虑以及开发的复杂性。
匿名(无名)管道
匿名管道(Anonymous Pipes)是Linux和其他Unix-like操作系统中一种基本的进程间通信(IPC)机制,用于在有共同祖先的进程之间传递信息,通常在父子进程间使用。匿名管道的特点和工作方式如下:
特点
- 单向数据流:匿名管道只允许数据在一个方向上流动,要么是从父进程到子进程,要么是从子进程到父进程。如果需要双向通信,通常需要创建两个管道。
- 内存中的数据传输:管道在内存中创建,不会有物理磁盘的参与,因此读写速度较快。
- 生命周期:匿名管道的生命周期通常仅限于创建它的进程及其子进程,它们不存在于文件系统中,不能被其他不相关的进程访问。
工作方式
- 创建:在Linux中,通过调用
pipe()
系统调用创建匿名管道。该调用会创建一个管道,并返回两个文件描述符,一个用于读取(通常是fd[0]
),一个用于写入(通常是fd[1]
)。 - 使用:创建管道后,通常会进行如下步骤:
- 父进程通过调用
fork()
创建一个子进程。 - 根据需要的通信方向,父进程和子进程中各自关闭不需要的文件描述符(例如,如果信息从父进程流向子进程,父进程会关闭读描述符,子进程会关闭写描述符)。
- 使用剩余的文件描述符来进行数据的读写。
- 父进程通过调用
- 数据传输:写入管道的数据会被存储在由操作系统管理的缓冲区中,直到被读取。如果管道的写端被关闭,读端仍然可以读取缓冲区内剩余的数据。如果读端关闭,写端上的写操作会收到
SIGPIPE
信号。 - 关闭:通信完成后,进程应关闭其文件描述符。当所有的文件描述符都被关闭后,管道被销毁。
用例
匿名管道广泛用于父子进程之间的简单通信场景,例如,在一个shell脚本中使用管道将一个命令的输出直接传递给另一个命令。
统计一个目录中文件的数目命令:ls | wc –l,为了执行该命令,shell 创建了两 个进程来分别执行 ls 和 wc。
匿名管道是理解和使用进程间通信的基础,虽然它的功能相对简单,但在很多基本的父子进程通信场景中非常有效。
管道的特点
管道其实是一个在内核内存中维护的缓冲器,这个缓冲器的存储能力是有限的,不同的 操作系统大小不一定相同。
◼ 管道拥有文件的特质:读操作、写操作,匿名管道没有文件实体,有名管道有文件实体, 但不存储数据。可以按照操作文件的方式对管道进行操作。
◼ 一个管道是一个字节流,使用管道时不存在消息或者消息边界的概念,从管道读取数据 的进程可以读取任意大小的数据块,而不管写入进程写入管道的数据块的大小是多少。
◼ 通过管道传递的数据是顺序的,从管道中读取出来的字节的顺序和它们被写入管道的顺 序是完全一样的。
在管道中的数据的传递方向是单向的,一端用于写入,一端用于读取,管道是半双工的。
◼ 从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写 更多的数据,在管道中无法使用 lseek() 来随机的访问数据。
◼ 匿名管道只能在具有公共祖先的进程(父进程与子进程,或者两个兄弟进程,具有亲缘 关系)之间使用。
为什么可以使用管道进行进程间通信
子进程fork出来后和父进程共享描述符
管道的数据结构
管道作为一种在Unix和类Unix系统中实现进程间通信的机制,本质上是由操作系统内核管理的一个缓冲区,它们的实现涉及到一些特定的数据结构。这些数据结构是操作系统内核的一部分,负责管理和调度数据的传输。下面是管道在内核中的基本数据结构组成和工作原理:
1. 管道缓冲区
管道的核心是一个环形缓冲区(circular buffer),用于存储从写进程传输到读进程的数据。这个缓冲区的大小在不同的操作系统中可能有所不同,但通常是固定的(如在Linux中,默认大小可能是4KB或更大)。
2. 文件描述符
管道通过一对文件描述符(file descriptors)提供给用户空间的进程。这对文件描述符分别代表管道的读端和写端:
- 读文件描述符:允许进程从管道中读取数据。
- 写文件描述符:允许进程向管道写入数据。
3. 内核结构体
在内核中,管道通常通过一个结构体表示,这个结构体包含了管理管道所需的所有信息:
- 缓冲区指针:指向实际存储数据的内存区域。
- 读写指针:分别指示缓冲区中下一个可读和可写的位置。
- 缓冲区大小:指示缓冲区的总容量。
- 引用计数:追踪有多少文件描述符指向该管道,确保在所有相关的文件描述符关闭之前,管道资源保持分配状态。
4. 同步机制
为了处理多个进程对同一管道的并发访问,内核还必须实现同步机制,如互斥锁(mutexes)或信号量(semaphores)。这些机制确保在一个时刻只有一个进程可以对管道进行写操作,同时也协调多个读操作者。
5. 阻塞和唤醒机制
内核使用条件变量或其他调度机制来管理阻塞和唤醒进程:
- 如果管道的缓冲区满了,写操作将阻塞,直到有空间可用。
- 如果管道的缓冲区为空,读操作将阻塞,直到有数据可读。
- 相关的系统调用会挂起进程,直到条件满足(数据可读或可写)。
通过这些数据结构和管理机制,操作系统能够高效地实现管道通信,同时保证数据的完整性和进程间的同步。这些底层实现对于最终用户和应用程序开发人员来说是透明的,他们主要通过标准的系统调用如 pipe()
, read()
, 和 write()
来使用管道功能。
父子进程通过匿名管道通信
pipe函数
在 Linux 和其他类 Unix 系统中,pipe
是一个用于进程间通信的系统调用。pipe
允许数据从一个进程流向另一个进程,使用了所谓的管道机制。这种机制是通过创建一个简单的通信方式,即一个数据流,从一个进程的输出作为另一个进程的输入。
基本概念
**管道在 Linux 中可以理解为一个缓冲区,操作系统允许一个进程写入特定的数据,而另一个进程可以从中读取数据。**通常,这是通过将一个进程的输出(标准输出)直接连接到另一个进程的输入(标准输入)来实现的。
使用示例
在命令行中,使用管道的一个非常常见的例子是 ls | grep something
,这里的 |
符号表示一个管道,它将 ls
命令的输出直接传递给 grep
命令。
系统调用
在更底层,当我们在 C 语言中使用 pipe
系统调用时,通常会涉及以下步骤:
- 创建管道: 使用
pipe()
函数创建一个管道,该函数会生成两个文件描述符,一个用于读,一个用于写。 - 数据传输: 写进程会写入数据到写文件描述符,读进程则从读文件描述符读取数据。
- 关闭管道: 当数据传输完成后,应该关闭不再需要的文件描述符。
C 语言中的 pipe
示例
下面是一个使用 pipe
的 C 程序示例,展示了如何在父进程和子进程之间传递数据:
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
int main() {
int pipefd[2]; // 文件描述符数组
char buf;
char *message = "Hello from parent!";
if (pipe(pipefd) == -1) {
perror("pipe");
return 1;
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return 1;
}
if (pid == 0) { // 子进程
close(pipefd[1]); // 关闭写端
while (read(pipefd[0], &buf, 1) > 0) {
write(STDOUT_FILENO, &buf, 1);
}
write(STDOUT_FILENO, "\n", 1);
close(pipefd[0]);
} else { // 父进程
close(pipefd[0]); // 关闭读端
write(pipefd[1], message, strlen(message));
close(pipefd[1]);
wait(NULL); // 等待子进程结束
}
return 0;
}
在这个示例中,父进程创建了一个管道和一个子进程。父进程将一个消息写入管道的写端,子进程从读端读取这个消息并将其输出到标准输出。这是一个简单的单向通信示例。
/*
#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建一个匿名管道,用来进程间通信。
参数:int pipefd[2] 这个数组是一个传出参数。
pipefd[0] 对应的是管道的读端
pipefd[1] 对应的是管道的写端
返回值:
成功 0
失败 -1
管道默认是阻塞的:如果管道中没有数据,read阻塞,如果管道满了,write阻塞
注意:匿名管道只能用于具有关系的进程之间的通信(父子进程,兄弟进程)
*/
// 子进程发送数据给父进程,父进程读取到数据输出
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
// 在fork之前创建管道
int pipefd[2];
int ret = pipe(pipefd);
if(ret == -1) {
perror("pipe");
exit(0);
}
// 创建子进程
pid_t pid = fork();
if(pid > 0) {
// 父进程
printf("i am parent process, pid : %d\n", getpid());
// 关闭写端
close(pipefd[1]);
// 从管道的读取端读取数据
char buf[1024] = {0};
while(1) {
int len = read(pipefd[0], buf, sizeof(buf));
printf("parent recv : %s, pid : %d\n", buf, getpid());
// 向管道中写入数据
//char * str = "hello,i am parent";
//write(pipefd[1], str, strlen(str));
//sleep(1);
}
} else if(pid == 0){
// 子进程
printf("i am child process, pid : %d\n", getpid());
// 关闭读端
close(pipefd[0]);
char buf[1024] = {0};
while(1) {
// 向管道中写入数据
char * str = "hello,i am child";
write(pipefd[1], str, strlen(str));
//sleep(1);
// int len = read(pipefd[0], buf, sizeof(buf));
// printf("child recv : %s, pid : %d\n", buf, getpid());
// bzero(buf, 1024);
}
}
return 0;
}
fpathconfig函数
在 Unix 和类 Unix 系统中,fpathconf
函数用于获取与文件相关的配置信息。这是 POSIX 标准的一部分,用于在运行时查询系统或文件特定的限制和选项。fpathconf
通过指定的文件描述符来查询这些属性,而 pathconf
通过文件路径来查询。
函数原型
fpathconf
函数的原型定义在 <unistd.h>
头文件中,其形式如下:
long fpathconf(int fd, int name);
参数
- fd:文件描述符,它引用已打开的文件或设备。
- name:一个常量,指定要查询的配置值。这些常量通常以
_PC_
开头,例如_PC_NAME_MAX
,表示路径名中最长的文件名长度。
返回值
- 如果成功,
fpathconf
返回请求的配置值。 - 如果出现错误,函数返回
-1
并设置errno
来指示具体的错误类型。
常用的配置常量
_PC_NAME_MAX
:查询文件系统中单个文件的最大名称长度。_PC_PATH_MAX
:查询文件系统中的最大路径长度。_PC_MAX_CANON
:终端输入队列中的最大规范(canonical)输入行长度。_PC_MAX_INPUT
:终端输入队列中可存储的最大字符数。
示例
下面是一个使用 fpathconf
函数的示例,该示例检查特定文件描述符所指向的文件系统允许的最大文件名长度:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
int fd = open("example.txt", O_RDONLY);
if (fd == -1) {
perror("open");
return 1;
}
long max_name_len = fpathconf(fd, _PC_NAME_MAX);
if (max_name_len == -1) {
perror("fpathconf");
close(fd);
return 1;
}
printf("Maximum filename length for the file system containing 'example.txt' is %ld\n", max_name_len);
close(fd);
return 0;
}
在这个例子中,我们首先打开一个名为 example.txt
的文件来获取它的文件描述符,然后使用 fpathconf
来查询与这个文件所在文件系统的最大文件名长度。如果函数调用成功,它会打印这个限制。
注意事项
- 检查错误:总是检查
fpathconf
返回的值是否为-1
,因为这表明函数调用失败,同时errno
会被设置以提供错误详情。 - 文件描述符有效性:在调用
fpathconf
前确保文件描述符有效,它必须引用一个成功打开的文件。
fpathconf
和 pathconf
是在编程时根据系统限制动态调整程序行为的有用工具。
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
int pipefd[2];
int ret = pipe(pipefd);
// 获取管道的大小
long size = fpathconf(pipefd[0], _PC_PIPE_BUF);
printf("pipe size : %ld\n", size);
return 0;
}
匿名管道通信案例
/*
实现 ps aux | grep xxx 父子进程间通信
子进程: ps aux, 子进程结束后,将数据发送给父进程
父进程:获取到数据,过滤
pipe()
execlp()
子进程将标准输出 stdout_fileno 重定向到管道的写端。 dup2
*/
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wait.h>
int main() {
// 创建一个管道
int fd[2];
int ret = pipe(fd);
if(ret == -1) {
perror("pipe");
exit(0);
}
// 创建子进程
pid_t pid = fork();
if(pid > 0) {
// 父进程
// 关闭写端
close(fd[1]);
// 从管道中读取
char buf[1024] = {0};
int len = -1;
while((len = read(fd[0], buf, sizeof(buf) - 1)) > 0) {
// 过滤数据输出
printf("%s", buf);
memset(buf, 0, 1024);
}
wait(NULL);
} else if(pid == 0) {
// 子进程
// 关闭读端
close(fd[0]);
// 文件描述符的重定向 stdout_fileno -> fd[1] 这样才会向想要输出的地方写内容
dup2(fd[1], STDOUT_FILENO);
// 执行 ps aux
execlp("ps", "ps", "aux", NULL);
perror("execlp");
exit(0);
} else {
perror("fork");
exit(0);
}
return 0;
}
管道的读写特点和管道设置为非阻塞
/*
实现 ps aux | grep xxx 父子进程间通信
子进程: ps aux, 子进程结束后,将数据发送给父进程
父进程:获取到数据,过滤
pipe()
execlp()
子进程将标准输出 stdout_fileno 重定向到管道的写端。 dup2
*/
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wait.h>
int main() {
// 创建一个管道
int fd[2];
int ret = pipe(fd);
if(ret == -1) {
perror("pipe");
exit(0);
}
// 创建子进程
pid_t pid = fork();
if(pid > 0) {
// 父进程
// 关闭写端
close(fd[1]);
// 从管道中读取
char buf[1024] = {0};
int len = -1;
while((len = read(fd[0], buf, sizeof(buf) - 1)) > 0) {
// 过滤数据输出
printf("%s", buf);
memset(buf, 0, 1024);
}
wait(NULL);
} else if(pid == 0) {
// 子进程
// 关闭读端
close(fd[0]);
// 文件描述符的重定向 stdout_fileno -> fd[1] 这样才会向想要输出的地方写内容
dup2(fd[1], STDOUT_FILENO);
// 执行 ps aux
execlp("ps", "ps", "aux", NULL);
perror("execlp");
exit(0);
} else {
perror("fork");
exit(0);
}
return 0;
}
管道的读写特点:
使用管道时,需要注意以下几种特殊的情况(假设都是阻塞I/O操作)
1.所有的指向管道写端的文件描述符都关闭了(管道写端引用计数为0),有进程从管道的读端
读数据,那么管道中剩余的数据被读取以后,再次read会返回0,就像读到文件末尾一样。
2.如果有指向管道写端的文件描述符没有关闭(管道的写端引用计数大于0),而持有管道写端的进程
也没有往管道中写数据,这个时候有进程从管道中读取数据,那么管道中剩余的数据被读取后,
再次read会阻塞,直到管道中有数据可以读了才读取数据并返回。
3.如果所有指向管道读端的文件描述符都关闭了(管道的读端引用计数为0),这个时候有进程
向管道中写数据,那么该进程会收到一个信号SIGPIPE, 通常会导致进程异常终止。
4.如果有指向管道读端的文件描述符没有关闭(管道的读端引用计数大于0),而持有管道读端的进程
也没有从管道中读数据,这时有进程向管道中写数据,那么在管道被写满的时候再次write会阻塞,
直到管道中有空位置才能再次写入数据并返回。
总结:
读管道:
管道中有数据,read返回实际读到的字节数。
管道中无数据:
写端被全部关闭,read返回0(相当于读到文件的末尾)
写端没有完全关闭,read阻塞等待
写管道:
管道读端全部被关闭,进程异常终止(进程收到SIGPIPE信号)
管道读端没有全部关闭:
管道已满,write阻塞
管道没有满,write将数据写入,并返回实际写入的字节数
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
/*
设置管道非阻塞
int flags = fcntl(fd[0], F_GETFL); // 获取原来的flag
flags |= O_NONBLOCK; // 修改flag的值
fcntl(fd[0], F_SETFL, flags); // 设置新的flag
*/
int main() {
// 在fork之前创建管道
int pipefd[2];
int ret = pipe(pipefd);
if(ret == -1) {
perror("pipe");
exit(0);
}
// 创建子进程
pid_t pid = fork();
if(pid > 0) {
// 父进程
printf("i am parent process, pid : %d\n", getpid());
// 关闭写端
close(pipefd[1]);
// 从管道的读取端读取数据
char buf[1024] = {0};
int flags = fcntl(pipefd[0], F_GETFL); // 获取原来的flag
flags |= O_NONBLOCK; // 修改flag的值
fcntl(pipefd[0], F_SETFL, flags); // 设置新的flag
while(1) {
int len = read(pipefd[0], buf, sizeof(buf));
printf("len : %d\n", len);
printf("parent recv : %s, pid : %d\n", buf, getpid());
memset(buf, 0, 1024);
sleep(1);
}
} else if(pid == 0){
// 子进程
printf("i am child process, pid : %d\n", getpid());
// 关闭读端
close(pipefd[0]);
char buf[1024] = {0};
while(1) {
// 向管道中写入数据
char * str = "hello,i am child";
write(pipefd[1], str, strlen(str));
sleep(5);
}
}
return 0;
}
out
i am parent process, pid : 6364
i am child process, pid : 6365
len : 16
parent recv : hello,i am child, pid : 6364
len : -1
parent recv : , pid : 6364
len : -1
parent recv : , pid : 6364
len : -1
parent recv : , pid : 6364
len : -1
parent recv : , pid : 6364
len : 16
parent recv : hello,i am child, pid : 6364
len : -1
parent recv : , pid : 6364
有名管道
有名管道(Named Pipe),也被称为 FIFO(First In First Out),是一种允许进程之间进行通信的特殊类型的文件。与匿名管道不同,有名管道在文件系统中有一个实际的目录条目,这使得不相关的进程(即不是父子关系的进程)也能通过这种方式进行通信。
有名管道的工作方式类似于一个传统的管道,它遵循先进先出的原则,数据从一端写入,在另一端读出。不同之处在于,有名管道使用文件系统中的路径名来识别,任何有访问权限的进程都可以打开这个管道进行读写操作。
有名管道在 Unix 和类 Unix 系统(如 Linux)中非常常见。在编程时,可以使用诸如 mkfifo
命令(在 Unix 系统中)来创建有名管道。例如:
mkfifo /tmp/myfifo
这条命令创建了一个名为 myfifo
的管道文件在 /tmp
目录下。之后,不同的进程可以通过标准的文件操作,如打开、读取、写入和关闭文件,来交换数据。
有名管道和匿名管道区别
有名管道和匿名管道是两种不同的进程间通信(IPC)机制,它们主要用于在不同的进程之间传输数据。这两者的主要区别如下:
持久性和作用域:
- 有名管道(Named Pipe 或 FIFO)是在文件系统中具有实际条目的管道,因此它持久存在直到被明确删除。有名管道可以被系统上的任何进程访问,只要这些进程有适当的文件访问权限。
- 匿名管道通常用于父进程和子进程之间的通信,不存在于文件系统中,而是存在于内存中。当创建它的进程终止时,匿名管道也会被自动销毁。
创建方式:
- 有名管道可以通过命令(如 Unix 的
mkfifo
命令)或程序调用(如mkfifo()
系统调用)创建,创建后它会作为一个特殊文件出现在文件系统中。 - 匿名管道通常在程序中通过调用
pipe()
系统调用创建,它不对外显露为文件系统中的对象。
- 有名管道可以通过命令(如 Unix 的
通信能力:
- 有名管道可以在不相关的进程之间提供通信的能力,即使这些进程没有共同的祖先进程。
- 匿名管道仅限于有共同祖先的进程之间(例如父子进程)使用。
用途:
- 有名管道更适用于长期、灵活的通信需求,尤其是在需要跨越不同程序的情况下。
- 匿名管道由于其生命周期与创建它的进程紧密相关,通常用于实现简短的、定向的通信,如在父子进程间快速传递数据。
这些区别使得有名管道和匿名管道各自在不同的应用场景中更加适用。例如,有名管道更适合在一个守护进程和一些短暂的客户端进程之间进行通信,而匿名管道则适合在一个复杂的管道命令中,将多个子命令连接起来进行数据传输。
mkfifo
在编程中,mkfifo
函数是用来创建有名管道的系统调用,它提供了一种方式让不相关的进程可以通过有名管道进行通信。这个函数属于 POSIX 标准,广泛应用于 Unix-like 系统(如 Linux 和 macOS)。
函数原型
mkfifo
函数的原型定义在 <sys/stat.h>
头文件中,其标准语法如下:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
pathname
:这是一个字符串,指定要创建的有名管道的路径。mode
:指定新管道的权限。这些权限与chmod
命令用法相似,通常会与umask
的设置结合以确定最终的权限。例如,0666
表示所有用户都可以读写管道。
返回值
- 成功时,
mkfifo
返回0
。 - 失败时,返回
-1
并设置errno
来指示错误原因,如EEXIST
(指定的文件已存在)、ENOSPC
(设备上没有足够空间)等。
示例代码
下面是一个使用 mkfifo
创建有名管道的简单示例:
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
int main() {
const char *path = "/tmp/example_fifo";
// 创建有名管道,权限设置为所有用户可读写
if (mkfifo(path, 0666) == -1) {
if (errno != EEXIST) {
perror("mkfifo");
return 1;
}
}
printf("FIFO '%s' created successfully\n", path);
return 0;
}
注意事项
- 创建管道后,可以使用标准的文件操作函数(如
open
,read
,write
,close
)对管道进行操作。 - 与匿名管道不同,有名管道在文件系统中具有表示,因此可以被系统上任何有适当权限的进程访问。
mkfifo
不会打开管道;它仅仅创建一个代表管道的节点。实际的读写操作需要通过打开这个文件进行。
使用 mkfifo
函数创建的有名管道非常适用于需要跨程序或多个独立进程间进行持久通信的场景。
/*
创建fifo文件
1.通过命令: mkfifo 名字
2.通过函数:int mkfifo(const char *pathname, mode_t mode);
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数:
- pathname: 管道名称的路径
- mode: 文件的权限 和 open 的 mode 是一样的
是一个八进制的数
返回值:成功返回0,失败返回-1,并设置错误号
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
// 判断文件是否存在
int ret = access("fifo1", F_OK);
if(ret == -1) {
printf("管道不存在,创建管道\n");
ret = mkfifo("fifo1", 0664);
if(ret == -1) {
perror("mkfifo");
exit(0);
}
}
return 0;
}
write.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
// 向管道中写数据
/*
有名管道的注意事项:
1.一个为只读而打开一个管道的进程会阻塞,直到另外一个进程为只写打开管道
2.一个为只写而打开一个管道的进程会阻塞,直到另外一个进程为只读打开管道
读管道:
管道中有数据,read返回实际读到的字节数
管道中无数据:
管道写端被全部关闭,read返回0,(相当于读到文件末尾)
写端没有全部被关闭,read阻塞等待
写管道:
管道读端被全部关闭,进行异常终止(收到一个SIGPIPE信号)
管道读端没有全部关闭:
管道已经满了,write会阻塞
管道没有满,write将数据写入,并返回实际写入的字节数。
*/
int main() {
// 1.判断文件是否存在
int ret = access("test", F_OK);
if(ret == -1) {
printf("管道不存在,创建管道\n");
// 2.创建管道文件
ret = mkfifo("test", 0664);
if(ret == -1) {
perror("mkfifo");
exit(0);
}
}
// 3.以只写的方式打开管道
int fd = open("test", O_WRONLY);
if(fd == -1) {
perror("open");
exit(0);
}
// 写数据
for(int i = 0; i < 100; i++) {
char buf[1024];
sprintf(buf, "hello, %d\n", i);
printf("write data : %s\n", buf);
write(fd, buf, strlen(buf));
sleep(1);
}
close(fd);
return 0;
}
read.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
// 从管道中读取数据
int main() {
// 1.打开管道文件
int fd = open("test", O_RDONLY);
if(fd == -1) {
perror("open");
exit(0);
}
// 读数据
while(1) {
char buf[1024] = {0};
int len = read(fd, buf, sizeof(buf));
if(len == 0) {
printf("写端断开连接了...\n");
break;
}
printf("recv buf : %s\n", buf);
}
close(fd);
return 0;
}