管道的概念
管道是一种进程通信的方式,管道是一个载体,用于传输数据的通道
管道的特点
1.管道的读写端是固定的
2.管道是半双工的同一时间只能执行一侧读一侧写
3.管道是处于内核中的一种特殊的文件,占用一定的内存空间(管道就是一个文件,存在于内存中,作为数据的缓冲区)
管道的分类
1.无名管道(匿名管道) --> 有亲属关系的进程通信
2.有名管道(命名管道) --> 没有亲属关系的进程通信
无名管道
特性
半双工,数据在同一时刻只能在一个方向上流动
数据只能从管道的一端写入,从另一端读出
写入管道中的数据遵循先入先出的规则(FIFO)(类似于队列)
管道所传输的数据是无格式的,这要求管道的读出方式必须是先约定好数据的格式,如多少字节算一个消息
管道是一个文件,只不过不属于某个系统,只存在于内存中
管道在内存中对应一个缓冲区,不同的系统其大小不一定相同
从管道读数据是一次性操作,数据一旦被读走,他就从管道中被抛弃,释放空间以便写入更多的数据
管道没有名字,只能在具有公共祖先的进程之间使用
获取管道文件描述符函数
#include <unistd.h> int pipe(int filedes[2]); 功能:经由参数 filedes 返回两个文件描述符 参数: filedes 为 int 类型数组的首地址,其存放了管道的文件描述符fd[0], fd[1] filedes[0]为读而打开,filedes[1]为写而打开管道 filedes[0]是输出,feledes[1]是输入 返回值: 成功:返回0 失败:返回-1
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <wait.h> #define BUF_SIZE 1024 int main(int argc, char const *argv[]) { //创建pipe函数所需要的参数 int fd[2] = {0}; //检测 pipe 函数的返回值变量 int ret = 0; //创建一个变量用来接收fork函数的返回值 pid_t pid; //创建一个数组用来作为读取函数的数据缓冲区 char rev_buf[BUF_SIZE] = {0}; //创建管道 ret = pipe(fd); //判断管道是否创建成功 if(ret == 0) { printf("pipe is ok\n"); } //调用父子进程函数 pid = fork(); //通过返回值判断父子进程是否创建成功 if(pid < 0) { perror("fork error :"); } //给子进程添加功能实现 if(pid == 0) { //读操作,函数参数为文件描述符,存放读取数据的内存首地址,读取到的字节个数 if(read(fd[0], rev_buf, BUF_SIZE) < 0) { perror("read error :"); exit(-1); } printf("rev_buf-->%s\n",rev_buf); exit(1); } //给父进程添加功能实现 else { close(fd[0]); //写操作,函数参数为文件描述符,写入的数据,写入数据的长度 if( write(fd[1],"hello world 2303",strlen("hello world 2303"))<0) { perror("write error:"); exit(-1); } wait(NULL); } return 0; }
实现原理
利用无名管道实现进程间的通信,都是父进程创建无名管道,然后再创建子进程,子进程继承父进程的无名管道的文件描述符,然后父子进程通过读写无名管道实现通信 从管道中读取数据的特点: 1.默认用 read 函数从管道中读取数据是阻塞的 2.调用 write 函数向管道里写数据,当缓冲区已满时 write 也会阻塞 3.通信过程中,读端口全部关闭后,写进程向管道内写数据时,写进程会(收到 SIGPIPE 信号)退出 管道中的数据一旦读出,在管道内部就不存在了
管道的阻塞函数
#include <unistd.h> #include <fcntl.h> int fcntl(文件描述符,设置文件描述符的状态,0); 功能:将文件描述符设置成阻塞状态 参数: 文件描述符 F_SETFL 设置文件描述符的状态 0 设置成阻塞状态 返回值: 成功:返回0 失败:返回-1 int fcntl(fd, F_SETFL, O_NONBLOCK); 功能:将文件描述符设置成非阻塞状态 参数: fd 文件描述符 F_SETFL 设置文件描述符的状态 O_NONBLOCK 设置成非阻塞状态 返回值: 成功:返回0 失败:返回-1
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> #include <fcntl.h> #define BUF_SIZE 1024 int main(int argc, char const *argv[]) { //创建pipe函数所需要的参 int fd[2] = {0}; //检测 pipe 函数的返回值变量 int ret = 0; //创建一个变量用来接收fork函数的返回值 pid_t pid = 0; //创建一个数组用来作为读取函数的数据缓冲区 char rev_buf[BUF_SIZE] = {0}; //创建管道 ret = pipe(fd); //判断管道是否创建成功 if(ret == 0) { printf("pipe is ok\n"); } //调用父子进程函数 pid = fork(); //通过返回值判断父子进程是否创建成功 if(pid < 0) { perror("fork error"); } //给子进程添加功能实现 if(pid == 0) { //改变管道读取数据文件描述符的状态为阻塞 fcntl(fd[0], F_SETFL, 0); //fcntl(fd[0], F_SETFL, O_NONBLOCK); while(1) { //关闭 写管道文件描述符 close(fd[1]); //读操作,函数参数为文件描述符,存放读取数据的内存首地址,读取到的字节个数 if(read(fd[0], rev_buf, BUF_SIZE) < 0) { perror("read error"); exit(-1); } printf("rev_buf -->%s\n", rev_buf); } //因为读取函数阻塞,无法进行到退出操作 exit(1); } else { //挂起 3 s sleep(3); //关闭 读管道文件描述符 close(fd[0]); //写操作,函数参数为文件描述符,写入的数据,写入数据的长度 if(write(fd[1], "iotming", strlen("iotming"))< 0) { perror("write error"); exit(-1); } //资源回收函数 wait(NULL); } return 0; }
文件描述符
-
文件描述符是非负整数,是文件的标识
-
用户使用文件描述符 (file descriptor) 来访问文件
-
利用 open 打开一个文件时,内核会返回一个文件描述符
-
每个进程都有一个文件描述符的表,进程刚被创建时,标准输入,标准输出,标准错误输出设备文件被打开,对应的文件描述符 0,1,2记录在表中
-
在进程中打开其他文件时,系统会返回文件描述符表中最小可用的文件描述符,并将此文件描述符记录在表中
0、1、2文件描述符
STDIN_FILENO --标准输入 0 scanf STDOUT_FILENO --标准输出 1 printf STDERR_FILENO --标准错误 2 error
文件描述符的复制函数
#include <unistd.h> int dup(int oldfd); 功能: 复制 oldfd 文件描述符,并分配一个新的文件描述符,新的文件描述符是调用进程文件描述符表中最小可用的文件描述符 参数: 要复制的文件描述符 oldfd 返回值: 成功:新文件描述符 失败:返回 -1,错误代码存于 errno 中
#include <unistd.h> int dup2(int oldfd, int newfd) 功能: 复制一份打开的文件描述符 oldfd,并分配新的文件描述符 newfd, newfd 也标识 oldfd 所标识的文件 (newfd 是小于文件描述符最大允许值的非负整数,如果 newfd 是一个已经打开的文件描述符,则首先关闭该文件,然后再复制) 参数: 要复制的文件描述符 oldfd 分配的新的文件描述符 newfd 返回值: 成功: 返回 newfd 失败: 返回-1, 错误代码存于 errno 中
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> #include <fcntl.h> #define BUF_SIZE 1024 int main(int argc, char const *argv[]) { //创建pipe函数所需要的参 int fd[2] = {0}; //检测 pipe 函数的返回值变量 int ret = 0; //创建一个变量用来接收fork函数的返回值 pid_t pid = 0; //创建一个数组用来作为读取函数的数据缓冲区 char rev_buf[BUF_SIZE] = {0}; //存放新的文件描述符 int new_fd = 0; printf("write test\n"); //写入数据函数,在文件标识符 1 中写入数据 write(1, "hello world\n", strlen("hello world\n")); //复制新的文件描述符 //new_fd = dup(STDOUT_FILENO); //用dup2函数将文件标识符 1 复制到文件标识符 6 中 new_fd = dup2(STDOUT_FILENO, 6); printf("new_fd -->%d\n", new_fd); //将 数据写入新的文件标识符中 write(new_fd ,"hello world111\n", strlen("hello world111\n")); }
使用 dup 或者 dup2 复制文件描述符后,新文件描述符和旧文件描述符指向同一个文件,共享文件锁定,读写位置和各项权限
当关闭新的文件描述符时,通过旧文件描述符仍可操作文件
当关闭旧的文件描述时,通过新的文件描述符仍可可操作文件
有名管道
概念
命名管道主要是用于没有亲属关系的进程通信,有名管道需要我们自己创建当进程退出后,有名管道依然会存在于我们的文件系统中,有名管道必须是有名字了,后续可以通过名字进行通信
特性
-
半双工,数据在同一时刻只能在一个方向上流动
-
写入 FIFO 中的数据遵循先入先出的规则
-
FIFO 所传送的数据是无格式的,这要求 FIFO 的读出方与写入方必须事先约定好数据的格式,如多少字节算一个消息等
-
FIFO 在文件系统中作为一个特殊的文件而存在,但 FIFO 中的内容却存放在内存中
-
管道在内存中对应一个缓冲区,不同的系统其大小不一定相同
-
从 FIFO 读数据是一次性操作, 数据一旦被读, 它就从 FIFO 中被抛弃, 释放空间以便写更多的数据
-
当使用 FIFO 的进程退出后, FIFO 文件将继续保存在文件系统中以便以后使用
-
FIFO 有名字, 不相关的进程可以通过打开命名管道进行通信
有名管道中可以很好地解决在无关进程间数据交换的要求,并且由于它们是存在于文件系统中的,这也提供了一种比匿名管道更持久稳定的通信办法。有名管道在一些专业书籍中叫做命名管道,它的特点
1.可以是无关联的进程通过 fifo 文件描述符进行数据传递
2.单向传输有一个写入端和一个读出端, 操作方式和无名管道相同
创建命名管道函数
#include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode); 参数: pathname: FIFO 的路径名+文件名; mode : mode_t 类型的权限描述符 返回值: 成功: 返回0 失败: 如果文件已经存在, 则会出错且非返回 -1
有名管道使用步骤
-
使用 mkfifo() 创建 fifo 文件描述符
-
打开管道文件描述符
-
通过读写文件描述符进行单向数据传输
写入通道数据操作
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> #include <fcntl.h> #define BUF_SIZE 1024 int main(int argc, char const *argv[]) { //创建pipe函数所需要的参 int fd[2] = {0}; //检测 pipe 函数的返回值变量 int ret = 0; //创建一个变量用来接收fork函数的返回值 pid_t pid = 0; //创建一个数组用来作为读取函数的数据缓冲区 char rev_buf[BUF_SIZE] = {0}; //存放新的文件描述符 int new_fd = 0; //创建命名管道参数为管道名跟读写权限 ret = mkfifo("fifo_test", 0666); //判断命名管道是否创建成功 if(ret == 0) { printf("mkfifo is ok\n"); } else { exit(-1); } //打开创建好的管道并用选择读写模式 new_fd = open("fifo_test", O_RDWR); //打印成功返回的文件描述符 printf("new_fd --> %d\n", new_fd); //给管道写入数据 ret = write(new_fd, "hello world\n", strlen("hello world\n")); if (ret < 0) { perror("write error"); exit(-2); } close(new_fd); }
读取管道数据操作
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> #include <fcntl.h> #define BUF_SIZE 1024 int main(int argc, char const *argv[]) { //创建pipe函数所需要的参 int fd[2] = {0}; //检测 pipe 函数的返回值变量 int ret = 0; //创建一个变量用来接收fork函数的返回值 pid_t pid = 0; //创建一个数组用来作为读取函数的数据缓冲区 char rev_buf[BUF_SIZE] = {0}; //存放新的文件描述符 int new_fd = 0; //创建命名管道参数为管道名跟读写权限 ret = mkfifo("fifo_test", 0666); //判断命名管道是否创建成功 if(ret == 0) { printf("mkfifo is ok\n"); } else { exit(-1); } //打开创建好的管道并用选择读取模式 new_fd = open("fifo_test", O_RDONLY); //打印成功返回的文件描述符 printf("new_fd --> %d\n", new_fd); //ret = write(new_fd, "hello world\n", strlen("hello world\n")); //读取 new_fd 文件标识符中的数据,存放到rev_buf数组中,读取数据的最大长度 ret = read(new_fd, rev_buf, BUF_SIZE); if (ret < 0) { perror("read error"); exit(-2); } //打印读取到的数据 printf("rev_buf-->%s\n", rev_buf); close(new_fd); }
敬请期待下集!!!