管道 命名管道
管道(pipe)又称无名管道
无名管道是一种特殊类型的文件, 在应用层体现为两个打开的文件描述符
管道是最古老的 UNIX IPC 方式, 其特点是:
半双工, 数据在同一时刻只能在一个方向上流动
数据只能从 管道的一端写入, 另一端读出
写入管道中的数据遵循 先入先出 的规则
管道所传送的数据是无格式的,这要求管道的读出方与写入方 必须事先约定好数据的格式, 如多少字节算一个消息等
管道不是普通的文件, 不属于某个文件系统, 其只存在于内存中
管道在内存中对应一个缓冲区。 不同的系统其大小不一定相同
从管道读数据是一次性操作, 数据一旦被读走, 它就从管道中被抛弃, 释放空间以便写更多的数据
管道没有名字, 只能在具有公共祖先的进程之间使用
无名管道的创建 pipe 函数
#include <unistd.h>
/*
* 功能:
* 经由参数 filedes 返回两个文件描述符
* 参数:
* filedes:int 型数组的首地址,其存放了管道的文件描述符 fd[0]、 fd[1]
* filedes[0]为读而打开,filedes[1]为写而打开管道
* filedes[0]的输出是 filedes[1]的输入
* return:
* 成功:0
* 失败:-1
*/
int pipe(int filedes[2]);
创建管道, 父子进程通过无名管道通信
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
int fd_pipe[2];
char buf[] = "hello world";
pid_t pid;
if(pipe(fd_pipe) < 0)
{
perror("pipe");
}
pid = fork();
if(pid < 0)
{
perror("fork");
exit(-1);
}
if(0 == pid)
{
write(fd_pipe[1], buf, strlen(buf));
_exit(0);
}
else
{
wait(NULL);
memset(buf, 0, sizeof(buf));
read(fd_pipe[0], buf, sizeof(buf));
printf("buf = [%s]\n",buf);
}
return 0;
}
打印:
父子进程通过管道实现数据的传输
注意:
利用无名管道实现进程间的通信, 都是父进程创建无名管道, 然后再创建子进程, 子进程继承父进程的无名管道的文件描述符, 然后父子进程通过读写无名管道实现通信
从管道中读数据的特点
默认用 read 函数从管道中读数据是阻塞的
调用 write 函数向管道里写数据, 当缓冲区已满时 write 也会阻塞
通信过程中, 读端口全部关闭后, 写进程向管道内写数据时, 写进程会(收到 SIGPIPE 信号) 退出
编程时可通过 fcntl 函数设置文件的阻塞特性
设置为阻塞:
fcntl(fd, F_SETFL, 0);
设置为非阻塞:
fcntl(fd, F_SETFL, O_NONBLOCK)
验证无名管道文件读写的阻塞和非阻塞
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd_pipe[2];
char buf[] = "hello world";
pid_t pid;
if(pipe(fd_pipe) < 0)
{
perror("pipe");
}
pid = fork();
if(pid < 0)
{
perror("fork");
exit(-1);
}
if(0 == fid)
{
sleep(3);
write(fd_pipe[1], buf, strlen(buf));
_exit(0);
}
else
{
//fcntl(fd_pipe[0], F_SETFL, 0);
fucntl(fd_pipe[0], F_SETFL, O_NONBLOCK);
while(1)
{
memset(buf, 0, sizeof(buf));
read(fd_pipe[0], buf, sizeof(buf));
printf("buf = [%s]\n",buf);
sleep(1);
}
}
return 0;
}
打印:
文件描述符概述
文件描述符是非负整数, 是文件的标识
用户使用文件描述符( file descriptor) 来访问文件
利用 open 打开一个文件时, 内核会返回一个文件描述符
每个进程都有一张文件描述符的表, 进程刚被创建时, 标准输入、 标准输出、 标准错误输出设备文件被打开,对应的文件描述符 0、 1、 2 记录在表中
在进程中打开其他文件时, 系统会返回文件描述符表中最小可用的文件描述符, 并将此文件描述符记录在表中
注意:
Linux 中一个进程最多只能打开 NR_OPEN_DEFAULT( 即 1024)个文件,故当文件不再使用时应及时调用 close 函数关闭文件
文件描述符的复制
dup 和 dup2 是两个非常有用的系统调用, 都是用来复制一个文件的描述符, 使新的文件描述符也标识旧的文件描述符所标识的文件
int dup(int oldfd);
int dup2(int oldfd, int newfd);
dup 和 dup2 经常用来重定向进程的 stdin、 stdout 和 stderr
dup 函数
#include <unistd.h>
/*
* 功能:
* 复制 oldfd 文件描述符,并分配一个新的文件描述符,新的文件描述符是调用进程文件描述符表中最小可用的文件描述符
* 参数:
* 要复制的文件描述符 oldfd
* 返回值:
* 成功: 新文件描述符
* 失败:-1
*/
int dup(int oldfd);
重定向
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd1;
int fd2;
fd1 = open("test", O_CREAT | O_WRONLY, S_IRWXU);
if(fd1 < 0)
{
perror("open");
exit(-1);
}
close(1);
fd2 = dup(fd1);
printf("fd2 = %d\n", fd2);
return 0;
}
打印:
dup2 函数 重定向
#include <unistd.h>
/*
* 功能:
* 复制一份打开的文件描述符 oldfd,并分配新的文件描述符 newfd,newfd 也标识 oldfd 所标识的文件
* 注意:
* newfd 是小于文件描述符最大允许值的非负整数,如果 newfd 是一个已经打开的文件描述符, 则首先关闭该文件,然后再复制
* 参数:
* oldfd: 要复制的文件描述符
* newfd: 分配的新的文件描述符
* 返回值:
* 成功:newfd
* 失败: -1
*/
int dup2(int oldfd, int newfd);
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd1;
int fd2;
//save stdout
fd2 = dup(1);
printf("new:fd2 = %d\n",fd2);
fd1 = open("test", O_CREAT | O_RDWR, S_IRWXU);
close(1);
dup(fd1);
printf("hello world\n");
close(1);
dup("fd2");
printf("you is sb\n");
return 0;
}
打印:
复制文件描述符后新旧文件描述符的特点
使用 dup 或 dup2 复制文件描述符后,新文件描述符和旧文件描述符指向同一个文件,共享文件锁定、 读写位置和各项权限
当关闭新的文件描述符时, 通过旧文件描述符仍可操作文件
当关闭旧的文件描述时, 通过新的文件描述符仍可可操作文件
exec 前后文件描述符的特点
close_on_exec 标志决定了文件描述符在执行 exec 后文件描述符是否可用
文件描述符的 close_on_exec 标志默认是关闭的, 即文件描述符在执行 exec 后文件描述符是可用的
若没有设置 close_on_exec 标志位, 进程中打开的文件描述符, 及其相关的设置在 exec 后不变, 可供新启动的程序使用
设置 close_on_exec 标志位的方法
int flags;
flags = fcnt1(fd, F_GETFD); //获取标志
flags |= FD_CLOEXEC; //打开标志
flags &= ~FD_CLOEXEC; //关闭标志
fcntl(fd, F_SETFD, flags); //设置标志