Linux系统:管道通信
文章目录
- 前言
- 一,管道是什么?
- 二,匿名管道
- 2-1 父子进程&匿名管道
- 2-2管道读写规则
- 2-3管道的特点
- 三,命名管道
- 3-1 创建命名管道
- 3-2匿名管道和命名管道的区别
- 3-3命名管道的打开规则
前言
在 Linux 系统中,管道(pipe)
是一种最基础的进程间通信方式,它就像一根虚拟的通道,让一个进程写入的数据能够被另一个进程读取。借助管道,不同的进程可以协同工作,实现数据的传递与共享。这种机制不仅在命令行中被广泛使用(例如 ls | grep txt
),也是理解 Linux 通信原理和系统编程的重要起点。
一,管道是什么?
管道是连接两个进程的桥梁,它允许一个进程把数据传递给另一个进程,从而实现进程间的通信。
二,匿名管道
匿名管道就是通过 pipe()
创建的“临时通道”,只能在相关进程之间使用,不能像命名管道那样跨无关进程访问。
pipe()
在unistd.h
头文件下
#include<unistd.h>
int pipe(fd[2]);
//fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
//返回值:成功返回0,失败返回错误代码
演示代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>int main()
{int fds[2];char buf[100];int len;if(pipe(fds)==-1){perror("make pipe");exit(1);}printf("%d and %d\n", fds[0], fds[1]);while(fgets(buf,100,stdin)){len = strlen(buf);if(write(fds[1],buf,len)!=len){perror("write to pipe");break;}memset(buf,0x00,sizeof(buf));if((len=read(fds[0],buf,100))==-1){perror("read from pipe");break;}if(write(1,buf,len)!=len){perror("write to stdout");break;}}
}
演示结果
root@hcss-ecs-f59a:/gch/code/HaoHao/day12# ./exe1
3 and 4
Please input some text:
helloworld
read from stdin:
helloworld
2-1 父子进程&匿名管道
一般来讲匿名管道通常使用在有亲属关系的进程中,也就是子进程和父进程,子进程会继承父进程的文件描述符表,那么当父进程pipe
创建两个文件描述符读和写,子进程也会继承下来,一个进程关闭写端,一个进程关闭读端就实现了进程间的单向通信。
演示代码
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#define ERR_EXIT(m)\
do\
{\perror(m);\exit(EXIT_FAILURE);\
}while(0)
int main(int argc,char*argv[])
{int pipefd[2];if(pipe(pipefd) == -1)ERR_EXIT("pipe error");pid_t pid;pid =fork();if(pid == -1){ERR_EXIT("fork error");}if(pid == 0){close(pipefd[0]);write(pipefd[1],"hello",5);close(pipefd[1]);exit(EXIT_SUCCESS);}close(pipefd[1]);char buf[10]={0};read(pipefd[0],buf,10);printf("buf=%s\n",buf);return 0;
}
演示结果
gch@hcss-ecs-f59a:/gch/code/HaoHao/day12$ ./exe
buf=hello
2-2管道读写规则
当没有数据可读时
(管道为空)- O_NONBLOCK disable(默认阻塞 I/O)
read
会阻塞,进程停在read
调用那里,直到有数据写入管道才能继续→ 典型的生产者-消费者模型:消费者在等生产者。- O_NONBLOCK enable(非阻塞 I/O)
read
直接返回 -1,并把errno
设置为EAGAIN
,告诉你“现在没有数据,稍后再试”→ 适合做 轮询/异步 I/O。
当管道满的时候
(Linux 管道有大小限制,通常是64 KB
左右)- O_NONBLOCK disable(默认阻塞 I/O)
write
会阻塞,进程停在write
调用那里,直到有数据被读走腾出空间→ 防止数据丢失。- O_NONBLOCK enable(非阻塞 I/O)
write
直接返回 -1,并把errno
设置为EAGAIN
,表示“写不进去,等会再试”。
管道的写端都关闭了
- 如果所有 写端 关闭了,再去
read
: read
返回 0(表现得像读到文件结尾 EOF)→ 这时读端知道“再也不会有数据了”
- 如果所有 写端 关闭了,再去
管道的读端都关闭了
- 如果所有 读端 关闭了,再去
write
: - 内核会给写进程发一个 SIGPIPE 信号,默认行为是终止进程。即使程序捕获信号继续执行,write 也会返回 -1,
errno=EPIPE
。→ 这是防止“没人收数据还拼命写”导致浪费内存
- 如果所有 读端 关闭了,再去
2-3管道的特点
-
管道只能用在有
“亲戚关系”
的进程之间,比如一个进程先建好管道,再用fork
创建子进程,这样父子进程之间就能通过管道来通信。 -
管道就像水流一样,数据是连续流动的。
-
一般来说,进程一旦退出,管道也就自动释放了,所以它的寿命跟进程绑定。
-
管道的读写由内核负责协调,保证不会乱套。
-
管道是半双工的,也就是说数据只能单向传输;如果要实现双方都能说话,就得建两个管道。
三,命名管道
- 匿名管道有个限制:它只能用在有“血缘关系”的进程之间通信。比如一个进程先创建管道,再通过
fork
生成子进程,这样父子进程就能借助这个匿名管道来传数据。 - 但如果两个进程之间没有父子关系(比如两个完全独立启动的程序),匿名管道就帮不上忙了。这时就需要用到
命名管道(FIFO)
- 命名管道跟匿名管道的本质差不多,区别在于它有个名字(路径),存在于文件系统里。只要两个进程都知道这个路径,就能通过它来读写数据。这样即使它们没有任何“亲缘关系”,也能顺利通信。
3-1 创建命名管道
创建命名管道有两种方式,一种是函数创建
,一种是命令创建
mkfifo filename
int mkfifo(const char *filename,mode_t mode);
演示代码
#include<sys/stat.h>
int main()
{mkfifo("p2",664);return 0;
}
演示结果
root@hcss-ecs-f59a:/gch/code/HaoHao/day12# ll
total 44
drwxrwxr-x 2 gch gch 4096 Aug 19 15:57 ./
drwxrwxrwx 14 root root 4096 Aug 19 09:53 ../
-rwxr-xr-x 1 root root 16696 Aug 19 15:57 exe*
-rw-rw-r-- 1 gch gch 54 Aug 19 15:51 makefile
p-w---x--T 1 root root 0 Aug 19 15:57 p2|
-rwxrwxrwx 1 root root 71 Aug 19 15:57 text.c*
这里的这个p2就是我们运行程序创建的管道文件
3-2匿名管道和命名管道的区别
- 匿名管道:通过
pipe()
函数直接创建并打开。 - 命名管道:需要先用
mkfifo()
创建一个特殊文件,然后再通过open()
打开使用。 - 它们的本质区别只在于==“创建和打开”的方式不同==。一旦这一步完成,后续的读写语义和使用方式基本是一样的。
3-3命名管道的打开规则
- 以读方式打开
FIFO
- 如果没有设置 O_NONBLOCK(阻塞模式):会一直等待,直到有另一个进程以写方式打开该
FIFO
。 - 如果设置了 O_NONBLOCK(非阻塞模式):立即返回成功,即使还没有写端。
- 如果没有设置 O_NONBLOCK(阻塞模式):会一直等待,直到有另一个进程以写方式打开该
- 以写方式打开 FIFO
- 如果没有设置 O_NONBLOCK(阻塞模式):会一直等待,直到有进程以读方式打开该
FIFO
。 - 如果设置了 O_NONBLOCK(非阻塞模式):会立刻返回失败,并设置
errno = ENXIO
,表示没有可用的读端。
- 如果没有设置 O_NONBLOCK(阻塞模式):会一直等待,直到有进程以读方式打开该
演示代码
clientPipe.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#define ERR_EXIT(m)\
do\
{ \perror(m);\exit(EXIT_FAILURE);\
}while(0)
int main()
{int wfd = open("mypipe",O_WRONLY);if(wfd<0){ERR_EXIT("open");}char buf[1024];while(1){buf[0]=0;printf("please Enter#");fflush(stdout);ssize_t s= read(0,buf,sizeof(buf)-1);if(s>0){buf[s]=0;write(wfd,buf,strlen(buf));}else if(s<=0){ERR_EXIT("read");}}close(wfd);return 0;
}
serverPipe.c
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#define ERR_EXIT(m)\
do\
{\perror(m);\exit(EXIT_FAILURE);\
}while(0);
int main()
{umask(0);if(mkfifo("mypipe",0664)<0){ERR_EXIT("mkfifo");}int rfd = open("mypipe",O_RDONLY);if(rfd<0){ERR_EXIT("open");}char buf[1024];while(1){buf[0]=0;printf("please wait...\n");ssize_t s = read(rfd,buf,sizeof(buf));if(s>0){buf[s-1]=0;printf("client say#%s\n",buf);}else if(s==0){printf("client quit,exit now!\n");exit(EXIT_SUCCESS);}else{ERR_EXIT("read");}}close(rfd);return 0;
}
makefile
.PHONY:all
all: clientPipe serverPipe
clientPipe:clientPipe.cgcc -o $@ $^
serverPipe:serverPipe.cgcc -o $@ $^
.PHONY:clean
clean:rm -f clientPipe serverPipe
演示结果:
进程一
root@hcss-ecs-f59a:/gch/code/HaoHao/day13# ./clientPipe
please Enter#hello,world
进程二
root@hcss-ecs-f59a:/gch/code/HaoHao/day13# ./serverPipe
please wait...
client say#hello,world
当我用完后可以使用==unlink(“./log.txt”);==关闭管道