目录
一、文件描述符复制
1、dup函数(复制文件描述符)
编辑 2、dup2函数(复制文件描述符)
编辑 二、无名管道pipe
1、概述
2、无名管道的创建
3、无名管道读写的特点
4、无名管道ps -A | grep bash实现
三、有名管道FIFO(命名管道)
1、概述
2、创建有名管道 mkfifo
3、有名管道读写的特点
一、文件描述符复制
让新的文件描述符 指向 旧的文件描述符。(新旧文件描述符指向同一个文件) 使用的函数dup、dup2。
1、dup函数(复制文件描述符)
#include<unistd.h>
int dup(int oldfd);
dup函数的功能:从系统中寻找最小可用的文件描述符 作为oldfd的副本。 新文件描述符 通过dup的返回值返回。
2、dup2函数(复制文件描述符)
#include<unistd.h>
int dup2(int oldfd, int newfd);
dup2的功能:将newfd作为oldfd的副本。 如果newfd事先存在 dup2会先close(newfd),然后将newfd作为oldfd的副本。
二、无名管道pipe
1、概述
管道(pipe)又称无名管道。 无名管道是一种特殊类型的文件,在应用层体现为 两个打开的文件描述符(读端和写端)。
管道的特点:
- 半双工,数据在同一时刻只能在一个方向上流动。
- 数据只能从管道的一端写入,从另一端读出。
- 写入管道中的数据遵循先入先出的规则。
- 管道所传送的数据是无格式的,这要求管道的读出方与写入方必须事先约定好数据 的格式, 如多少字节算一个消息等。
- 管道不是普通的文件,不属于某个文件系统,其只存在于内存中。
- 管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。
- 从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写更多的数据。
- 管道没有名字,只能在具有公共祖先的进程之间使用
2、无名管道的创建
#include<unistd.h>
int pipe(int filedes[2]);
功能:经由参数filedes返回两个文件描述符
参数: filedes为int型数组的首地址,其存放了管道的文件描述符fd[0]、fd[1]。 filedes[0]为读而打开,filedes[1]为写而打开管道,filedes[0]的输出是filedes[1]的输 入。
返回值: 成功:返回 0 失败:返回-1
注意:在使用无名管道的时候 必须事先确定,谁发,谁收的问题。一旦确定不可更改。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
//创建一个无名管道
int fd[2];
pipe(fd);
//创建一个子进程
//父进程发 子进程收
pid_t pid=fork();
if(pid==0)//子进程
{
//子进程的写端无意义(可以事先关闭)
close(fd[1]);
//子进程接收父进程消息
unsigned char buf[128]="";
printf("子进程%d正在等待父进程的消息\n", getpid());
read(fd[0],buf,sizeof(buf));
printf("子进程%d读到的消息为:%s\n", getpid(), buf);
//子进程读完数据 应该关闭读端
close(fd[0]);
//显示退出
_exit(-1);
}
if(pid>0) //父进程
{
//父进程的读端无意义(可以事先关闭)
close(fd[0]);
//写端写入数据
printf("3秒后父进程%d写入数据hello pipe\n",getpid());
sleep(3);
write(fd[1],"hello pipe",strlen("hello pipe"));
printf("父进程:%d完成写入\n", getpid());
//通信完成 应该关闭写端
close(fd[0]);
//等待子进程退出
wait(NULL);
}
return 0;
}
3、无名管道读写的特点
1、默认用read函数从管道中读数据是阻塞的。
2、调用write函数向管道里写数据,当缓冲区已满时write也会阻塞。
3、通信过程中,读端口全部关闭后,写进程向管道内写数据时,写进程会(收 到SIGPIPE信号)退出。
4、无名管道ps -A | grep bash实现
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
//创建一个无名管道
int fd[2];
pipe(fd);
//创建两个子进程
int i=0;
for(i=0;i<2;i++)
{
pid_t pid=fork();
if(pid==0)
break;//防止子进程继续创建子进程
}
if(i==0)//子进程1
{
//ps ‐A 写端
close(fd[0]);
//1作为fd[1]的副本
dup2(fd[1],1);
//执行ps ‐A
execlp("ps","ps","-A",NULL);
_exit(-1);
}
else if(i==1)//子进程2
{
//grep bash 读端
close(fd[1]);
//0作为fd[0]的副本
dup2(fd[0],0);
//执行grep bash
execlp("grep","grep","bash",NULL);
}
if(i==2) //父进程
{
//关闭管道读写端
close(fd[0]);
close(fd[1]);
while(1)
{
pid_t pid=waitpid(-1,NULL,WNOHANG);
if(pid>0)
printf("子进程%d退出了\n", pid);
else if(pid==0)
continue;
else if(pid<0)
break;
}
}
return 0;
}
三、有名管道FIFO(命名管道)
1、概述
主要用于没有血缘关系的进程间通信。
特点:
- 半双工,数据在同一时刻只能在一个方向上流动。
- 写入FIFO中的数据遵循先入先出的规则。
- FIFO所传送的数据是无格式的,这要求FIFO的读出方与写入方必须事先 约定好数据的格式,如多少字节算一个消息等。
- FIFO在文件系统中作为一个特殊的文件而存在,但FIFO中的内容却存放在内存中。
- 管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。
- 从FIFO读数据是一次性操作,数据一旦被读,它就从FIFO中被抛弃,释 放空间以便写更多的数据。
- 当使用FIFO的进程退出后,FIFO文件将继续保存在文件系统中以便以后使用。
- FIFO有名字,不相关的进程可以通过打开命名管道进 行通信(重要)
2、创建有名管道 mkfifo
FIFO文件的创建
#include<sys/type.h>
#include<sys/stat.h>
int mkfifo( const char *pathname, mode_t mode);
参数:
pathname:FIFO的路径名+文件名。
mode:mode_t类型的权限描述符。
返回值: 成功:返回 0 失败:如果文件已经存在,则会出错且返回-1
fifo_write.c:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
//创建有名管道(确保两个进程识别相同目录)
mkfifo("my_fifo",0666);
//open以写的方式的打开 有名管道(阻塞 到 对方 以读的方式打开)
int fp=open("my_fifo",O_WRONLY);
if(fp<0)
{
perror("open");
return 0;
}
printf("写端open成功\n");
while(1)
{
//获取键盘输入
unsigned char buf[128]="";
printf("请输入需要发送的数据:");
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]=0;
//发送数据
write(fp,buf,sizeof(buf));
//退出循环
if(strcmp(buf,"Bye")==0)
break;
}
close(fp);
}
fifo.read.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
//创建有名管道(确保两个进程识别相同目录)
mkfifo("my_fifo",0666);
//open以读的方式的打开 有名管道(阻塞 到 对方 以写的方式打开)
int fp=open("my_fifo",O_RDONLY);
if(fp<0)
{
perror("open");
return 0;
}
printf("读端open成功\n");
//循环的读取数据
while(1)
{
//接收数据
unsigned char buf[128]="";
read(fp,buf,sizeof(buf));
printf("收到数据:%s\n", buf);
//退出循环
if(strcmp(buf,"Bye")==0)
break;
}
close(fp);
}
3、有名管道读写的特点
阻塞方式打开管道:
1、open以只读方式打开FIFO时,要阻塞到某个进程为写而打开此FIFO
2、open以只写方式打开FIFO时,要阻塞到某个进程为读而打开此FIFO。
3、open以只读、只写方式打开FIFO时会阻塞,调用read函数从FIFO里读数据时read也会阻塞。
4、通信过程中若写进程先退出了,则调用read函数从FIFO里读数据时不阻塞;若写进程又 重新运行,则调用read函数从FIFO里读数据时又恢复阻塞。
5、通信过程中,读进程退出后,写进程向命名管道内写数据时,写进程也会(收到 SIGPIPE信号)退出。
6、调用write函数向FIFO里写数据,当缓冲区已满时write也会阻塞。
非阻塞方式打开管道:
1、先以只读方式打开:如果没有进程已经为写而打开一个FIFO, 只读open成功,并且 open不阻塞。
2、先以只写方式打开:如果没有进程已经为读而打开一个FIFO,只写open 将出错返回-1。
3、read、write读写命名管道中读数据时不阻塞。
4、通信过程中,读进程退出后,写进程向命名管道内写数据时,写进程也会(收到 SIGPIPE信号)退出。
注意: open函数以可读可写方式打开FIFO文件时的特点:
1、open不阻塞。
2、调用read函数从FIFO里读数据时read会阻塞。
3、调用write函数向FIFO里写数据,当缓冲区已满时write也会阻塞