> 文章列表 > 【Linux】命名管道使用示例-代码实现

【Linux】命名管道使用示例-代码实现

【Linux】命名管道使用示例-代码实现

文章目录

  • 1 管道基础知识复习(可直接跳转代码实现)
    • 1.1 管道的读写规则
    • 1.2 管道的特点
  • 2 命名管道
    • 2.1 命名管道本质
    • 2.2 创建命名管道
      • 2.2.1 在命令行创建:
      • 2.2.2 在程序中调用函数创建
    • 2.3 命名管道和匿名管道的区别
    • 2.4 命名管道的打开规则
  • 3 代码分解实现
    • 3.1 makefile书写
      • 3.1.1 测试编译
    • 3.2 comm.cpp的编写
      • 3.2.1 权限设置:0666表示->
      • 3.2.2 uint32_t
    • 3.3 server.cc
    • 3.4 client.cc

1 管道基础知识复习(可直接跳转代码实现)

1.1 管道的读写规则

首先我们复习以下管道的一些特性:

  • 当没有数据可读时:

    • O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
    • O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
  • 当管道满的时候

    • O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
    • O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
  • 如果所有管道写端对应的文件描述符被关闭,则read返回0

  • 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出

  • 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。

  • 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

1.2 管道的特点

  • 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
  • 管道提供流式服务
  • 一般而言,进程退出,管道释放,所以管道的生命周期随进程
  • 一般而言,内核会对管道操作进行同步与互斥
  • 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
    【Linux】命名管道使用示例-代码实现

2 命名管道

2.1 命名管道本质

  • 匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
  • 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道
  • 命名管道是一种特殊类型的文件

2.2 创建命名管道

2.2.1 在命令行创建:

$ mkfifo filename

2.2.2 在程序中调用函数创建

int mkfifo(const char *filename,mode_t mode);
//其中,第一个参数是文件名,第二个参数是权限设置
int main(int argc, char *argv[])
{
mkfifo("p2", 0644);
return 0;
}

2.3 命名管道和匿名管道的区别

  • 匿名管道由pipe函数创建并打开。
  • 命名管道由mkfifo函数创建,打开用open
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完
    成之后,它们具有相同的语义。

2.4 命名管道的打开规则

  • 如果当前打开操作是为读而打开FIFO时
    • O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
    • O_NONBLOCK enable:立刻返回成功
  • 如果当前打开操作是为写而打开FIFO时
    • O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
    • O_NONBLOCK enable:立刻返回失败,错误码为ENXIO

3 代码分解实现

要使用管道进行通信,我们需要两个"人",一个是客户端,一个是服务器.且为了方便代码的编写,我们将两者公用的结构体和头文件放在同一个hpp文件中

3.1 makefile书写

//要让两个文件同时编译,我们可以定义一个all,然后all分别依赖两个文件:
.PHONY:all
all: server clientserver:server.ccg++ -o $@ $^ -std=c++11
client:client.ccg++ -o $@ $^ -lncurses -std=c++11
.PHONY:clean
clean:rm -rf server client

3.1.1 测试编译

【Linux】命名管道使用示例-代码实现
完美通过

3.2 comm.cpp的编写

#pragma once
#include<iostream>
#include<string>
#define NUM 1024//为了保证管道通信的双方都可以通过命名管道文件进行通信:
const std::string filename = "./fifo";// 即在当前目录下创建fifo文件
//由于我们需要调用linux系统调用,所以必然是c\\cpp混编
//而linux系统调用不认识string类,到时候我们务必要转换为C字符串类型
uint32_t mode = 0666;//设置权限

3.2.1 权限设置:0666表示->

666代表
该文件拥有者对该文件拥有读写的权限但是没有操作的权限
该文件拥有者所在组的其他成员对该文件拥有读写的权限但是没有操作的权限
其他用户组的成员对该文件也拥有读写权限但是没有操作的权限

777 代表
该文件拥有者对该文件拥有读写操作的权限
该文件拥有者所在组的其他成员对该文件拥有读写操作的权限
其他用户组的成员对该文件也拥有读写操作权限

但是要注意,我们真正使用的时候,权限会被当时的掩码减去,以下是例子:

一般而言,umask为022,也就是说,对于当前用户没有拿掉权限,group用户和other用户都被拿走了w权限,所以此时如果用户进行创建目录和文件的时候,默认权限是会进行如下的减法操作:
新建文件:666-022=644;
新建目录:777-022=755.

所以当我们真正要为这个文件赋予666权限的时候,要注意将当前环境的掩码设置为0

3.2.2 uint32_t

我们直接转到定义:
【Linux】命名管道使用示例-代码实现
可以看到,其实就是一个无符号整型,不过系统帮我们封装了
我们这里为了规范,也直接使用unit32_t来定义

3.3 server.cc

接下来如果有函数不太会用,可以使用man手册:

man 1 命令
man 2 系统调用
man 3 库函数

接下来是该代码最重头的地方:

#include <iostream>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "comm.hpp"int main()
{//1. 创建管道文件,今天我们只需要创建一次umask(0);//原因上面说了.且这个设置并不影响系统的默认配置,只会影响当前进程的掩码设置int n = mkfifo(fifoname.c_str(), mode);//linux系统调用不认识string类,务必要转换为C字符串类型//用n接收返回值是为了检查其调用合法性if(n != 0)//正常调用应该返回0{std::cout << errno << " : " << strerror(errno) << std::endl;return 1;}std::cout << "create fifo file success" << std::endl;//此时输出一个提示方便我们调试//2.让服务器端直接开启管道文件(以只写方式),也是需要以一个值来接收判断合法性int rfd = write(filename.c_str(),O_RDONLY);if(rfd < 0 ){std::cout << errno << " : " << strerror(errno) << std::endl;return 2;}// 3. 正常通信char buffer[NUM];while(true){buffer[0] = 0;//每次都要把buffer置空(直需要把第一个数据设为\\0即可)ssize_t n = read(rfd, buffer, sizeof(buffer) - 1);//服务端是读取数据,返回值是读取到的字节个数if(n > 0){buffer[n] = 0;//一个字符是一个字节,所以读取到了n个字节的话,在第n+1个字节的位置放置0,来切断句子printf("%c", buffer[0]);fflush(stdout);//立马刷新缓冲区}else if(n == 0){std::cout << "client quit, me too" << std::endl;//什么都没有读到,说明客户端已经退出了break;}else {std::cout << errno << " : " << strerror(errno) << std::endl;break;}}// 关闭不要的fdclose(rfd);unlink(fifoname.c_str());//删除这个管道文件,以便下次使用return 0;
}

3.4 client.cc

#include <iostream>
#include <cstdio>
#include <cerrno>
#include <cstring>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
// #include <ncurses.h>
#include "comm.hpp"int main()
{//1. 不需创建管道文件,我只需要打开对应的文件即可!int wfd = open(fifoname.c_str(), O_WRONLY);if(wfd < 0){std::cerr << errno << ":" << strerror(errno) << std::endl;return 1;}// 可以进行常规通信了char buffer[NUM];while(true){// std::cout << "请输入你的消息# ";// char *msg = fgets(buffer, sizeof(buffer), stdin);// assert(msg);// (void)msg;// int c = getch();// std::cout << c << std::endl;// if(c == -1) continue;system("stty raw");int c = getchar();system("stty -raw");//std::cout << c << std::endl;//sleep(1);//buffer[strlen(buffer) - 1] = 0;// abcde\\n\\0// 012345//if(strcasecmp(buffer, "quit") == 0) break;ssize_t n = write(wfd, (char*)&c, sizeof(char));assert(n >= 0);(void)n;}close(wfd);return 0;
}