更多linux知识:linux目录索引
1.什么是管道
把一个进程连接到另一个进程的一个数据流称为一个“管道”,通常是用作把一个进程的输出通过管道连接到另一个进程的输入。管道本质上是内核的一块缓存
例子:
在shell中输入命令:ls -l | grep string,我们知道ls命令(其实也是一个进程)会把当前目录中的文件都列出来,但是它不会直接输出,而是把本来要输出到屏幕上的数据通过管道输出到grep这个进程中,作为grep这个进程的输入,然后这个进程对输入的信息进行筛选,把存在string的信息的字符串(以行为单位)打印在屏幕上。
2. 管道的实现原理
3. 匿名管道
概念
匿名管道是基于文件描述符的通信方式。实现两个进程间的通信时必须通过fork创建子进程,实现父子进程之间的通信
读写规则
- 管道内没有数据时,读端(read)发生阻塞,等待有效数据进行读取
- 管道容量被数据填满时,写端(write)发生阻塞,等待进程将数据读走再进行写入
- 如果所有管道写端对应的文件描述符被关闭,read返回0,但会将之前管道里的数据读完
- 如果所有管道的读端对应的文件描述符被关闭,write操作会产生信号,SIGPIPE,进而导致write进程退出
- 当要写入的数据量不大于管道的容量(PIPE_BUF)时,linux将保证写入的原子性
- 当要写入的数据量大于管道容量(PIPE_BUF)时,linux将不再保证写入的原子性
特点
- 只能够进行单向通信
- 只能够用于有血缘关系(父子,兄弟,爷孙)的进程之间,多常用于父子之间
- 管道内部自带同步机制:子进程写一条,父进程读一条
- 管道在进行通信的时候,对外层提供的服务叫做面向字节流的服务
- 当进程退出之时,管道也随之释放,与文件保持一致
- 管道的生命周期为随进程,进程结束管道就没了
流:相当于水流,写入数据时,写多少字节和你自己有关系,读的时候没有 格式的要求,完全取决你自己;
字节流:以字节来读取和写入,字节数的大小完全取决于自己
- 单进程通信原理
父子进程通信原理
创建管道
#include <unistd.h> int pipe(int pipefd[2]); 参数:fd为文件描述符数组,其中fd[0]表示读端,fd[1] 表示写端 返回值:成功返回0,失败返回-1
单进程通信实例
代码:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> int main() { int fd[2];//文件描述符数组 char buf[1024];//临时缓冲区 int len; ssize_t s; ssize_t w; if(pipe(fd) == -1){//创建管道 perror("pipe"); return -1; } while(1){ printf("Please enter:"); fflush(stdout); s = read(0,buf,sizeof(buf));//从屏幕上读取内容 if( s < 0){ perror("read"); return -2; } len = strlen(buf); w = write(fd[1],buf,len);//将读取的数据写入管道 if(w != len){ perror("write"); return -3; } memset(buf,0,sizeof(buf));//将buf清0 s = read(fd[0],buf,sizeof(buf));//从管道当中读取数据 if(s < 0){ perror("read"); return -4; } w = write(1,buf,len);//将读的数据写入屏幕 if(w != len){ perror("write"); return -5; } } return 0; }
结果:
父子进程通信实例
#include <stdio.h> #include <unistd.h> #include <string.h> int main() { int fds[2]; if(pipe(fds) < 0){//创建一个管道,用于父子间进行通信 perror("pipe"); return 1; } char buf[1024];//临时数组,用于存放通信的消息 printf("Please enter:"); fflush(stdout); ssize_t s = read(0,buf,sizeof(buf)-1); if(s > 0){ buf[s] = 0; } pid_t pid = fork(); if(pid == 0){//子进程只写,关闭读端 close(fds[0]); while(1){ sleep(1); write(fds[1],buf,strlen(buf));//将buf的内容写入管道 } } else{//父进程只读,关闭写端 close(fds[1]); char buf1[1024]; while(1){ ssize_t s = read(fds[0],buf1,sizeof(buf1)-1);//从管道里读数据,放入buf if(s > 0){ buf1[s-1] = 0; printf("client->farther:%s\n",buf1); } } } }
结果:
4. 命名管道
概念
命名管道本质上是一个管道文件,可以通过命令创建也可以通过函数创建,用户可以看到特点
1. 可以进行不相干进程间的通信
2. 命名管道是一个文件,对于文件的相关操作对其同样适用
读写规则
1. 对于管道文件,当前进程操作为只读时,则进行阻塞,直至有进程对其写入数据
2. 对于管道文件,当前进程操作为只写时,则进行阻塞,直至有进程从管道中读取数据
命名管道的创建
1. 利用命令创建
mkfifo filename
2. 函数创建
int mkfifo(const char *filename,mode_t mode); 【参数】: filename:创建的有名管道的全路径名 mode:创建的命名管道的模式,指明其存取权限
文件拷贝实例
write.c
#include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <fcntl.h> #include <sys/stat.h> //读取文件,将文件内容写入管道 int main() { mkfifo("tp",0644);//创建一个管道文件 int infd = open("123",O_RDONLY);//打开一个文件 if(infd == -1){ perror("open"); return 1; } int outfd = open("tp",O_WRONLY);//打开管道文件,将123文件的内容写入管道文件 if(outfd == -1){ perror("open"); return 2; } char buf[1024];//用于存放文件内容 ssize_t s; while( (s = read(infd,buf,sizeof(buf))) >0) { write(outfd,buf,s); } close(infd); close(outfd); }
read.c
#include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> //从管道文件里面读取内容,并将内容写入另一个文件中 int main() { int infd = open("abc.bak",O_CREAT | O_WRONLY | O_TRUNC,0644);//创建一个新的文件 if(infd == -1){ perror("open"); return 1; } //将从管道读取的内容写入到新的文件中 int outfd = open("tp",O_RDONLY);//打开管道文件 if(outfd == -1){ perror("open"); return 2; } char buf[1024];//临时数组 ssize_t s; while( (s = read(outfd,buf,sizeof(buf))) > 0) { write(infd,buf,s); } close(infd); close(outfd); return 0; }
服务器、客户端实例
server.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
//
int main()
{
umask(0);//将权限清0
if(mkfifo("./mypipe",0666|S_IFIFO) < 0){//创建管道
perror("mkfifo");
return 1;
}
int fd = open("./mypipe",O_RDONLY);//打开管道
if(fd < 0){
perror("open");
return 2;
}
char buf[1024];
while(1){
buf[0] = 0;
printf("请等待。。。\n");
ssize_t s = read(fd,buf,sizeof(buf)-1);
if(s > 0){
buf[s-1] = 0;//过滤\n
printf("服务器:%s\n",buf);
}else if(s == 0){//当客户端退出时,read返回0
printf("客户端退出,自己退出\n");
break;
}
}
close(fd);
return 0;
}
client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
int main()
{
int fd = open("./mypipe",O_WRONLY);//打开管道
if(fd < 0){
perror("open");
return 1;
}
char buf[1024];
while(1){
printf("客户端:");
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf)-1);//向管道文件中写数据
if(s > 0){
buf[s] = 0;//以字符串的形式写
write(fd,buf,strlen(buf));
}
}
close(fd);
return 0;
}
Makefile:
.PHONY:all
all:client server
client:client.c
gcc -o $@ $^
server:server.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f client server mypipe
结果:
5.总结
类型 | 进程关系 | 不同点 | 本质 |
---|---|---|---|
匿名管道 | 必须是亲缘关系 | 由pipe创建并打开 | 内核的一块缓存 |
命名管道 | 两个毫不相干进程 | 由mkfifo创建,open打开 | 一个文件 |