Linux 高并发学习笔记 - 有名管道实现进程间通信

2.3.3 有名管道

Linux 高并发学习笔记 - 笔记索引

有名管道概述与原理
  • 有名管道FIFO命名管道)以一个文件实体的形式存在于磁盘中,但有名管道的大小始终为0 BytesFIFO文件被打开,将在内存中生成管道缓冲区,所有管道流经过该缓冲区,但始终不经过磁盘。磁盘中的FIFO文件仅用于定位有名管道,不存储任何数据。
  • 管道不同的,有名管道可用于实现公共祖先的进程间通信
  • 管道具有类似的结构和原理。
有名管道阻塞特征
  • 有名管道阻塞特征基本与管道阻塞特征相同,需要注意的是:读端必须先于写端打开,详见下表。

管道阻塞特征 : { 操作读端 : {      s i z e r e f n u m 打开读端 有进程引用写端 无进程引用写端 管道非空 阻塞,等待写端准备连接 正常读取,返回实际读取量 正常读取,返回实际读取量 管道为空 阻塞,等待写端准备连接 阻塞,等待管道写入 E O F ,返回 0 操作写端 : {      s i z e r e f n u m 打开写端 有进程引用读端 引用读端文件描述符全部关闭 管道未满 阻塞,等待读端连接 正常写入,返回实际写入量 触发 S I G P I P E 信号,异常退出 管道已满 阻塞,等待读端连接 阻塞,等待管道读出 触发 S I G P I P E 信号,异常退出 管道非阻塞特征 : { 操作读端 : {      s i z e r e f n u m 打开读端 有进程引用写端 无进程引用写端 管道非空 直接打开 正常读取,返回实际读取量 正常读取,返回实际读取量 管道为空 直接打开 返回 0 ,不更改接收变量 E O F ,返回 0 操作写端 : {      s i z e r e f n u m 打开写端 有进程引用读端 引用读端文件描述符全部关闭 管道未满 返回-1,不存在设备 正常写入,返回实际写入量 触发 S I G P I P E 信号,异常退出 管道已满 返回-1,不存在设备 返回-1,管道已满 触发 S I G P I P E 信号,异常退出 \begin{array}{rl} 管道阻塞特征: & \left\{ \begin{array}{l} 操作读端:\left\{~~~~ \begin{array}{c|cc} {_{size}}{^{refnum}} & 打开读端 & 有进程引用写端 & 无进程引用写端 \\ \hline 管道非空 & 阻塞,等待写端准备连接 & 正常读取,返回实际读取量 & 正常读取,返回实际读取量 \\ 管道为空 & 阻塞,等待写端准备连接 & 阻塞,等待管道写入 & EOF,返回0\\ \end{array} \right. \\ \\ 操作写端:\left\{~~~~ \begin{array}{c|cc} {_{size}}{^{refnum}} & 打开写端 & 有进程引用读端 & 引用读端文件描述符全部关闭 \\ \hline 管道未满 & 阻塞,等待读端连接 & 正常写入,返回实际写入量 & 触发SIGPIPE信号,异常退出 \\ 管道已满 & 阻塞,等待读端连接 & 阻塞,等待管道读出 & 触发SIGPIPE信号,异常退出 \\ \end{array} \right. \end{array} \right. \\ \\ 管道非阻塞特征: & \left\{ \begin{array}{l} 操作读端:\left\{~~~~ \begin{array}{c|cc} {_{size}}{^{refnum}} & 打开读端 & 有进程引用写端 & 无进程引用写端 \\ \hline 管道非空 & 直接打开 & 正常读取,返回实际读取量 & 正常读取,返回实际读取量 \\ 管道为空 & 直接打开 & 返回0,不更改接收变量 & EOF,返回0\\ \end{array} \right. \\ \\ 操作写端:\left\{~~~~ \begin{array}{c|cc} {_{size}}{^{refnum}} & 打开写端 & 有进程引用读端 & 引用读端文件描述符全部关闭 \\ \hline 管道未满 & 返回\text{-1},不存在设备 & 正常写入,返回实际写入量 & 触发SIGPIPE信号,异常退出 \\ 管道已满 & 返回\text{-1},不存在设备 & 返回\text{-1},管道已满 & 触发SIGPIPE信号,异常退出 \\ \end{array} \right. \end{array} \right. \\ \\ \end{array} 管道阻塞特征:管道非阻塞特征: 操作读端:     sizerefnum管道非空管道为空打开读端阻塞,等待写端准备连接阻塞,等待写端准备连接有进程引用写端正常读取,返回实际读取量阻塞,等待管道写入无进程引用写端正常读取,返回实际读取量EOF,返回0操作写端:     sizerefnum管道未满管道已满打开写端阻塞,等待读端连接阻塞,等待读端连接有进程引用读端正常写入,返回实际写入量阻塞,等待管道读出引用读端文件描述符全部关闭触发SIGPIPE信号,异常退出触发SIGPIPE信号,异常退出 操作读端:     sizerefnum管道非空管道为空打开读端直接打开直接打开有进程引用写端正常读取,返回实际读取量返回0,不更改接收变量无进程引用写端正常读取,返回实际读取量EOF,返回0操作写端:     sizerefnum管道未满管道已满打开写端返回-1,不存在设备返回-1,不存在设备有进程引用读端正常写入,返回实际写入量返回-1,管道已满引用读端文件描述符全部关闭触发SIGPIPE信号,异常退出触发SIGPIPE信号,异常退出

有名管道操作
  • 命令行生成管道文件
$ mkfifo <pathname>  // 生成管道文件
  • 创建、打开与读写管道文件
#include <sys/types.h>
#include <sys/stat.h>
// 	create a FIFO special file
// 		pathname:
// 			FIFO file path to create
// 		mode:
// 			create mode, same to mode in "open"
// 		return value:
// 			return 0 for success, or -1 for errors including file exist and etc.
int mkfifo(const char *pathname, mode_t mode);
// $ man 3 mkfifo

#include <fcntl.h>
// 	open a FIFO file in read or write mode
// 		pathname:
// 			FIFO file to read or write
// 		flags:
// 			O_RDONLY for FIFO read mode,
// 			O_WRONLY for FIFO write mode,
// 			O_NONBLOCK (optional) for non-block IO
// 		return value:
// 			return file descriptor of FIFO, or -1 for error
int open(const char *pathname, int flags);

// read
ssize_t read(int fd, void *buf, size_t count);

// write
ssize_t write(int fd, const void *buf, size_t count);
实际案例:本机聊天功能
  • 为简化案例,本聊天功能仅实现 一对一 一来一回式 交互。
  • 本聊天功能使用一体式程序,使用两方分离式程序更方便些。
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 错误处理函数: 打印错误信息并退出
void err(int ret, const char cmd[]) {
    if (ret == -1) {
        perror(cmd);
        exit(0);
    }
}

// 父进程函数: 处理子进程正常退出与异常退出问题
void parent() {
    int wstatus;
    int ret = wait(&wstatus);
    if (access("chatr.fifo", F_OK) == 0) {
        // 删除临时管道文件
        unlink("chatr.fifo");
    }
    if (access("chatw.fifo", F_OK) == 0) {
        // 删除临时管道文件
        unlink("chatw.fifo");
    }
    if (WIFSIGNALED(wstatus)) {
        printf("对方异常退出。\n");
    }
}

// 子进程函数 - 主函数
void child() {
    int flag = 0;  // 对话发起方标记
    if (access("chatr.fifo", F_OK) == -1) {
        mkfifo("chatr.fifo", 0664);
        flag = 1;  // 首先创建管道文件的为对话发起方
    }
    if (access("chatw.fifo", F_OK) == -1) {
        mkfifo("chatw.fifo", 0664);
        flag = 1;  // 首先创建管道文件的为对话发起方
    }
    int fdr, fdw;
    if (flag) {
        // 如果是对话发起方
        printf("正在等待加入聊天...\n");
        /* 注意: 
        	chatr.fifo 与 chatw.fifo 文件在主客进程中打开次序必须保持一致, 
        	否则由于阻塞机制存在, 进程将一直阻塞.
        */
        fdr = open("chatr.fifo", O_RDONLY);
        err(fdr, "open");
        fdw = open("chatw.fifo", O_WRONLY);
        err(fdw, "open");
        printf("你是聊天的发起人。\n");
    } else {
        // 如果是对话接受方
        printf("正在加入聊天...\n");
        fdw = open("chatr.fifo", O_WRONLY);
        err(fdw, "open");
        fdr = open("chatw.fifo", O_RDONLY);
        err(fdr, "open");
        printf("你是聊天的接受人。\n");
    }

    char buf[1024];
    int turn = flag;
    while (1) {
        if (turn) {
            printf("到你发言了!\n> ");
            gets(buf);
            if (strcmp(buf, "/exit") == 0) {
                int ret = write(fdw, buf, sizeof(buf));
                err(ret, "write");
                printf("你终止了对话。\n");
                exit(0);
            } else {
                int ret = write(fdw, buf, sizeof(buf));
                err(ret, "write");
                printf("你: %s\n", buf);
            }
        } else {
            printf("等待对方回应...\n");
            int ret = read(fdr, buf, sizeof(buf));
            err(ret, "read");
            buf[ret] = '\0';
            if (strcmp(buf, "/exit") == 0) {
                printf("对方终止对话。\n");
                exit(0);
            } else {
                printf("他: %s\n", buf);
            }
        }
        turn = !turn;
    }
}

int main() {
    int pid = fork();
    err(pid, "fork");
    if (pid) {
        parent();
    } else {
        child();
    }
    return 0;
}

效果呈现:
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值