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 @ 2024-02-26 18:10  Yasuo_Hasaki  阅读(15)  评论(0)    收藏  举报
//雪花飘落效果