复制文件描述符
dup函数
作用 : 文件描述符复制
语法
#include <unistd.h>
int dup(int oldfd);
参数 :
所需复制的文件描述符
返回值
复制得到的文件描述符
功能 : 从文件描述符表中 , 寻找一个最小可能的文件描述符(通过返回值返回)作为 oldfd复制
#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{int newFd = dup(1);write(newFd, "hello world", 11);return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{int fd = open("a.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);close(1);dup(fd);printf("啦啦啦,德玛西亚");close(fd);return 0;
}
dup2函数(推荐)
#include <unistd.h>
int dup2(int oldfd, int newfd);
参数 :
oldfd:原文件描述符
newfd:指定复制到的文件描述符 , 如果该文件描述符存在 , 那么将原有的关闭
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{int fd = open("a.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);dup2(fd, 1);printf("123");return 0;
}
无名管道
又名管道 (pipe)
无名管道是一种特殊类型的文件,在应用层体现为两个打开的文件描述符,1 个描述符写 fd[1], 1 个描述符读 fd[0]
核心 :0 读 1 写
特点:
1,管道不是普通的文件 , 不属于某个文件系统 , 其只存在于内存中。
2,半双工,数据在同一时刻只能在一个方向上流动
补充
单工: 指数据传输只支持数据在一个方向上传输
双工: 指二台通讯设备之间,允许有双向的资料传输
全双工: 允许二台设备间同时进行双向数据传输。一般的电话、手机就是全双工的系统,因为在讲话时同时也可以听到对方的声音。
半双工: 允许二台设备间进行双向数据传输 , 但不能同时进行。因此同一 时间只允许一设备传送资料,若另一设备要传送资料,需等原来传送资料的设备传送完 成后再处理。
3,数据只能从管道的一端写入,从另一端读出。
4,写入管道中的数据遵循先入先出的规则。
5,管道所传送的数据是无格式的,这要求管道的读出方与写入方必须事先约定好数 据的格式,如多少字节算一个消息等
6,管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。
7,从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间 以便写更多的数据
8,管道没有名字,只能在具有公共祖先的进程之间使用。
补充 :
管道可以用于任意两个或更多相关进程之间的通信,只要在创建子进程 的系列调用之前通过一个共同的祖先进程创建管道即可。
如管道可用于一个进程和其子孙进程之间的通信。第一个进程创建管 道,然后创建子进程,接着子进程再创建第一个进程的孙子进程。
管道通常用于两个兄弟进程之间的通信—— 它们的父进程创建了管道,并 创建两个子进程。
pipe函数
作用 : 创建无名管道
语法
#include <unistd.h>
int pipe(int fd[2]);
参数:
fd 为 int 型数组的首元素地址,其存放了管道的文件描述符 fd[0] 、 fd[1] 。
fd[0]为读而打开, fd[1] 为写而打开管道。
返回值:
成功:返回 0
失败:返回-1
如: int fd[2];
pipe(fd);
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{// 1,创建管道int fd[2];pipe(fd);// 2,创建子进程,子进程与父进程具有公共祖先int pid = fork();if (pid < 0){printf("创建子进程失败");return 0;}else if (pid == 0){// 子进程,读取父进程传递的数据// 因为子进程只读取消息,所以写入无用,可以关闭close(fd[1]);char buf[128];read(fd[0], buf, sizeof(buf));printf("子进程接收到的消息:%s\n", buf);// 读取结束关闭读close(fd[0]);// 关闭子进程_exit(-1);}else if (pid > 0){// 父进程,写入数据// 因为父进程只写入消息,所以读无用,可以关闭close(fd[0]);write(fd[1], "hello gd", 8);printf("父进程发送消息完成\n");close(fd[1]);wait(NULL);}return 0;
}
读写特点
1、默认用 read 函数从管道中读数据是阻塞的。
2、调用 write 函数向管道里写数据,当缓冲区已满时 write 也会阻塞。管道的缓冲区的大小: 64Kb
3、通信过程中,读端口全部关闭后,写进程向管道内写数据时,写进程会(收到 SIGPIPE 信号)退出。
4,从管道中读数据的特点 编程时可通过 fcntl 函数设置文件的阻塞特性。设置为阻 塞:fcntl(fd, FSETFL,0); 设置为非阻塞: fcntl(fd, FSETFL, O_NONBLOCK);
示例 1 :缓冲区已满时 write 也会阻塞。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{int fd[2];pipe(fd);// 0读1写int pid = fork();if (pid == 0){close(fd[1]);sleep(2);close(fd[0]);_exit(0);}else if (pid > 0){close(fd[0]);int count = 0;for (int i = 1; i < 10000; i++){char buf[1024] = {0};write(fd[1], buf, 1024);count += 1024;printf("i=%d\tcount=%d\n", i, count);}close(fd[1]);wait(NULL);}return 0;
}
示例 2 :通信过程中,写端关闭,读端将解阻塞
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{int pd[2];pipe(pd);int pid = fork();if (pid == 0){close(pd[1]);char buf[100] = {0};printf("等待写入\n");read(pd[0], buf, 100);close(pd[0]);printf("子进程结束\n");_exit(0);}else if (pid > 0){close(pd[0]);printf("5秒后关闭写\n");sleep(5);close(pd[1]);wait(NULL);}return 0;
}
示例 3: 通信过程中 读端关闭 写端将收到 SIGPIPE 信号 退出写端进程
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.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]);int i = 0;while (1){char buf[128] = "";int len = read(fd[0], buf, sizeof(buf));i++;printf("len=%d\n", len);if (i == 5)break;}// 通信完记得关闭读端close(fd[0]);}else if (pid > 0) // 父进程 写{// 读端 无用 可删除close(fd[0]);while (1){printf("父进程%u写入数据\n", getpid());write(fd[1], "hello pipe", 10);sleep(1);}// 通信完记得关闭写端close(fd[1]);wait(NULL); // 等待子进程结束}return 0;
}
综合案例
要求:使用代码实现ps -A | grep bush
#include <stdio.h>
#include <unistd.h>
#include <sys/types.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){close(fd[0]);dup2(fd[1],1);execl("/bin/ps","ps","-A",NULL);_exit(0);}else if(i == 1){close(fd[1]);dup2(fd[0],0);execl("/bin/grep","grep","bash",NULL);_exit(0);}else if(i == 2){while (1){int id = waitpid(-1,NULL,WNOHANG);if(id == -1){break;}} }return 0;
}
有名管道
又名 : 命名管道 (FIFO)
特点 :
1、半双工,数据在同一时刻只能在一个方向上流动。
2、写入 FIFO 中的数据遵循先入先出的规则。
3、 FIFO 所传送的数据是无格式的,这要求 FIFO 的读出方与写入方必须事先约 定好数据的格式,如多少字节算一个消息等。
4、 FIFO 在文件系统中作为一个特殊的文件而存在,但 FIFO 中的内容却存放在 内存中。
5、管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。
6、从 FIFO 读数据是一次性操作,数据一旦被读,它就从 FIFO 中被抛弃,释放 空间以便写更多的数据。
7、当使用 FIFO 的进程退出后, FIFO 文件将继续保存在文件系统中以便以后使 用。
8、 FIFO 有名字,不相关的进程可以通过打开命名管道进行通信。
mkfifo函数
作用 : 创建有名管道
语法
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数 :
pathname:文件名
mode:文件操作模式 , 一般用 0666( 所有用户可读可写 )
返回值 :
成功:0
失败:-1, 一般失败是因为存在与 pathname 名相同的文件
读写特点
1、open打开管道 不指定O_NONBLOCK (阻塞)
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 也会阻塞。
2、open打开管道 指定O_NONBLOCK (非阻塞)
1、先以只读方式打开:如果没有进程 , 已经为写而打开一个 FIFO, 只读 open 成功, 并且 open 不阻塞。
2、先以只写方 式打开:如果没有进程 , 已经为读而打开一个 FIFO ,只写 open 将出错返回-1 。
3、 read 、 write 读写命名管道中读数据时不阻塞。
4、通信过程中,读进程退出后, 写进程向命名管道内写数据时,写进程也会(收到SIGPIPE 信号)退出。
3、 注意: open 函数以可读可写方式打开 FIFO 文件时的特点:
1、 open 不阻塞。
2、调用 read 函数从 FIFO 里读数据时 read 会阻塞。
3、调用 write 函数向 FIFO 里写数据 , 当缓冲区已满时 write 也会阻塞
综合案例
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char const *argv[])
{// 1,创建有名管道,san发si接的管道mkfifo("sanToSi", 0666);// 2,创建有名管道,si发san接的管道mkfifo("siToSan", 0666);int i = 0;for (i = 0; i < 2; i++){// 创建进程int pid = fork();if (pid == 0){// pid==0说明是子进程进入的,子进程无需在创建进程break;}}if (i == 0){int fd;
// 子进程1发送消息
#ifdef USER1fd = open("siToSan", O_WRONLY);
#endif // DEBUG
#ifdef USER2fd = open("sanToSi", O_WRONLY);
#endif // DEBUGif (fd < 0){perror("打开发送管道失败\n");_exit(-1);}while (1){char buf[128];fgets(buf, sizeof(buf), stdin);buf[strlen(buf) - 1] = 0;write(fd, buf, sizeof(buf));printf("my:%s\n", buf);if (strcmp(buf, "886") == 0){break;}}close(fd);_exit(-1);}else if (i == 1){// 子进程2,接收消息int fd = 0;
#ifdef USER1fd = open("sanToSi", O_RDONLY);
#endif // DEBUG
#ifdef USER2fd = open("siToSan", O_RDONLY);
#endif // DEBUGif (fd < 0){perror("打开接收管道失败\n");_exit(-1);}while (1){char buf[128];int len = read(fd, buf, sizeof(buf));printf("读取的字节数:%d\n", len);if (len > 0){printf("si:%s\n", buf);if (strcmp(buf, "886") == 0){break;}}}close(fd);_exit(-1);}else if (i == 2){printf("父进程%d正在执行\n", getpid());// 父进程while (1){pid_t id = waitpid(-1, NULL, WNOHANG);if (id > 0){printf("子进程%d被回收了\n", id);}else if (id == 0){continue;}else if (id < 0){break;}}}return 0;
}
// 命令代码编译gcc 文件名 -o 生成可执行文件名 -D USERX
//-D 相当于定义一个宏
总结
无名管道与有名管道的使用场景
1,无名管道应用与有血缘关系的进程中
2,有名管道应用与没有血缘关系的进程中
无名管道与有名管道的区别
1,无名管道基于内存 , 无需文件管理系统
2,有名管道基于文件和内存 , 需要文件管理系统
dup2
作用: 复制文件描述
意义: 可以实现文件的重定向