一、何为管道
管道也称无名管道,是一种特殊类型的文件,在应用层体现为两个打开的文件描述符
无名管道是创建在内核空间的,多个进程知道同一个无名管道的空间,就可以利用它进行通信
无名管道会给当前进程两个文件描述符,一个用来读操作,一个用来写操作
管道的特点:
- 半双工,数据在同一时刻只能在一个方向上流动。
- 数据只能从管道的一端写入,从另一端读出。
- 写入管道中的数据遵循先入先出的规则。
- 管道所传送的数据是无格式的,这要求管道的读出方与写入方必须事先约定好数据的格式,如多少字节算一个消息等。
- 管道不是普通的文件,不属于某个文件系统,其只存在于内存中。
- 管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。
- 从管道读数据是一次性操作,数据一旦被读走,它就从管道中抛弃,释放空间以便读取更多的数据
- 管道没有名字,只能在具有公共祖先的进程之间使用
二、管道的创建
#include <unistd.h>
int pipe(int filedes[2]);
功能:精油参数filedes返回两个文件描述符
参数:int型的数组,存放两个元素,filedes[0]为读而打开,filedes[1]为写而打开
返回值:成功返回0 失败返回-1
操作方式:通过文件IO中的read和write函数即可对无名管道进行操作
实例:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(){
int fds[2];
if(pipe(fds)!=0){
perror("fail to pepe!");
return -1;
}
printf("fds[0] = %d,fds[1] = %d\n",fds[0],fds[1]);
if(write(fds[1],"hello world!",strlen("hello world!"))==-1)
{ //strlen判断长度时只是字符串长度,不带结束符
perror("fail to write!");
exit(1);
}
/***************************************************
*管道内容未被读出,再次往里面写入内容相当于追加内容,不会将之前的内容删除
*****************************************************/
write(fds[1],"nihao axuezm!",sizeof("nihao axuezm!"));
//sizeof判断长度是加上结束符的
char buf[32];
/*********************************************
*读取时会读取指定大小的内容,不管是第几次写入的
************************************************/
if(read(fds[0],buf,sizeof(buf))==-1){
perror("fail to read!");
exit(2);
}
printf("%s\n",buf);
/************************************************
*管道中如果没有内容,读取时会阻塞,直到读取到内容后返回
*************************************************/
if(read(fds[0],buf,sizeof(buf))==-1){
perror("fail to read!");
exit(2);
}
printf("%s\n",buf);
close(fds[0]);
close(fds[1]);
return 0;
}
三、管道的应用
因为管道只能在有亲缘关系间的进程进行通信,所以利用管道进行进程间通信一般都是先用父进程创建一个无名管道,然后父进程再创建子进程,子进程就会继承父进程创建的无名管道的文件描述符,从而能都进行父子进程之间的通信。 (前文说过内核空间中的数据是独一份的,所以子进程继承的父进程创建的管道与父进程的是同一个)
当需要交换数据时,发送数据的一端向filedes[1]写入数据,接收数据的一端从filedes[0]读取数据,这一流程即为一次数据的交互
应用实例:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
int fds[2];
if(pipe(fds)!=0){
perror("fail to pepe!");
return -1;
}
pid_t pid;
if((pid=fork())==-1)
{
perror("fail to fork!");
return -1;
}
if(pid > 0){
char buf[108];
while(1){
printf("-***write here***-\n");
fgets(buf,sizeof(buf),stdin);
if(write(fds[1],buf,strlen(buf))==-1){
perror("fail to write");
exit(2);
}
sleep(1);
}
wait(NULL);
}else{
char buf[108] = "";
while(1){
memset(buf,0,sizeof(buf));
if(read(fds[0],buf,sizeof(buf))==-1){
perror("fail to read");
exit(1);
}
printf("-***read here***-\n");
printf("%s",buf);
printf("-***************-\n\n");
}
}
close(fds[0]);
close(fds[1]);
return 0;
}
四、无名管道读取数据的特点
1、默认用read函数从管道中读数据是阻塞的。
2、调用write函数向管道里写数据,当缓冲区己满时(64k)write也会阻塞。
3、通信过程中,读端口全部关闭后,写进程向管道内写数据时,写进程会(收到SIGPIPE管道破裂信号)退出。==》只存在写端口不存在读端口
4、当读端存在写端不存在(即被close了)时,若管道有数据则正常读取,没有数据read会立即返回0,不会阻塞
五、通过fcntl函数设置文件的阻塞特性
设置为阻塞:fcntl(fd,F_SETFL,0);
设置为非阻塞:fcntl(fd,F_SETFL,O_NONBLOCK);
何为阻塞非阻塞?
如果是阻塞,管道中没有数据,read会一直等待,直到有数据才会继续运行,否则一直等待
如果是非阻塞,read函数运行时,会先看一下管道中是否有数据,如果有数据,则正常运行读取数据,如果管道中没有数据,则read函数会立即返回,继续执行下面的代码
六、文件描述符的复制
dup和dup2是可以用来复制文件描述符的系统调用,使新的文件描述符也标识旧的文件描述符所标识的文件。
int dup(int oldfd);
/**********************************************************
*复制一个旧的文件描述符(oldfd),并分配一个新的文件描述符,
*新的文件描述符是调用进程文件描述符表中最小可用的文件描述符
*返回值:成功返回新的文件描述符,失败返回-1
**********************************************************/
int dup2(int oldfd,int newfd);
/**********************************************************
*复制一个旧的文件描述符(oldfd),并分配一个新的文件描述符(newfd),
*新的文件描述符得是在允许范围内的值(0~1023),
*若newfd已经打开了,会先将该文件关闭,然后再复制
*返回值:成功返回newfd,失败返回-1
**********************************************************/
dup和dup2经常用来重定向进程的stdin、stdout和stderr
实例:使用dup复制标准输出的文件描述符
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
int fd = dup(1);
if(fd == -1){
perror("fail to dup!");
exit(1);
}
printf("fd = %d\n",fd);
write(fd,"hello world!\n",sizeof("hello world!\n"));
close(fd);
return 0;
}
实例:输出重定向使用方式==》可以先复制原本的标准输出文件描述符再替换,后面再复制回去,灵活运用。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
int fd_file = open("test.txt",O_WRONLY | O_CREAT | O_TRUNC,0664);
if(fd_file == -1){
perror("fail to open!");
exit(1);
}
/********使用dup函数时**********/
close(1);
int fd = dup(fd_file);
/******************************/
/********使用dup2函数时*********
*int fd = dup2(fd_file,1);
*fd、fd_file、1指向的都是test.txt文件,fd==1,即将fd_file复制一份为1
******************************/
if(fd == -1){
perror("fail to dup!");
exit(1);
}
printf("fd = %d\n",fd);
printf("hello world!\n");
close(fd_file);
//close(fd);//此处关闭了这个文件描述符写入就会失败,原因不明
return 0;
}
注意:在使用dup2时要指定好newfd的值,若是一个只定义没有赋初值的变量默认会给最小的文件描述符值,即0