为了能到远方,脚下的每一步都不能|

Yasuo_Hasaki

园龄:1年1个月粉丝:2关注:0

2024-02-24-物联网系统编程(5-管道、命名管道)

5.管道、命名管道

5.1 管道概述

​ 管道(pipe)又称无名管道。无名管道是一种特殊类型的文件,在应用层体现为两个打开的文件描述符。

​ 任何一个进程在创建的时候,系统都会 给他分配4G的虚拟内存,分为3G的用户空间和1G的内核空间,内核空间是所有进程公有的,无名管道就是创建在内核空间的,多个进程知道同一个无名管道的空间,就可以利用他来进行通信。

无名管道虽然是在内核空间创建的,但是会给当前用户进程两个文件描述符,一个负责执行读操作,一个负责执行写操作。

image-20240225171640809

管道是最古老的 UNIX IPC方式,其特点是:

  1. 半双工,数据在同一时刻只能在一个方向上流动;
  2. 数据只能从管道的一端写入,从另一端读出;
  3. 写入管道中的数据遵循先入先出的规则;
  4. 管道所传送的数据是无格式的,这要求管道的读出方与写入方必须事先约定好数据的格式,如多少字节算一个消息等;
  5. 管道不是普通的文件,不属于某个文件系统,其只存在于内存中;
  6. 管道在内存中对应一个缓冲区。不同的系统其大小不一定相同;
  7. 从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写更多的数据;
  8. 管道没有名字,只能在具有公共祖先的进程之间使用;

5.2 无名管道创建- pipe函数

#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建一个有名管道,返回两个文件描述符负责对管道进行读写操作;
参数:
pipefd:int型数组的首地址,里面有两个元素;
pipefd[0]负责对管道执行读操作;
pipefd[1]负责对管道执行写操作
返回值:
成功: 返回 0
失败: 返回 -1
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
int fd_pipe[2];
// 使用pipe创建无名管道
if (pipe(fd_pipe) == -1)
{
perror("fail to pipe\n");
exit(1);
}
printf("fd_pipe[0] = %d\n", fd_pipe[0]);
printf("fd_pipe[1] = %d\n", fd_pipe[1]);
// 对无名管道执行读写操作
// 由于无名管道给当前用户进程两个文件描述符,所以只要操作这两个文件
// 描述符就可以操作无名管道,所以通过文件I0中的read和write函数对无名管道进行操作
// 通过write函数向无名管道中写入数据
// fd_pipe[1]负责执行写操作
// 迪过write函数同无名管道中写人数据
// fd_pipe[1]负责执行写操作
// 如果管道中有数据,再次写入的数据会放在之前数据的后面,不会把之前的数据替换
if (write(fd_pipe[1], "hello world", 11) == -1)
{
perror("fail to write");
exit(1);
}
write(fd_pipe[1], "I LOVE Beijing", strlen("I LOVE Beijing") + 1);
// 通过read函数从无名管道中读取数据
// fd_pipe[e]负责执行读操作
// 读取数据时,直接从管道中读取指定个数的数据,如果管道中没有数据了,则read函数会阻塞等待
char buf[15] = "";
ssize_t bytes;
if ((bytes = read(fd_pipe[0], buf, sizeof(buf))) == -1)
{
printf("fail to read");
exit(1);
}
printf("[%s]\n", buf);
printf("bytes = %ld\n", bytes);
bytes = read(fd_pipe[0], buf, sizeof(buf));
printf("[%s]\n", buf);
printf("bytes = %ld\n", bytes);
return 0;
}

输出结果

fd_pipe[0] = 3
fd_pipe[1] = 4
[hello worldI LO]
bytes = 15
[VE Beijing]
bytes = 11

5.3 无名管道实现父子进程通信

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
// 使用无名管道实现父子进程间的通信
// 由于无名管道创建之后给当前进程两个文件描述符,所以如果是完全不相关的进程
// 无法获取同一个无名管道的文件描述符,所以无名管道只能在具有亲缘关系的进程间通信
int main(int argc, char const *argv[])
{
int pipefd[2];
if (pipe(pipefd) == -1)
{
perror("fail to pipe\n");
exit(1);
}
pid_t pid;
// 使用fork函数创建子进程
if ((pid = fork()) < 0)
{
perror("fail to fork");
exit(1);
}
else if (pid > 0)
{ // 父进程,负责给子进程发送数据
char buf[128] = {};
while (1)
{
fgets(buf, sizeof(buf), stdin);
{
buf[strlen(buf) - 1] = '\0';
if (write(pipefd[1], buf, sizeof(buf)) == -1)
{
perror("fail to write");
exit(1);
}
}
}
}
else
{ // 子进程
char buf[128] = "";
while (1)
{
/* 子进程接收父进程数据 */
if (read(pipefd[0], buf, sizeof(buf)) == -1)
{
perror("fail to read");
exit(1);
}
printf("from parent: %s\n");
}
}
return 0;
}

输出结果

hello world
from parent: hello world

注意: 利用无名管道实现进程问的通信,都是父进程创建无名管道,然后再创建子进程,子进程继承父进程的无名管道的文件描述符,然后父子进程通过读写无名管道实现通信。

5.4 无名管道读写规律

5.4.1 读写端都存在,只读不写

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
int main(int argc, char const *argv[])
{
int pipefd[2];
if (pipe(pipefd) == -1)
{
perror("fail to pipe\n");
exit(1);
}
// 读写端都存在,只读不写
// 如果管道中有数据,会正常读取数据
// 如果管道中不存在数据,会阻塞等待
write(pipefd[1], "hello world", 11);
char buf[128] = "";
if (read(pipefd[0], buf, sizeof(buf)) == -1)
{
perror("fail to read");
exit(1);
}
printf("buf = %s\n", buf);
if (read(pipefd[0], buf, sizeof(buf)) == -1)
{
perror("fail to read");
exit(1);
}
printf("buf = %s\n", buf);
return 0;
}

输出结果

buf = hello world

5.4.2 读写端都存在,只写不读

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
int main(int argc, char const *argv[])
{
int pipefd[2];
if (pipe(pipefd) == -1)
{
perror("fail to pipe\n");
exit(1);
}
// 读写端都存在,只写不读
// 如果一直执行写操作,则无名管道对应的缓冲区会被写满,写满之后,write函数也会阻塞等待
// 默认无名管道缓冲区为64k字节
int num = 0;
while (1)
{
if (write(pipefd[1], "hello world", 1024) == -1)
{
perror("fail to write");
exit(1);
}
num++;
printf("num = %d\n", num);
}
return 0;
}
num = 1
num = 2
num = 3
num = 4
num = 5
num = 6
num = 7
num = 8
num = 9
num = 10
num = 11
num = 12
num = 13
num = 14
num = 15
num = 16
num = 17
num = 18
num = 19
num = 20
num = 21
num = 22
num = 23
num = 24
num = 25
num = 26
num = 27
num = 28
num = 29
num = 30
num = 31
num = 32
num = 33
num = 34
num = 35
num = 36
num = 37
num = 38
num = 39
num = 40
num = 41
num = 42
num = 43
num = 44
num = 45
num = 46
num = 47
num = 48
num = 49
num = 50
num = 51
num = 52
num = 53
num = 54
num = 55
num = 56
num = 57
num = 58
num = 59
num = 60
num = 61
num = 62
num = 63
num = 64

5.4.3 只有读端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
int main(int argc, char const *argv[])
{
// 如果管道中有数据,会正常读取数据
// 如果管道中没有数据,会返回0
int pipefd[2];
if (pipe(pipefd) == -1)
{
perror("fail to pipe\n");
exit(1);
}
write(pipefd[1], "hello world", 11);
close(pipefd[1]);
// 关闭文件描述符,只有读端
char buf[128] = "";
ssize_t bytes;
if ((bytes = read(pipefd[0], buf, sizeof(buf))) == -1)
{
perror("fail to read");
exit(1);
}
printf("bytes = %ld\n", bytes);
printf("buf = %s\n", buf);
// 清空管道内部的内容
memset(buf, 0, sizeof(buf));
if ((bytes = read(pipefd[0], buf, sizeof(buf))) == -1)
{
perror("fail to read");
exit(1);
}
printf("bytes = %ld\n", bytes);
printf("buf = %s\n", buf);
return 0;
}

输出结果

bytes = 11
buf = hello world
bytes = 0
buf =

5.4.4 只有写端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
void handler(int sig)
{
printf("SIGPIPE信号产生了,管道破裂了\n");
}
int main(int argc, char const *argv[])
{
signal(SIGPIPE, handler);
int pipefd[2];
if (pipe(pipefd) == -1)
{
perror("fail to pipe\n");
exit(1);
}
// 关闭读文件信号描述符,会出现断管现象
close(pipefd[0]);
int num = 0;
while (1)
{
if (write(pipefd[1], "hello world", 1024) == -1)
{
perror("fail to write");
exit(1);
}
num++;
printf("num = %d\n", num);
}
return 0;
}

输出结果

SIGPIPE信号产生了,管道破裂了
fail to write: Broken pipe

5.5 fcntl设置文件的阻塞特性

从管道中读数据的特点

  1. 默认用 read 函数从管道中读数据是阻塞的。
  2. 调用 write 函数向管道里写数据,当缓冲区已满时 write 也会阻塞。
  3. 通信过程中,读端口全部关闭后,写进程向管道内写数据时,写进程会(收到 SIGPIPE 信号)退出

编程时可通过fcntl函数设置文件的阻塞特性:

// 设置为阻塞:
fcntl(fd, F_SETFL, 0);
// 设置为非阻塞:
fentl(fd, F_SETFL, O_NONBLOCK):

非阻塞:

  1. 如果是阻塞,管道中没有数据,read会一直等待,直到有数据才会继续运行,否则一直等待;
  2. 如果是非阻塞,read函数运行时,会先看一下管道中是否有数据,如果有数据,则正常运行读取数据;如果管道中没有数据,则read函数会立即返回,继续下面的代码运行。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
int fd_pipe[2];
char buf[] = "hello world\n";
pid_t pid;
if (pipe(fd_pipe) < 0)
{
perror("fail to pipe");
exit(1);
}
pid = fork();
if (pid < 0)
{
perror("fail to fork");
exit(1);
}
if (pid == 0)
{
while (1)
{
sleep(5);
write(fd_pipe[1], buf, strlen(buf));
}
}
else
{
while (1)
{
// 使用非阻塞
fcntl(fd_pipe[0], F_SETFL, O_NONBLOCK);
// 使用阻塞,默认效果
// fcntl(fd_pipe[0],F_SETFL,0);
memset(buf, 0, sizeof(buf));
read(fd_pipe[0], buf, sizeof(buf));
printf("buf = [%s] \n", buf);
sleep(1);
}
}
return 0;
}

输出结果

buf = []
buf = []
buf = []
buf = []
buf = []
buf = [hello world
]
buf = []
buf = []
buf = []
buf = []
buf = [hello world
]
buf = []
buf = []
buf = []
buf = []
buf = [hello world
]
buf = []
buf = []
buf = []
buf = []
buf = [hello world
]

5.6 文件描述符概述

​ 文件描述符是非负整数,是文件的标识。用户使用文件描述符(file descriptor)来访问文件。

​ 利用open打开一个文件时,内核会返回一个文件描述符。每个进程都有一张文件描述符的表,进程刚被创建时,标准输入、标准输出、标准错误输出设备文件被打开,对应的文件描述符0、1、2 记录在表中。
​ 在进程中打开其他文件时,系统会返回文件描述符表中最小可用的文件描述符,并将此文件描述符记录在表中。
注意:
​ Linux中一个进程最多只能打开NR_OPEN_DEFAULT(即1024)个文件,故当文件不再使用时应及时调用close函数关闭文件。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
#if 0
close(0);
int fd1, fd2, fd3;
fd1 = open("file.txt", O_RDONLY | O_CREAT, 0664);
fd2 = open("file.txt", O_RDONLY | O_CREAT, 0664);
fd3 = open("file.txt", O_RDONLY | O_CREAT, 0664);
printf("fd1 = %d\n", fd1);
printf("fd2 = %d\n", fd2);
printf("fd3 = %d\n", fd3);
return 0;
#endif
#if 1
int fd;
//Linux中一个进程最多只能打开NR_OPEN_DEFAULT(即1024)个文件
//故当文件不再使用时应及时调用close函数关闭文件
while (1)
{
if ((fd = open("file.txt", O_RDONLY | O_CREAT, 0664)) < 0)
{
perror("fail to open");
exit(1);
}
printf("fd = %d\n", fd);
}
return 0;
#endif
}

5.7 文件描述符的复制

dup,和 dup2 是两个非常有用的系统调用,都是用来复制一个文件的描述符,使新的文件描述符也标识旧的文件描述符所标识的文件。

int dup(int gldfd);
int dup2(int oldfd, int newfd);

dup dup2 经常用来重定向进程的stdin、stdout 和 stderr

5.7.1 dup函数

#include <unistd.h>
int dup(int oldfd);
功能:复制 oldfd文件描述符,并分配一个新的文件描述符,新的文件描述符是调用进程文件描述符表中最小可用的文件描述符;
参数:
要复制的文件描述符 oldfd;
返回值:
成功:新文件描述符
失败:返回-1,错误代码存于 errno 中
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(void)
{
// 通过dup复制一个文件描述符
int fd;
fd = dup(1);
printf("fd = %d\n", fd);
// 由于通过dup函数将1复制了一份,所以fd相当于1,可以向终端写数据
write(fd, "nihao beijing\n", strlen("nihao beijing\n"));
return 0;
}

输出结果

fd = 3
nihao beijing
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(void)
{
// 如果要实现输出重定向的功能
// printf是操作文件描述符为1所对应的文件,
// 默认是操作终端,只要能把1对应的文件改为文件,那么就可以实现重定向,将原本输出到终端的内容输出到文件中
int fd_file;
fd_file = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664);
printf("fd = %d\n", fd_file);
if (fd_file == -1)
{
/* code */
perror("fail to open");
exit(1);
}
// 此处关闭了1
close(1);
// 再复制一个,则fd的文件标识符就是1,而这个1指向的是文件test.txt
// 又因为printf指向的是文件标识符1,所以此时printf指向的是文件标识符3,也就是test.txt
// printf -> 1 <==> 3 -> test.txt
int fd = dup(fd_file);
printf("hello world\n");
printf("fd = %d\n", fd);
return 0;
}

终端输出

fd_file = 3

test.txt输出

hello world
fd = 1

5.7.2 dup2函数

#include <unistd.h>
int dup2(int oldfd, int newfd)
功能: 复制一份打开的文件描述符 oldfd,并分配新的文件描述符newfd,newfd,也标识 oldfd,所标识的文件。
参数:
要复制的文件描述符 oldkd
分配的新的文件描述符 newfd
返回值:
成功: 返回 newfd
失败: 返回-1,错误代码存于error中
注意: newfd是小于文件描述符最大允许值的非负整数,如果 newfd,是一个已经打开的文件描述符,则首先关闭该文件,然后再复制。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char const *argv[])
{
int fd1;
int fd2 = 3;
// 将文件标识符1复制一份为fd2,所以fd2标识的是标准输出
dup2(1, fd2);
printf("fd2 = %d\n", fd2);
fd1 = open("test01.txt", O_CREAT | O_RDWR, 0664);
// 输出重定向:关闭文件描述符1,将df复制一份为1,所以1此时表示的是text01.txt文件
dup2(fd1, 1);
printf("hello world\n");
// 再次更改输出,将文件标识符fd2复制为1,此时1表示终端
dup2(fd2, 1);
printf("nihao beijing\n");
return 0;
}

终端输出

fd2 = 3
nihao beijing

test01.txt输出

hello world

5.8 有名管道

5.8.1 有名管道概述

命名管道(FIF0)和管道(pipe)基本相同,但也有一些显著的不同,其特点是:

  1. 半双工,数据在同一时刻只能在一个方向上流动。
  2. 写入 FIFO 中的数据遵循先入先出的规则。
  3. FIFO 所传送的数据是无格式的,这要求 FIFO 的读出方与写入方必须事先约定好数据的格式,如多少字节算一个消息等。
  4. FIFO 在文件系统中作为一个特殊的文件而存在并且在文件系统中可见,但FIFO 中的内容却存放在内存中。
  5. 管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。
  6. 从 FIFO 读数据是一次性操作,数据一旦被读,它就从 FIFO 中被抛弃,释放空间以便写更多的数据。
  7. 当使用 FIFO 的进程退出后,FIFO 文件将继续保存在文件系统中以便以后使用。
  8. FIFO 有名字,不相关的进程可以通过打开命名管道进行通信。

5.8.2 有名管道创建

# 方法一 通过shell创建
mkfifo 文件名
$ mkfifo myfifo
$ ll
-rw-rw-r-- 1 spider spider 0 2月 26 11:24 file.txt
prw-rw-r-- 1 spider spider 0 2月 26 14:58 myfifo|
-rw-rw-r-- 1 spider spider 12 2月 26 14:29 test01.txt
-rw-rw-r-- 1 spider spider 19 2月 26 14:28 test.txt
// 方法2 使用mkfifo函数
#include <sys/types.h>
#include<sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
功能:创建一个有名管道,产生一个本地文件系统可见的文件pathname;
参数:
pathname:有名管道创建后生成的文件,可以带路径;
mode:管道文件的权限,一般通过八进制数设置即可,例如8664;
返回值:
成功: 0
失败: -1
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
int main(int argc, char const *argv[])
{
// 通过mkfifo函数创建有名管道
if (mkfifo("fifo file", 0664) == -1)
{
if (errno != EEXIST)
{
perror("fall to mkfifo");
exit(1);
}
}
return 0;
}

终端

$ ll
prw-rw-r-- 1 spider spider 0 2月 26 15:12 fifo_file|

5.8.3 有名管道的基本读写操作

由于有名管道在本地创建了一个管道文件,所以系统调用的IO函数基本都可以对有名管道进行操作,但是不能使用Iseek修改管道文件的偏移量

注意:有名管道创建的本地的文件只是起到标识作用,真正有名管道实现进程间通信还是在内核空间开辟内存,所以本地产生的文件只是一个标识,没有其他作用,对本地管道文件的操作实质就是对内核空间的操作

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#define FIFONAME "fifo_file"
int main(int argc, char const *argv[])
{
// 通过mkfifo函数创建有名管道
if (mkfifo("fifo_file", 0664) == -1)
{
if (errno != EEXIST)
{
perror("fall to mkfifo");
exit(1);
}
}
// 通过open函数打开文件
int fd;
fd = open(FIFONAME, O_RDWR);
if (fd == -1)
{
perror("fail to open\n");
exit(1);
}
// 通过write函数写数据
if ((write(fd, "hello world", strlen("hello world")) == -1))
{
perror("fail to write\n");
exit(1);
}
write(fd, "nihao beijing", strlen("nihao beijing"));
// 通过read函数读取数据
char buf[32] = "";
if (read(fd, buf, sizeof(buf)) == -1)
{
perror("fail to read\n");
exit(1);
}
printf("buf = [%s]\n", buf);
// 使用close函数关闭文件标识符
close(fd);
return 0;
}

输出结果

buf = [hello worldnihao beijing]

5.8.4 有名管道的进程通信

14_fifo_send.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#define FIFONAME1 "fifo_file1"
#define FIFONAME2 "fifo_file2"
int main(int argc, char const *argv[])
{
// 通过mkfifo函数创建有名管道
if (mkfifo(FIFONAME1, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fall to mkfifo");
exit(1);
}
}
if (mkfifo(FIFONAME2, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fall to mkfifo");
exit(1);
}
}
int fd_w, fd_r;
if ((fd_w = open(FIFONAME1, O_WRONLY)) == -1)
{
perror("fail to write\n");
}
if ((fd_r = open(FIFONAME2, O_RDONLY)) == -1)
{
perror("fail to read\n");
}
char buf[128] = "";
ssize_t bytes;
while (1)
{
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
if (bytes = write(fd_w, buf, sizeof(buf)) == -1)
{
perror("fail to write\n");
}
if (bytes = read(fd_r, buf, sizeof(buf)) == -1)
{
perror("fail to read\n");
}
printf("from recv: %s\n", buf);
}
return 0;
}

15_fifo_recv.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#define FIFONAME1 "fifo_file1"
#define FIFONAME2 "fifo_file2"
int main(int argc, char const *argv[])
{
// 通过mkfifo函数创建有名管道
if (mkfifo(FIFONAME1, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fall to mkfifo");
exit(1);
}
}
if (mkfifo(FIFONAME2, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fall to mkfifo");
exit(1);
}
}
int fd_w, fd_r;
if ((fd_r = open(FIFONAME1, O_RDONLY)) == -1)
{
perror("fail to read\n");
}
if ((fd_w = open(FIFONAME2, O_WRONLY)) == -1)
{
perror("fail to write\n");
}
char buf[128] = "";
ssize_t bytes;
while (1)
{
if (bytes = read(fd_r, buf, sizeof(buf)) == -1)
{
perror("fail to read\n");
}
printf("from recv: %s\n", buf);
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
if (bytes = write(fd_w, buf, sizeof(buf)) == -1)
{
perror("fail to write\n");
}
}
return 0;
}

image-20240226160004330

5.9 有名管道的读写规律

5.9.1 读写端都存在,只读不写

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#define FIFONAME1 "my_fifo"
int main(int argc, char const *argv[])
{
// 通过mkfifo函数创建有名管道
if (mkfifo(FIFONAME1, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fall to mkfifo");
exit(1);
}
}
// 打开管道
int fd;
// 读写端由标志符决定 O_RDWR- 读写端都存在
if ((fd = open(FIFONAME1, O_RDWR)) == -1)
{
perror("fail to open\n");
}
// 写入数据
write(fd, "hello world", 11);
char buf[128] = "";
// 读取数据
read(fd, buf, sizeof(buf));
printf("buf = %s\n", buf);
// 没有数据,再读取,会阻塞
read(fd, buf, sizeof(buf));
printf("buf = %s\n", buf);
return 0;
}

输出结果

buf = hello world

5.9.2 读写端都存在,只写不读

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#define FIFONAME1 "my_fifo"
int main(int argc, char const *argv[])
{
// 通过mkfifo函数创建有名管道
if (mkfifo(FIFONAME1, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fall to mkfifo");
exit(1);
}
}
// 打开管道
int fd;
// 读写端由标志符决定 O_RDWR - 读写端都存在
// 读写端都存在,只写不读,write函数会发生阻塞
// 默认有名管道的缓冲区为64k字节
if ((fd = open(FIFONAME1, O_RDWR)) == -1)
{
perror("fail to open\n");
}
// 写入数据
int num=0;
while (1)
{
write(fd, "", 1024);
num++;
printf("num = %d\n", num);
}
return 0;
}

输出结果

num = 1
num = 2
num = 3
num = 4
num = 5
num = 6
num = 7
num = 8
num = 9
num = 10
num = 11
num = 12
num = 13
num = 14
num = 15
num = 16
num = 17
num = 18
num = 19
num = 20
num = 21
num = 22
num = 23
num = 24
num = 25
num = 26
num = 27
num = 28
num = 29
num = 30
num = 31
num = 32
num = 33
num = 34
num = 35
num = 36
num = 37
num = 38
num = 39
num = 40
num = 41
num = 42
num = 43
num = 44
num = 45
num = 46
num = 47
num = 48
num = 49
num = 50
num = 51
num = 52
num = 53
num = 54
num = 55
num = 56
num = 57
num = 58
num = 59
num = 60
num = 61
num = 62
num = 63
num = 64

5.9.3 在一个进程中,只有读端,没有写端

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#define FIFONAME1 "my_fifo"
int main(int argc, char const *argv[])
{
// 通过mkfifo函数创建有名管道
if (mkfifo(FIFONAME1, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fall to mkfifo");
exit(1);
}
}
printf("************************\n");
// 打开管道
int fd;
// 读写端由标志符决定 O_RDONLY - 只有读端没有写端
// 此时会在open处阻塞
if ((fd = open(FIFONAME1, O_RDONLY)) == -1)
{
perror("fail to open\n");
}
printf("-------------------------------\n");
// 读取内容
char buf[128] = "";
ssize_t bytes;
if ((bytes = read(fd, buf, sizeof(buf)) == -1))
{
perror("fail to read\n");
exit(1);
}
printf("bytes = %ld\n", bytes);
printf("buf = %s\n", buf);
return 0;
}

输出结果

************************

5.9.4 在一个进程中,只有写端,没有读端

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#define FIFONAME1 "my_fifo"
int main(int argc, char const *argv[])
{
// 通过mkfifo函数创建有名管道
if (mkfifo(FIFONAME1, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fall to mkfifo");
exit(1);
}
}
printf("************************\n");
// 打开管道
int fd;
// 读写端由标志符决定 O_WRONLY- 只有写端没有读端
// 此时会在open处阻塞
if ((fd = open(FIFONAME1, O_WRONLY)) == -1)
{
perror("fail to open\n");
}
printf("-------------------------------\n");
// 写入内容
write(fd, "hello world", 11);
printf("666\n");
return 0;
}

输出结果

************************

5.9.5 一个进程负责只写端,另一个进程负责只读端

将5.9.3 和5.9.4 中的代码同时运行,保证有名管道的读写端都存在。

规律:

  1. 只要保证有名管道的读写端都存在,就不会发生阻塞
  2. 如果一个进程只读,一个进程只写,都运行后,关闭写端,读端read会返回0
  3. 如果一个进程只读,一个进程只写,都运行后,关闭读端,写端立即产生SIGPIPE信号(断管),默认处理方式是退出

5.9.6 有名管道非阻塞规律

指定 O_NONBLOCK(即 open 位或 O_NONBLOCK):

  1. 先以只读方式打开: 如果没有进程已经为写而打开一个 FIFO, 只读 open 成功,并且 open 不阻塞;
  2. 先以只写方式打开:如果没有进程已经为读而打开一个FIFO,只写 open 将出错返回-1;
  3. read、write 读写命名管道中读数据时不阻塞;
  4. 通信过程中,读进程退出后,写进程向命名管道内写数据时,写进程也会收到SIGPIPE 信号退出。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
int main(int argc, char const *argv[])
{
int fd;
if (mkfifo("myfifo", 0664) == -1)
{
if (errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
#if 0
// 如果open 标志位设置非阻塞,open和read都不会阻塞
fd = open("myfifo", O_RDONLY | O_NONBLOCK);
if (fd < 0)
{
perror("open fifo");
exit(1);
}
while (1)
{
char recv[100];
bzero(recv, sizeof(recv));
read(fd, recv, sizeof(recv));
printf("read from my_fifo buf = [%s]\n", recv);
sleep(1);
}
#endif
#if 0
char send[100] = "hello I love you";
// 以写的方式打开,在open处会报错 open fifo: No such device or address
fd = open("myfifo", O_WRONLY | O_NONBLOCK);
if (fd < 0)
{
perror("open fifo");
exit(1);
}
write(fd, send, strlen(send));
printf("write to my_fifo buf = %s\n",send);
char recv[100];
read(fd, recv, sizeof(recv));
printf("read from my_fifo buf = [%s]\n", recv);
#endif
#if 1
char send[100] = "hello I love you";
// 以可读可写的方式打开,那么和阻塞效果一样
fd = open("myfifo", O_RDWR | O_NONBLOCK);
if (fd < 0)
{
perror("open fifo");
exit(1);
}
write(fd, send, strlen(send));
printf("write to my_fifo buf = %s\n",send);
char recv[200];
read(fd, recv, sizeof(recv));
printf("read from my_fifo buf = [%s]\n", recv);
#endif
return 0;
}

输出结果

write to my_fifo buf = hello I love you
read from my_fifo buf = [hello I love you]
posted @   Yasuo_Hasaki  阅读(11)  评论(0编辑  收藏  举报
//雪花飘落效果
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起