IPC之十:使用共享文件进行进程间通信的实例

news2025/2/9 9:20:22

IPC 是 Linux 编程中一个重要的概念,IPC 有多种方式,常用的 IPC 方式有管道、消息队列、共享内存等,但其实使用广大程序员都熟悉的文件也是可以完成 IPC 的,本文介绍如何使用共享文件实现进程间通信,本文给出了具体的实例,并附有完整的源代码;本文实例在 Ubuntu 20.04 上编译测试通过,gcc版本号为:9.4.0;本文的实例中涉及多进程编程、文件锁等概念,所以对 Linux 编程的初学者有一些难度,但对于了解 Linux 下共享文件,特别是文件锁的应用,将是非常难得的。

1 使用共享文件实现IPC的基本概念

  • 文件操作是一个程序员的必备技能,相比较 IPC 的各种方法(比如:管道、消息队列、共享内存等),程序员显然更熟悉文件的操作;

  • 那么,能不能使用文件实现进程间通信呢?答案时肯定的,多个进程共享一个文件同样可以完成进程间通信;

  • 首先描述一个场景:

    • Server/Client 模式,一个服务端进程,三个客户端进程;
    • 进程间通信时,以每个进程的 PID 作为通信地址的唯一标识
    • 客户端只与服务端进程进行通信,客户端进程之间不进行通信;
  • 使用共享文件实现 IPC,其实就是发送方将消息写入文件,接收方再从相同的文件中读出,看起来十分简单,但在多进程环境中,并不像看起来的那么简单;

  • 使用共享文件进行 IPC 时,有两个比较麻烦的地方,一个是文件指针,另一个是文件锁机制;

  • 先说文件指针问题:

    • 当一个文件被打开时,其文件指针的偏移为 0,当读出 10 个字节时,其文件指针偏移将增加 10;
    • 写入文件时,会从当前文件指针处写入文件,当写入 10 个字节后,其文件指针偏移将增加 10;
    • 一般读出需要从文件头顺序读取,但是写入需要向文件的尾部写入,所以如果一个进程中对同一个共享文件既有读操作又有写操作时,文件指针将比较混乱;
    • 这种混乱还表现在可能还有其它进程对共享文件进行写操作,导致你期望的文件指针与实际有所不同;
    • 为了避免这种文件指针的混乱,通常在一个进程中对同一个共享文件仅做读操作或者仅做写操作;
    • 对于我们上面描述的 IPC 场景,服务端需要接收客户端的消息并做出回应,通常我们要使用两个共享文件,一个文件服务端仅做读操作,客户端仅做写操作,用于客户端向服务端传递消息,另一个文件服务端仅做写操作,客户端仅做读操作,用于服务端向客户端传递消息;
  • 再说文件锁机制:

    • 当多个进程同时对一个文件进行写操作时,很明显是会有冲突的,假定进程 1 要写入 100 个字节,进程 2 要写入 50 个字节,可能进程 1 写入完 30 个字节时,产生了进程调度,使进程 2 开始向文件写入数据,从而导致写入数据的混乱;
    • 当一个进程对文件进行写入操作时,如果有另一个进程正在读数据,也是有冲突的,假定写进程要写入 100 个字节,写入 30 个字节时,产生进程调度,读进程开始读文件,读出了刚刚写入的 30 个字节,而这 30 个字节是要写入的 100 个字节中的一部分,是不完整的数据;
    • 所以,当一个进程对一个共享文件进行写操作时,需要独占该文件,也就是同时不能有其它进程对该文件进行读写操作;
    • 当一个进程对一个文件进行读操作时,当然不能允许有其它进程进行写操作,但可以允许其它进程进行读操作;
    • 这种对文件的占有机制又叫做文件锁机制,我们在下一节会做专门的介绍;
  • 使用共享文件进行 IPC 并不是一种常用的方式,在编程实践中很少这样去做,其实际运行时是有真实的文件 I/O 发生的,也就是其通信过程会真实的写入到文件系统中,如果通信频繁、信息量大且持续时间长,有可能在磁盘上产生一个很大的物理文件;

  • 很显然,使用共享文件进行 IPC 的运行效率也是不高的,但仍然不失为一种 IPC 方法,而且相关的编程实践对理解 Linux 的共享文件及文件锁机制将会非常有帮助。

2 文件锁及其操作

  • fcntl() 函数可以对文件进行加锁操作;

  • fcntl() 可以对一个文件描述符做很多操作,在此,我们仅介绍其符合 POSIX 标准部分,与文件“锁”相关的调用方法;

  • 下面是 fcntl() 的调用方法:

    #include <unistd.h>
    #include <fcntl.h>
    
    int fcntl(int fd, int cmd, ... /* arg */ );
    
  • fcntl() 是一个不定参数的调用函数,但对于 POSIX 的文件锁而言,它只有三个参数:

    int fcntl(int fd, int cmd, (struct flock *)lock);
    
  • 在这个调用中,fd 是一个已经打开的文件描述符,cmd 是要执行的命令;

  • POSIX 与文件锁相关的命令有三个:

    • F_SETLK:获取文件锁或者释放文件锁,如果文件锁已被其它进程占有会立即返回错误;
    • F_SETLKW:执行与 F_SETLK 相同的指令,但当文件锁被其它进程占有时,会产生阻塞,直到获得该文件锁;
    • F_GETLK:获取当前文件锁状态;
  • 其中,struct flock 的定义如下:

    struct flock {
        short l_type;   /* Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */
        short l_whence; /* How to interpret l_start: SEEK_SET, SEEK_CUR, SEEK_END */
        off_t l_start;  /* Starting offset for lock */
        off_t l_len;    /* Number of bytes to lock */
        pid_t l_pid;    /* Process holding the lock. */
    };
    
    • POSIX 文件锁可以分为读文件锁和写文件锁两种;
    • POSIX 规定文件锁可以仅锁定文件中的一部分,而不是锁定整个文件,struct flock 结构不仅定义了锁的类型,同时,l_startl_len 两个字段还定义了文件中那一部分被这个文件锁锁定;
    • l_type:锁类型,F_RDLCK - 读文件锁,F_WRLCK - 写文件锁,F_UNLCK - 释放文件锁;
    • l_startl_len:该文件锁仅锁定从偏移量 l_start 开始,长度为 l_len 字节的区域,l_len 为 0 表示从 l_start 开始到文件结束;
    • l_whencel_start 偏移量计算的起始位置,可以有三个选项:
      • SEEK_SET:从文件的开始计算 l_start 的偏移量,此时 l_start 必须是一个正整数;
      • SEEK_CUR:从当前文件指针处计算 l_start 的偏移量,此时,l_start 可以为负整数,但不能跑到文件起始位置之前;
      • SEEK_END:从文件尾部计算 l_start 的偏移量,此时,l_start 为负整数或者 0;
    • l_pid:在调用 F_GETLK 获取当前文件锁状态时,如果文件锁被其它进程占用,该字段将返回占用文件锁的进程号;
  • 在大多数的应用中,无需仅锁定文件的一部分,锁定整个文件即可,也就是 l_wence=SEEK_SET; l_start=0; l_len=0

  • 下面代码片段在文件 fd 上获取写文件锁:

    ......
    struct flock lock;
    
    lock.l_tyepe = F_WRLCK;
    lock.l_wence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;
    
    fcntl(fd, F_SETLKW, &lock);
    ......
    
  • 下面代码片段释放了一个文件锁:

    ......
    struct flock lock;
    
    lock.l_tyepe = F_UNLCK;
    lock.l_wence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;
    
    fcntl(fd, F_SETLKW, &lock);
    ......
    
  • 命令 F_SETLKF_SETLKW 的唯一区别是一个不阻塞直接返回,另一个阻塞直到获得所请求的文件锁;

  • man fcntl 可以查看该函数的在线手册;

3 实例

  • 正如第 1 节中描述的场景一样,该实例建立一个服务端进程,三个客户端进程,模拟一个 client/server 架构的服务过程;

  • 正如第 1 节介绍的一样,需要使用两个共享文件实现客户端进程与服务端进程之间的通信,从服务端进程看,一个文件用于服务端读取客户端的消息,另一个文件用于服务端向客户端发送消息;

  • 两个共享文件由服务端进程建立,服务端进程要最先开始运行,否则客户端进程无法打开共享文件;

  • 整个通信过程以每个进程的进程号作为唯一地址标识,当目的进程号为 0 时表示是一条广播消息,所有进程都要接收并处理;

  • 客户端进程启动时,需要知道服务端进程的 PID 才可以与服务端进行通信,此时要发出一条广播消息,服务端进程收到后回应一条消息从而建立通信通道;

  • 客户端在空闲时循环向服务端发送一个字符串,服务端在收到后回应一个确认消息,模拟一个服务端为客户端提供服务的过程;

  • 服务端向多个客户端进程发送消息时使用同一个共享文件,所以每个客户端进程要具备过滤地址的功能,即:只保留发给自己的消息,丢弃发给其它客户端进程的消息;

  • 因为多个客户端进程都要向同一个共享文件中写入数据(即向服务端发送消息),每次写入时应该写在文件的尾部,但对每个进程而言,当前的文件指针不一定是在文件的尾部,所以在获取了文件写入锁以后,需要将文件指针移动的文件的尾部才能写入数据;

  • 为了通信方便,在传送信息时,所有进程使用下面的统一结构:

    struct ipc_msg {
        int len;            // total length including itself
        int src_pid;        // source PID
        int dest_pid;       // destination PID
        uint seq_num;       // sequence number of the current message
        ushort cmd;         // command code
        char msg[1];        // the auxiliary information
    };
    
  • len 为整个信息的总长度,包括 len 字段自身,接收端首先接收该字段,然后确定该信息后面还需要读取的字节数,再一次性地读取完整个结构;

  • src_pid 为发送该信息的进程 PID;

  • dest-pid 为接收该信息的进程 PID,当该字段为 0 时,表示该信息为广播消息,所以,一个进程应该接收该字段为自身 PID 或者该字段为 0 的消息,并丢弃其它消息;

  • cmd 表示该信息的含义,目前有五个可选值:

    1. CMD_SERVER_ONLINE - 表示服务端在线,客户端在启动后并不知道服务端进程的 PID,所以应该周期性地广播 CMD_SERVER_STATUS 消息,服务端进程收到该广播消息后,向相应的客户端进程发送 CMD_SERVER_ONLINE 消息,客户端收到该消息便可获知服务端进程的 PID,从而建立通信通道;
    2. CMD_SERVER_OFFLINE - 表示服务端离线,当服务端准备退出时,广播该信息,客户端在收到该消息时,应主动退出;
    3. CMD_SERVER_STATUS - 客户端进程启动后广播该信息,服务端进程收到该信息应回复 CMD_SERVER_ONLINE,从而使客户端获得服务端进程的 PID;
    4. CMD_STRING - 客户端在空闲时定期向服务端进程发送一个字符串,以模拟客户端进程向服务端进程请求服务的过程,发送此消息时,字符串应放在 msg 字段中,所以这个消息的长度是不定长的,在实际的应用中,这个字符串可以是一个 json 数据,可以实现复杂的服务请求;
    5. CMD_STRING_OK - 服务端在收到客户端进程发送的 CMD_STRING 消息后,回应一个 CMD_STRING_OK 消息,模拟对客户端请求服务的响应;
  • 各个进程在向共享文件写入数据时,均要求以 struct ipc_msg 格式写入,分下面几个步骤完成:

    1. struct ipc_msg 分配内存,如果有 ipc_msg.msg 字段,则分配的内存要包含 ipc_msg.msg 字符串的长度;
    2. 计算整个消息的长度,长度应包括 ipc_msg.msg 最后的 \0 字符,将消息长度填写到 ipc_msg.len 字段中;
    3. 将当前进程的 PID 写入到 ipc_msg.src_pid 字段;
    4. 将接收进程的 PID 写入到 ipc_msg.dest_pid 字段,如果是广播消息,该字段填 BROADCAST_PROCESS_ID
    5. 将消息序列号写入到 ipc_msg.seq_num 字段,
    6. 根据情况填写 ipc_msg.cmd 字段;
    7. 如果有 ipc_msg.msg,将字符串写入 ipc_msg.msg 中;
    8. struct ipc_msg 写入共享文件;
    9. 释放为 struct ipc_msg 分配的内存;
  • 各进程在读入数据时,要遵循下面步骤:

    1. 首先读取一个 int,此为 struct ipc_msg 中的 len 字段,然后根据 len 字段的值读取剩余的数据;
    2. 检查 dest_pid 字段是否为自身的 PID 或者 BROADCAST_PROCESS_ID,否则丢弃该消息,转到步骤 1 读取下一个消息;
    3. 根据消息内容做出回应;
  • 源程序:ipc-files.c(点击文件名下载源程序)演示了如何使用共享文件实现进程间通信;

  • 编译:gcc -Wall -g ipc-files.c -o ipc-files

  • 运行:./ipc-files

  • 运行动图:

    screenshot of running ipc-files

欢迎订阅 『进程间通信专栏』


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1335856.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于电商场景的高并发RocketMQ实战-Commitlog基于内存的高并发写入优化、基于JVM offheap的内存读写分离机制

&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308; 【11来了】文章导读地址&#xff1a;点击查看文章导读&#xff01; &#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f3…

linux:下载、网络请求、端口

一&#xff1a;ping命令 可以通过ping命令,检查指定的网络服务器是否是可联通状态 语法: ping [-c num] ip或主机名 1、选项&#xff1a;-c,检查的次数&#xff0c;不使用-c选项&#xff0c;将无限次数持续检查 2、参数&#xff1a;ip或主机名&#xff0c;被检查的服务器的…

NET中使用SQLSugar操作sqlserver数据库

目录 一、SqlSugar是什么&#xff1f; 二、迁移和建表 1.建立实体 2.创建上下文类 3.在Program中添加SqlSugar服务 4.在控制器中注入上下文类 三、简单实现CURD功能 总结 一、SqlSugar是什么&#xff1f; SqlSugar是一款老牌 .NET 开源ORM框架。 主要特点&#xff1a…

天啦撸 超级麻烦的MySQL索引和数据引擎,快拿小本本记好

1 MySQL的索引 1.1 索引 定义&#xff1a; 索引是一个排序的列表&#xff0c;包含索引字段的值和其对应的行记录的数据所在的物理地址 ●索引是一个排序的列表&#xff0c;在这个列表中存储着索引的值和包含这个值的数据所在行的物理地址&#xff08;类似于C语言的链表通过…

边缘计算AI智能盒子的视频源必须是固定点监控摄像头吗?

边缘计算AI盒子的视频输入源&#xff0c;要求是RTSP或者GB28181&#xff0c;可以是固定点监控摄像头&#xff08;枪机、球机等&#xff09;&#xff0c;也可以是移动摄像头&#xff0c;例如执法记录仪、智能安全帽、布控球等&#xff0c;但由于RTSP输入要求摄像头有固定IP&…

贪吃蛇小游戏的代码实现之知识点铺垫篇

今天给大家介绍一个很经典的小游戏&#xff0c;它和扫雷在经典小游戏这方面可以说是旗鼓相当&#xff0c;它的名字就是贪吃蛇。贪吃蛇游戏最初为单机模式&#xff0c;后续又陆续推出团战模式、赏金模式、挑战模式等多种玩法。该游戏具体玩法是&#xff1a;用游戏把子上下左右控…

redis复习笔记01(小滴课堂)

高并发的必备两大“核技术”队列和缓存 介绍本地缓存和分布式缓存 Nosql介绍和Reidis介绍 Linux服务器源码安装Redis6和相关依赖 在路径下上传压缩包。 上传压缩包。 版本更新了&#xff0c;但这是临时的。 版本更新了。 解压压缩包&#xff1a; 重命名&#xff1a; 我们可以看…

网页乱码问题(edge浏览器)

网页乱码问题&#xff08;edge&#xff09; 文章目录 网页乱码问题&#xff08;edge&#xff09;前言一、网页乱码问题1.是什么&#xff1a;&#xff08;描述&#xff09;2.解决方法&#xff1a;&#xff08;针对edge浏览器&#xff09;&#xff08;1&#xff09;下载charset插…

JavaWeb的Servlet的入门和使用方法

1 什么是Servlet Servlet是Server Applet的简称&#xff0c;是用Java编写的是运行在 Web 服务器上的程序&#xff0c;它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。使用 Servlet&#xff0c;可以收集来自网页表单的用户输…

树莓派开箱安装

树莓派摄像头安装教程&#xff1a; 百度安全验证 树莓派5代9层亚克力外壳安装&#xff1a; 百度安全验证

CGAL的三角曲面网格骨架化

1、介绍 马模型的曲线骨架。 骨架是用于分割、形状匹配、重建、虚拟导航等的有效形状抽象。顾名思义&#xff0c;曲线骨架是曲线结构的图&#xff08;1D&#xff09;。对于3D几何体来说&#xff0c;它不是由表面&#xff08;2D&#xff09;组成的中轴。如图所示&#xff0c;形…

IDEA的JavaWeb项目的webapp目录的小蓝点没亮怎么办???

我们可以看到没有小蓝点&#xff0c;正常的是有的。 没有小蓝点的原因&#xff1a;你的webapp 这个文件夹的路径不对。 解决方案&#xff1a;&#xff08;修改webapp的路径&#xff09; 我们可以看到&#xff0c;错误的路径是显示红色。 这个地方没有就添加一个路径&#xff0…

机器人制作开源方案 | 智能循迹搬运车

作者&#xff1a;范永晨 杨丽鹏 张东生 单位&#xff1a;唐山学院 指导老师&#xff1a;袁娜 都说21世纪为物流的天下&#xff0c;物流行业牵扯到各个领域。目前&#xff0c;劳动人口数量下降、老龄化现象加速、工资上涨&#xff0c;土地成本提升&#xff0c;随着市场不断发展…

外卖系统开发:构建高效、安全的外卖平台

在当今数字化时代&#xff0c;外卖系统成为了餐饮行业不可或缺的一部分。本文将介绍如何使用一些主流的技术和代码片段来开发一个简单而功能强大的外卖系统。 1. 技术选择 在开始外卖系统的开发之前&#xff0c;首先需要选择合适的技术栈。以下是一个常见的技术栈&#xff1…

IP 地址归属地查询

IP 地址归属地查询 1. IP 地址归属地查询2. IP 地址归属地查询References 1. IP 地址归属地查询 https://tool.lu/ip/index.html 2. IP 地址归属地查询 https://www.ip.cn/ip/.html References [1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/

zookeeper基本使用

目录 环境搭建 单机版搭建 集群版搭建 基本语法使用 可视化客户端 数据结构 节点分类 1. 持久节点 2. 临时节点 3. 有序节点 4. 容器节点 5. TTL节点 节点状态 监听机制 watch监听 永久性watch 应用场景 1. 实现分布式锁 2. 乐观锁更新数据 应用场景总结 选…

【工具使用-A2B】使用A2B配置32通道车载音频系统

一,简介 在工作中需要使用A2B芯片传输32通道的车载音频数据。本文主要介绍,如何配置A2B工程来实现32通道车载音频系统。 二,配置介绍 2.1 A2B链路图配置: 2.2 A2B Master 寄存器配置: 2.3 A2B Slave 寄存器配置:

鸿蒙的基本项目_tabbar,首页,购物车,我的

以上效果&#xff0c;由四个ets文件实现&#xff0c;分别是容器页面。首页&#xff0c;购物车&#xff0c;我的。 页面里的数据&#xff0c;我是用json-server进行模拟的数据。 一、容器页面 使用组件Tabs和Tabcontent结合。 import Home from "./Home"; import …

并发编程——3.细说线程

这篇文章我们来详细的说一下并发编程中的线程及其相关的内容 目录 1.线程的创建 1.1通过继承Thread 1.2通过实现Runnable接口的方式 1.3使用FutureTask方式&#xff08;实现Callable接口的方式&#xff09; 1.4三种方式的区别 2.线程的原理 3.线程的常用方法 3.1start…

音频修复增强软件iZotope RX 10 mac特点介绍

iZotope RX 10 mac是一款音频修复和增强软件。 iZotope RX 10 mac软件特点 声音修复&#xff1a;iZotope RX 10可以去除不良噪音、杂音、吱吱声等&#xff0c;使音频变得更加清晰干净。 音频增强&#xff1a;iZotope RX 10支持对音频进行音量调节、均衡器、压缩器、限制器等处…