5.01无名管道概述
1、管道概述
管道又称为无名管道,无名管道是特殊的文件,在应用层体现为两个打开的文件描述符。
内核空间是所有进程所共有的。
无名管道:就是创建在内核中的,多个进程知道同一个无名管道的空间,就可以利用它来进行通信。
无名管道虽然是在内核空间创建的,但是会给当前用户进程两个文件描述符,一个负责执行读,一个负责写
管道是古老的UNIX ipc方式,其特点是:
1、半双工,fd[0]读,fd[1]写;数据在同一时刻只能在一个方向三流动
2、数据只能从管道的一端写入,另一端读出
3、写入管道中的数据先入先出
4、传送的数据是无格式的,要求管道的读出方与写入方必须实现约定好数据格式
5、管道不是普通的文件,不属于某个文件系统,其只存在内存中
6、管道内存中对应一个缓冲区,不同系统大小不一定相同
7、从管道读数据时一次性操作,一旦读走,它就从管道中被抛弃
8、管道没有名字,只能实现父子进程通信
2、无名管道的创建------pipe函数
#include <unistd.h>
int pipe(int fd[2]);
功能:经由参数fd[2]返回两个文件描述符
参数:fd为int型数组,存放管道文件描述符fd[0],fd[1]
成功0,失败-1
#include<stdio.h>
#include<stdlib.h>
#include<unstd.h>
int main(int argc, char **argv)
{
int fd_pipe[2];
if(pipe(fd_pipe) == -1)
{
perror();
exit(1);
}
printf("%d\n", fd_pipe[0]);
printf("%d\n", fd_pipe[1]);
//接下来就是对两个文件描述的使用了
if(write(fd_pipe[0], "hello world") == -1)
{
exit(1);
}
buf[32] = "";
if(read(fd_pipe[1], buf) == -1)
{
exit(1);
}
//可以用ssize_t类型的数据接收返回值,用%ld来打印
//如果第一次读了数据,那么会产生文件偏移量,这个值是不会变化的
printf("%s\n", buf);
return 0;
}
//可以用ssize_t类型的数据接收返回值,用%ld来打印
多次写入的数据会直接在文件中追加
//如果第一次读了数据,那么会产生文件偏移量,这个值是不会变化的
管道中如果没有数据了,read就会阻塞,毕竟这个是阻塞函数
3、无名管道实现进程通信
注意:利用无名管道实现进程间通信,都是父进程创建无名管道,然后再创建子进程,子进程继承父进程的无名管道的文件描述符,然后父子进程实现通信;
无名管道使用范围比较小,只有具有亲缘关系的进程之间。
父进程先创建无名管道,然后在fork,这样就可以实现通信了,
要想实现全双工通信还得要创建2个管道,否则两个进程同时写的话会出错
#include<stdio.h>
#include<stdlib.h>
#include<unstd.h>
#include<string.h>
int main(int argc, char **argv)
{
pid_t pid;
int pipe_fd[2];
if(pipe(pipe_fd) == -1)
{
exit(1);
}
if((pid = fork()) < 0)
{exit(1);}
else if(pi > 0) //父进程,父进程只负责发送数据,子进程负责读数据
{
char buf[128]={};
while(1)
{
fgets(buf,sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
if(write(pipe_fd[1], buf, sizeof(buf)) == -1)
{
exit(1);
}
}
}
else //子进程只负责读
{
char buf[128]= "";
while(1)
{
if(read(pipd_fd, buf, sizeof(buf) == -1)) //
{
exit(1);
}
}
printf(" son process:%s\n", buf);
}
return 0;
}
4、无名管道的读写规律
从管道中读数据的特点:
①默认用read函数从管道中读取数据时阻塞的
②用write函数向管道中写入数据,当缓冲区满了write会阻塞
③通信中,读关闭,写数据进程会收到(SIGPIPE)退出!
几种情况需要考虑:
①读写端都存在,只读不写:
②读写端都存在,只写不读
③只有读端:
④只有写端:写数据进程会收到(SIGPIPE)退出!
实例1:读写端都存在,只读不写
#include<stdio.h>
#include<stdlib.h>
#include<unstd.h>
#include<string.h>
int main(int argc, char **argv)
{
pid_t pid;
int pipe_fd[2];
if(pipe(pipe_fd) == -1)
{
exit(1);
}
//读写端都存在,只读不写会怎么样?
write(pipe_fd[1], "hello world", 11);
char buf[128] = "";
if(read(pipd_fd, buf, sizeof(buf) == -1)) //
{
exit(1);
}
printf("%s\n", buf);
if(read(pipd_fd, buf, sizeof(buf) == -1)) //
{
exit(1);
}
printf("%s\n", buf);
return 0;
}
实例2:读写端都存在,只写不读
缓冲区写满之后,也会阻塞,默认最大为64K字节
#include<stdio.h>
#include<stdlib.h>
#include<unstd.h>
#include<string.h>
int main(int argc, char **argv)
{
int pipe_fd[2];
if(pipe(pipe_fd) == -1)
{
exit(1);
}
int num = 0;
while(1)
{
if(write(pipe_fd[1], "666", 1024) == -1)
{
exit(1);
}
num++;
printf("%d\n", num);
}
return 0;
}
实例3:只有读端:
#include<stdio.h>
#include<stdlib.h>
#include<unstd.h>
#include<string.h>
int main(int argc, char **argv)
{
int pipe_fd[2];
if(pipe(pipe_fd) == -1)
{
exit(1);
}
write(pipe_fd[1], "hello world", 11);
close(piped_fd[1]);
char buf[128] ="";
ssize_t bytes;
if((bytes = read(pipd_fd[0], buf, sizeof(buf))) == -1)//
{exit(1);}
printf("bytes = %ld\n", bytes);
printf("buf = %s\n", buf);
memset(buf, 0, sizeof(buf));
if((bytes = read(pipd_fd[0], buf, sizeof(buf))) == -1)//
{
exit(1);
}
printf("bytes = %ld\n", bytes);
printf("buf = %s\n", buf);
return 0;
}
实例4:只有写端:管道破裂信号
......
close(pipe_fd[0]);//读端关闭,一直写
int num = 0
while(1)
{
if(write(pipe_fd[1], "666", 1024) == -1)
{
exit(1);
}
num++;
printf("%d\n", num);
}
return 0;
只有写端,没有读端,一旦执行写操作就会产生一个信号SIGPIPE(管道破裂),只要执行就产生,不管以后没有满。
我们可以自己定义处理函数(先注册)
void handler(int sig)
{
printf("AAAAAAAAAAAAAAAAAAAAA\n");
}
int main(int argc, char *argv[])
{
signal(SIGPIPE, handler);//要先注册
int pipefd[2];
if(pipe(pipefd) == -1)
{
exit(1);
}
close(pipe_fd[0]);//读端关闭,一直写
int num = 0
while(1)
{
if(write(pipe_fd[1], "666", 1024) == -1)
{
exit(1);
}
num++;
printf("%d\n", num);
}
return 0;
}
5、通过fcntl函数设置文件的阻塞特性(设置非阻塞的目的什么????)
编程时可以通过fcntl函数设置文件的阻塞属性:
设置为阻塞:fcntl(fd, F_SETFL,0)
设置为非阻塞:fcntl(fd, F_SETFL, O_NONBLOCK)
默认就是就是阻塞的(0)
#include<stdio.h>
#include<stdlib.h>
#include<unstd.h>
int main(int argc, char **argv)
{
int pipefd[2];
char buf[] = "hello world";
if(pipe(pipefd) == -1)
{
exit(1);
}
pid_t pid;
if((pid = fork()) < 0)
{
exit(1);
}
if(pid == 0)
{
while(1)
{
sleep(5);
write(fd_pipe[1], buf, strlen(buf);
}
}
else
{
while(1)
{
//如果将fd_pipe[0]设置为非阻塞
fcntl(fd_pipe[0], F_SETPL, O_NONBLOCK);
memset(buf, 0, sizeof(buf));
read(fd_pipe[0], buf, sizeof(buf));
printf("buf=[%s]\n", buf);
sleep(1);
}
}
return 0;
}
6、文件描述符的概念
(需要好好补充,文件描述符信息很多)
7、文件描述符的复制---dup函数
dup和dup2是两个非常有用的系统调用,都是用来复制一个文件描述符的,使用新的文件描述符也标识旧的文件描述符所标识的文件;
int dup(int oldfd);
int dup2(int oldfd, int newfd)
dup和dup2经常用来重定向进程stdin stdout stderr
#include<unistd.h>
int dup(int oldfd);
功能:复制old文件描述符,并分配一个新的文件描述符,新的文件描述符调用进程文件描述符中最小的可用的文件描述符
参数:要复制的旧的文件
返回新的文件描述符,失败-1
返回新的文件符。这个新的是系统中文件描述符可用的最小值。
int main(void)
{
int fd;
fd = dup(1);
printf("%d\n", fd);
可以直接往fd中写数据,就是往1(标准输出)中写数据
return 0
}也可以通过dup实现输出重定向
关闭1,然后dup(1),要想重新实现输出到标准输出,只需要把1给关闭就行
int fd_file;
fd_file = open("test.txt", O_WRONLY | O_CREAT | O_TRUNK, 0664)
close(1);
int fd=dup(fd_file);
printf("AAAAAAAAA");
8、文件描述符的复制---dup2函数
int dup2(int oldfd, int newfd);
功能:复制一份打开的文件描述符oldfd,并分配一个新的文件描述符newfd, 两者标识同一个文件
注意:newfd是小于文件描述符最大允许的非负整数,如果newfd是一个已经打开的文件描述符,这首先关闭该文件,然后再复制;
参数:oldfd newfd
成功返回newfd,失败返回-1
这个函数比dup函数更加方便!newfd如果是一个已经打开的文件描述符,那么会选择先把它关闭。
同样也可以实现输出重定向和再恢复
fd1 = open("a.txt", O_CREATE | O_WRONLY, 0664)
if(fd <0).....
fd2 = dup2(fd1, 1);
注意:如果使用dup2函数,则应该在声明的时候先把第二个参数赋值一个初始化的值,否则会直接赋值0,就不能标准输入了
{
int fd1;
int fd2 = 3;
dup2(1, fd2) ; //这个地方要注意呀
printf("fd2 = %d\n", fd2);
fd1 = open("test.txt", O_CREAT | O_RDWD, 0664)
dup2(fd1, 1);
printf("hello world\n");
dup2(fd2, 1);
printf("AAAAAAAAAAAAAAAAAAA")'
return 0;
}