文章目录
- Linux 进程间通信的方式
- 管道
- 匿名管道
- 有名管道
Linux 进程间通信的方式
管道
管道特点
- 管道其实是一个在内核内存中维护的缓冲器,这个缓冲器的存储能力是有限的,不同的操作系统大小不一定相同
- 管道拥有文件的特质:读操作、写操作
- 匿名管道没有文件实体
- 有名管道有文件实体,但不存储数据。可以按照操作文件的方式对管道进行操作
- 一个管道是一个字节流,使用管道时不存在消息或者消息边界的概念,从管道读取数据的进程可以读取任意大小的数据块,而不管写入进程写入管道的数据块的大小是多少。
- 通过管道传递的数据是顺序的,从管道中读取出来的字节的顺序和它们被写入管道的顺序是完全一样的
- 在管道中的数据的传递方向是单向的,一端用于写入,一端用于读取,管道是半双工的。(半双工(Half Duplex)数据传输指数据可以在一个信号载体的两个方向上传输,但是不能同时传输。)
- 从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写更多的数据,在管道中无法使用 lseek() 来随机的访问数据
- 匿名管道只能在具有公共祖先的进程(父进程与子进程,或者两个兄弟进程,具有亲缘关系)之间使用。【子进程fork之后与父进程共享文件描述符(✳理解)】
匿名管道
-
管道也叫无名(匿名)管道,它是是 UNIX 系统 IPC(进程间通信)的最古老形式,所有的 UNIX 系统都支持这种通信机制。
-
example:统计一个目录中文件的数目命令:ls | wc –l,为了执行该命令,shell 创建了两个进程来分别执行 ls 和 wc。
匿名管道创建
-
int pipe(int pipefd[2])
- 功能:创建一个匿名管道,用来进程间通信。
- 参数:int pipefd[2] 这个数组是一个传出参数。
pipefd[0] 对应的是管道的读端
pipefd[1] 对应的是管道的写端 - 返回值:成功 0,失败 -1
注意:
- 管道默认是阻塞的:如果管道中没有数据,read阻塞,如果管道满了,write阻塞
- 匿名管道只能用于具有关系的进程之间的通信(父子进程,兄弟进程)
program1
: 子进程发送数据给父进程,父进程读取到数据输出
#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);
if(ret==-1){
perror("pipe");
exit(0);
}
pid_t pid=fork();
//父进程
if(pid>0){
char buf[1024] = {0};
int len = read(pipefd[0], buf, sizeof(buf));
printf("parent recv : %s, pid : %d\n", buf, getpid());
}else if(pid==0){
char * str = "hello,i am child";
write(pipefd[1], str, strlen(str));
}
return 0;
}
输出结果:
(base) user@ubuntu:~/Desktop/OS/NiuKe$ gcc -o test test.c
(base) user@ubuntu:~/Desktop/OS/NiuKe$ ./test
parent recv : hello,i am child, pid : 3552
program2
: 父子进程相互通信
#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);
if(ret==-1){
perror("pipe");
exit(0);
}
pid_t pid=fork();
//父进程
if(pid>0){
printf("i am parent process, pid : %d\n", getpid());
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());
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());
}
}
return 0;
}
输出结果
(base) user@ubuntu:~/Desktop/OS/NiuKe$ gcc -o test test.c
(base) user@ubuntu:~/Desktop/OS/NiuKe$ ./test
i am parent process, pid : 3630
i am child process, pid : 3631
parent recv : hello,i am child, pid : 3630
child recv : hello,i am parent, pid : 3631
parent recv : hello,i am child, pid : 3630
child recv : hello,i am parent, pid : 3631
parent recv : hello,i am child, pid : 3630
【补充】:由于上面program2
的代码加上了sleep,可以正常运行。如果去掉这句语句,则会导致进程自己往管道写同时又从管道读出的情况。因此,这个问题的解决办法就是提前关闭不需要的端口(读or写)。
总结
- 读管道
- 管道中有数据,read返回实际读到的字节数
- 管道中无数据
- 写端被全部关闭,read返回0(相当于读到文件的末尾)
- 写端没有完全关闭,read阻塞等待
- 写管道
- 管道读端全部被关闭,进程异常终止(进程收到SIGPIPE信号)
- 管道读端没有全部关闭:
- 管道已满,write阻塞
- 管道没有满,write将数据写入,并返回实际写入的字节数
有名管道
-
匿名管道,由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道(
FIFO
),也叫命名管道、FIFO
文件。 -
有名管道(FIFO)
不同于匿名管道之处在于它提供了一个路径名与之关联,以FIFO
的文件形式存在于文件系统中,并且其打开方式与打开一个普通文件是一样的,这样即使与FIFO
的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO
相互通信,因此,通过FIFO
不相关的进程也能交换数据 -
一旦打开了
FIFO
,就能在它上面使用与操作匿名管道和其他文件的系统调用一样的I/O系统调用了(如read()、write()和close())。与管道一样,FIFO
也有一个写入端和读取端,并且从管道中读取数据的顺序与写入的顺序是一样的。FIFO
的名称也由此而来:先入先出 -
有名管道(FIFO)
和匿名管道(pipe)
有一些特点是相同的,不一样的地方在于FIFO
在文件系统中作为一个特殊文件存在,但FIFO
中的内容却存放在内存中。- 当使用
FIFO
的进程退出后,FIFO
文件将继续保存在文件系统中以便以后使用。 FIFO
有名字,不相关的进程可以通过打开有名管道进行通信。
有名管道创建
- mkfifo 文件名(shell命令创建)
- int mkfifo(const char *pathname, mode_t mode);
- pathname:管道名称的路径
- mode:文件的权限
program
:两进程通过有名管道通信(单一发送)
写端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
// 向管道中写数据
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;
}
读端
#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};
// 这里不能写strlen(buf) 因为这里的含义是每次按固定长度读取,最开始strlen(buf)=0
int len = read(fd, buf, sizeof(buf));
if(len == 0) {
printf("写端断开连接了...\n");
break;
}
printf("recv buf : %s\n", buf);
}
close(fd);
return 0;
}
运行
(不管哪端先执行)