管道的概念
管道是一种进程通信的方式,管道是一个载体,用于传输数据的通道
管道的特点
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);
}
敬请期待下集!!!
本文详细介绍了管道在进程通信中的作用,包括管道的特点、分类(无名管道和有名管道)、使用方法(如pipe函数、fork、read/write操作),以及如何处理阻塞和非阻塞情况。还涵盖了创建和使用命名管道(FIFO)的步骤和注意事项。
1173

被折叠的 条评论
为什么被折叠?



