当前位置: 首页 > news >正文

【Linux】进程间通信(1)

进程是操作系统进行资源分配的基本单位,各进程的地址空间相互独立(受操作系统内存隔离机制保护),无法直接访问对方数据。进程间通信(Inter-Process Communication, IPC) 是操作系统提供的一套机制,用于打破这种隔离,实现进程间的数据交换、资源共享、事件通知或进程控制。

1.1 进程间通信的目的

进程间通信的设计核心是解决多进程协作的关键需求,具体可分为四类:

  • 数据传输:一个进程需将数据(如用户输入、计算结果)发送给另一个进程。例如:即时通讯软件中,消息发送进程将用户输入的文字发送给消息接收进程;数据分析进程将计算结果发送给数据展示进程。

  • 资源共享:多个进程需共享同一资源(如文件、内存块、硬件设备),避免资源冗余存储。例如:多个视频播放进程共享系统显卡资源以渲染画面;多个文档编辑进程共享同一磁盘文件(需通过 IPC 保证读写同步,避免数据错乱)。

  • 通知事件:一个进程需向其他进程发送 “事件信号”,告知特定事件已发生。例如:子进程执行完毕后,通过信号通知父进程回收其资源;监控进程检测到磁盘空间不足时,通知文件写入进程暂停操作。

  • 进程控制:一个进程(如调试器gdb)需完全控制另一个进程的执行流程,包括拦截异常、查看寄存器状态、暂停 / 继续执行。例如:gdb通过 IPC 机制附加到目标进程,设置断点后可实时监控目标进程的执行状态。

1.2 进程间通信的发展

IPC 机制随操作系统演进不断优化,主要经历三个阶段,不同阶段的机制适配不同场景的需求:

  • 早期 Unix 阶段:核心机制为管道(Pipe),是最古老的 IPC 形式,设计简单但仅支持亲缘进程(如父子进程)通信,适用于简单的流式数据传输。

  • System V IPC 阶段:推出System V 消息队列System V 共享内存System V 信号量,支持非亲缘进程通信,提供更灵活的资源管理,但接口较复杂且可移植性差(不同 Unix 系统实现存在差异)。

  • POSIX IPC 阶段:基于 POSIX 标准统一接口,推出POSIX 消息队列POSIX 共享内存POSIX 信号量,以及适用于线程 / 进程同步的互斥量条件变量读写锁,可移植性强(支持 Linux、macOS、BSD 等),成为现代操作系统的主流 IPC 方案。

1.3 进程间通信的分类

根据实现原理和功能,主流 IPC 机制可分为以下类别,后续章节将重点展开 “管道” 的细节:

分类具体机制核心特点
管道类匿名管道(Pipe)、命名管道(FIFO)基于 “一切皆文件” 思想,数据流式传输,半双工通信
System V IPC消息队列、共享内存、信号量基于内核对象管理,独立于进程生命周期,支持非亲缘进程
POSIX IPC消息队列、共享内存、信号量、互斥量、条件变量标准化接口,可移植性强,支持线程 / 进程共享
其他信号(Signal)、Socket信号用于紧急事件通知;Socket 支持跨主机进程通信

1.4 匿名管道

1.4.1 管道的定义

管道是 Unix 系统中最古老的 IPC 形式,核心是 “从一个进程连接到另一个进程的数据流”。它遵循 Linux “一切皆文件” 的设计思想 —— 内核在内存中维护一个临时缓冲区,进程通过读写该缓冲区实现通信,操作方式与读写普通文件一致(如read()write()系统调用)。

1.4.2 匿名管道(Pipe)

匿名管道是最基础的管道类型,通过pipe()系统调用创建,仅支持亲缘进程(如父子、兄弟进程)通信。

1. pipe()函数接口
#include <unistd.h>
// 功能:创建匿名管道,返回两个文件描述符
// 参数:fd[2] - 输出型数组,fd[0]表示“读端”,fd[1]表示“写端”
// 返回值:成功返回0;失败返回-1,同时设置errno(如EMFILE表示文件描述符耗尽)
int pipe(int fd[2]);
2. 实例代码:基础管道通信

以下代码实现 “从键盘读取数据→写入管道→读取管道→输出到屏幕” 的完整流程,直观展示匿名管道的使用:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main(void) {int fds[2];          // 存储管道的读端(fds[0])和写端(fds[1])char buf[100] = {0}; // 用于存储读写的数据int len;             // 记录读取/写入的数据长度// 1. 创建匿名管道,失败则报错退出if (pipe(fds) == -1) {perror("make pipe"); // 打印错误原因(如“make pipe: Too many open files”)exit(1);}// 2. 循环从键盘读取数据(标准输入stdin)while (fgets(buf, 100, stdin) != NULL) {len = strlen(buf); // 获取输入数据的长度(包含换行符)// 3. 将数据写入管道(写端fds[1])if (write(fds[1], buf, len) != len) {perror("write to pipe");break; // 写入失败则退出循环}// 4. 清空缓冲区,准备读取管道数据memset(buf, 0x00, sizeof(buf));// 5. 从管道读取数据(读端fds[0])if ((len = read(fds[0], buf, 100)) == -1) {perror("read from pipe");break; // 读取失败则退出循环}// 6. 将读取到的数据写入屏幕(标准输出stdout)if (write(1, buf, len) != len) {perror("write to stdout");break;}}return 0;
}
3. 用fork()共享管道的原理

匿名管道仅支持亲缘进程通信,核心依赖fork()的 “文件描述符继承” 特性,具体流程如下:

  1. 父进程调用pipe(fds)创建管道,获得fds[0](读端)和fds[1](写端);
  2. 父进程调用fork()创建子进程,子进程会复制父进程的文件描述符表,因此也拥有fds[0]fds[1],指向同一内核缓冲区;
  3. 父进程和子进程通过关闭 “不需要的端” 实现单向通信(如父进程关闭读端fds[0]、子进程关闭写端fds[1],则父进程写、子进程读)。
4. 从文件描述符角度理解管道
  • 管道的本质是内核维护的 “伪文件”,没有实际的磁盘存储,仅存在于内存中;
  • 进程通过fd[0](读端)和fd[1](写端)操作管道,这两个文件描述符与普通文件的描述符无差异,可通过read()write()close()等系统调用操作;
  • 只有持有管道文件描述符的进程才能访问管道,非亲缘进程无该描述符,因此无法通信。

5. 从内核角度理解管道本质

内核为管道维护三个核心结构:

  • 缓冲区:用于存储管道数据,大小由内核定义(通常为PIPE_BUF,默认 4096 字节);
  • 读写指针:分别记录当前读取和写入的位置,确保数据按 “先进先出(FIFO)” 顺序传输;
  • 引用计数:记录当前持有管道读端和写端的进程数,当读端引用计数为 0 时,写操作会触发SIGPIPE信号;当写端引用计数为 0 时,读操作会返回 0(类似文件 EOF)。

“Linux 一切皆文件” 的思想在此体现:管道的操作逻辑与普通文件完全一致,进程无需区分 “操作的是管道还是文件”,降低了编程复杂度。

1.4.3 匿名管道的进阶示例

1. 测试管道读写(父子进程通信)

以下代码通过fork()创建父子进程,实现 “子进程写、父进程读” 的单向通信,验证管道的基本功能:

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>void ChildWrite(int wfd)
{char buffer[1024];int cnt = 0;while (true){snprintf(buffer, sizeof(buffer), "I am child, pid: %d, cnt: %d", getpid(), cnt++);write(wfd, buffer, strlen(buffer));sleep(1);}
}
void FatherRead(int rfd)
{char buffer[1024];while (true){buffer[0] = 0;ssize_t n = read(rfd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = 0;std::cout << "child say: " << buffer << std::endl;}}
}int main()
{// 创建管道int fds[2] = {0};int n = pipe(fds); // fds[0] 读端,fds[1] 写端口if (n < 0){std::cerr << "pipe error" << std::endl;return 1;}std::cout << "fds[0] " << fds[0] << std::endl;std::cout << "fds[1] " << fds[1] << std::endl;// 创建子进程// f -> r, c -> wpid_t id = fork();if (id == 0){// childclose(fds[0]);ChildWrite(fds[1]);close(fds[1]);exit(0);}close(fds[1]);FatherRead(fds[0]);waitpid(id, nullptr, 0);close(fds[0]);return 0;
}

2. 创建进程池处理任务

当需要多个子进程并发处理任务时(如批量数据处理、多任务调度),可通过 “父进程创建多个管道 + 多个子进程” 实现进程池,核心是通过管道实现 “父进程派发任务、子进程执行任务” 的协作模式。

以下是进程池的核心代码实现(含头文件和主函数):

(1)ProcessPool.hpp:管道通信信道封装和进程池管理

用于封装 “管道写端” 和 “子进程 ID”,方便父进程定位子进程并发送任务:

#ifndef __PROCESS_POOL_HPP__
#define __PROCESS_POOL_HPP__#include <iostream>
#include <cstdlib> // stdlib.h stdio.h -> cstdlib cstdio
#include <vector>
#include <unistd.h>
#include <sys/wait.h>
#include "Task.hpp"// 先描述
class Channel
{
public:Channel(int fd, pid_t id) : _wfd(fd), _subid(id){_name = "channel-" + std::to_string(_wfd) + "-" + std::to_string(_subid);}~Channel(){}void Send(int code){int n = write(_wfd, &code, sizeof(code));(void)n; // ?}void Close(){close(_wfd);}void Wait(){pid_t rid = waitpid(_subid, nullptr, 0);(void)rid;}int Fd() { return _wfd; }pid_t SubId() { return _subid; }std::string Name() { return _name; }private:int _wfd;pid_t _subid;std::string _name;// int _loadnum;
};// 在组织
class ChannelManager
{
public:ChannelManager() : _next(0){}void Insert(int wfd, pid_t subid){_channels.emplace_back(wfd, subid);// Channel c(wfd, subid);// _channels.push_back(std::move(c));}Channel &Select(){// 采用轮询的方式选择一个信道auto &c = _channels[_next];_next++;_next %= _channels.size();return c;}void PrintChannel(){for (auto &channel : _channels){std::cout << channel.Name() << std::endl;}}void StopSubProcess(){for (auto &channel : _channels){channel.Close();std::cout << "关闭: " << channel.Name() << std::endl;}}void WaitSubProcess(){// for (auto &channel : _channels)// {//     channel.Wait();//     std::cout << "回收: " << channel.Name() << std::endl;// }for (int i = _channels.size() - 1; i >= 0; i--){_channels[i].Close();std::cout << "关闭: " << _channels[i].Name() << std::endl;_channels[i].Wait();std::cout << "回收: " << _channels[i].Name() << std::endl;}}~ChannelManager() {}private:std::vector<Channel> _channels;int _next;
};const int gdefaultnum = 5;class ProcessPool
{
public:ProcessPool(int num) : _process_num(num){_tm.Register(PrintLog);_tm.Register(Download);_tm.Register(Upload);}void Work(int rfd){while (true){int code = 0;ssize_t n = read(rfd, &code, sizeof(code));if (n > 0){if (n != sizeof(code)){continue;}std::cout << "子进程[" << getpid() << "]收到一个任务码: " << code << std::endl;_tm.Execute(code);}else if (n == 0){std::cout << "子进程退出" << std::endl;break;}else{std::cout << "读取错误" << std::endl;break;}}}bool Start(){for (int i = 0; i < _process_num; i++){// 1. 创建管道int pipefd[2] = {0};int n = pipe(pipefd);if (n < 0)return false;// 2. 创建子进程pid_t subid = fork();if (subid < 0)return false;else if (subid == 0){// 子进程// 3. 关闭不需要的文件描述符close(pipefd[1]);Work(pipefd[0]); //??close(pipefd[0]);exit(0);}else{// 父进程//  3. 关闭不需要的文件描述符close(pipefd[0]); // 写端:pipefd[1];_cm.Insert(pipefd[1], subid);// wfd, subid}}return true;}void Debug(){_cm.PrintChannel();}void Run(){// 1. 选择一个任务int taskcode = _tm.Code();// 2. 选择一个信道[子进程],负载均衡的选择一个子进程,完成任务auto &c = _cm.Select();std::cout << "选择了一个子进程: " << c.Name() << std::endl;// 2. 发送任务c.Send(taskcode);std::cout << "发送了一个任务码: " << taskcode << std::endl;}void Stop(){// 关闭父进程所有的wfd即可// _cm.StopSubProcess();// 回收所有子进程_cm.WaitSubProcess();// 为什么两个不是合在一起写呢?// 如果合在一起写会发现,父进程关闭写端后,父进程还在等待,会导致父进程阻塞,无法退出。// 这是因为父进程的文件描述表会被后面的子进程继承,父进程的管道写端将会被多个子进程共享,当父进程关闭写端时并不是真的关闭了// 这时管道文件就没有结束,所以子进程还在等待。// 要想解决这个问题:// 1. 可以倒着关, 因为最后面的管道文件没有被子进程继承,所以关掉它的写端,子进程就能正常退出。// 2. 让子进程自己关闭自己继承下来的他的哥哥进程的写端就可以了}~ProcessPool(){}private:ChannelManager _cm;int _process_num;TaskManager _tm;
};#endif // __PROCESS_POOL_HPP__
(2)Task.hpp:任务管理

定义任务类型和任务管理器,实现任务的选择和执行:

#pragma once#include <iostream>
#include <vector>
#include <ctime>typedef void (*task_t)();////////////////debug/////////////////////
void PrintLog()
{std::cout << "我是一个打印日志的任务" << std::endl;
}void Download()
{std::cout << "我是一个下载的任务" << std::endl;
}void Upload()
{std::cout << "我是一个上传的任务" << std::endl;
}
//////////////////////////////////////class TaskManager
{
public:TaskManager(){srand(time(nullptr));}void Register(task_t t){_tasks.push_back(t);}int Code(){return rand() % _tasks.size();}void Execute(int code){if(code >= 0 && code < _tasks.size()){_tasks[code]();}}~TaskManager(){}
private:std::vector<task_t> _tasks;
};

1.5 命名管道

在 Unix/Linux 系统的进程间通信(IPC)机制中,命名管道(FIFO,First In First Out)是对匿名管道的关键扩展。它解决了匿名管道仅能在具有亲缘关系进程(如父子、兄弟进程)间通信的限制,通过在文件系统中创建一个可见的 “特殊文件”,实现了无亲缘关系进程间的可靠数据传输。

1.5.1 命名管道的核心概念

命名管道本质是一种特殊类型的文件,其核心特性如下:

  • 文件系统可见性:创建后会在文件系统中生成一个对应的路径(如 ./mypipe),进程可通过该路径识别并访问管道,这是其与匿名管道(仅存在于内存)的核心区别。
  • FIFO 特性:数据传输遵循 “先进先出” 原则,保证写入顺序与读取顺序一致。
  • 半双工通信:与匿名管道一致,数据传输为单向流,需双向通信时需创建两个命名管道。
  • 面向字节流:传输的数据为无结构字节流,不保留消息边界,需开发者自行定义数据解析规则(如固定长度、分隔符)。

1.5.2 创建命名管道

命名管道的创建有两种方式:命令行创建和程序内创建,两种方式最终生成的管道功能完全一致。

1. 命令行创建:mkfifo 命令

mkfifo 是专门用于创建命名管道的命令行工具,语法如下:

$ mkfifo [选项] 管道文件名
  • 常用选项:无特殊选项时,默认创建的管道权限受 umask 影响(通常为 0644 或 0664)。
  • 示例:创建名为 mypipe 的命名管道
    $ mkfifo mypipe
    $ ls -l mypipe  # 查看管道文件类型,显示为"p"(pipe)
    prw-r--r-- 1 user user 0 Oct  2 14:00 mypipe
    

2. 程序内创建:mkfifo 函数

C 语言中通过 mkfifo() 系统调用创建命名管道,函数原型如下:

#include <sys/types.h>
#include <sys/stat.h>int mkfifo(const char *filename, mode_t mode);
  • 参数说明
    • filename:指向管道文件路径的字符串(如 "p2")。
    • mode:管道文件的权限位(如 0644,表示所有者可读可写,组和其他用户可读),最终权限会与当前进程的 umask 进行按位与(~umask & mode),因此创建前常通过 umask(0) 清除权限屏蔽。
  • 返回值:成功返回 0;失败返回 -1,并设置 errno(如 EEXIST 表示文件已存在,EACCES 表示权限不足)。
示例:程序内创建命名管道
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>#define ERR_EXIT(m) \
do { \perror(m); \exit(EXIT_FAILURE); \
} while(0)int main(int argc, char *argv[])
{umask(0);  // 清除权限屏蔽,确保设置的mode生效// 创建名为"p2"、权限为0644的命名管道if (mkfifo("p2", 0644) == -1) {ERR_EXIT("mkfifo failed");}printf("命名管道\"p2\"创建成功\n");return 0;
}

1.5.3 命名管道的打开规则

命名管道创建后需通过 open() 函数打开才能进行读写,其打开行为受阻塞标志(O_NONBLOCK) 和打开模式(读 / 写) 共同影响,这是使用命名管道的核心注意点。

打开模式O_NONBLOCK 禁用(默认阻塞)O_NONBLOCK 启用(非阻塞)
为读打开(O_RDONLY)阻塞,直到有进程以 “写模式” 打开该管道立即返回成功,即使暂无写进程打开管道
为写打开(O_WRONLY)阻塞,直到有进程以 “读模式” 打开该管道立即返回失败,错误码设为 ENXIO
读写打开(O_RDWR)立即返回成功(无需等待其他进程)立即返回成功(无需等待其他进程)

注意:实际开发中极少使用 “读写打开”,因为这会绕过管道的 “两端配对” 机制,可能导致数据读写逻辑混乱。

1.5.4 匿名管道与命名管道的区别

二者在数据传输语义(阻塞、字节流、半双工)上完全一致,核心差异仅集中在创建与访问方式,具体对比如下:

特性匿名管道(Pipe)命名管道(FIFO)
创建函数pipe(int pipefd[2])mkfifo(const char *filename, mode_t mode)
打开方式创建时自动打开(返回读写端 fd)需通过 open() 函数手动打开
进程间通信限制仅支持具有亲缘关系的进程支持无亲缘关系的任意进程(通过文件路径访问)
存在形式仅存在于内存,随进程退出自动释放存在于文件系统,需手动 unlink() 删除
标识方式通过文件描述符(pipefd [0]/pipefd [1])标识通过文件系统路径(如 ./mypipe)标识

1.5.5 命名管道的实践实例

命名管道的典型应用场景包括 “无亲缘进程间文件传输”“客户端 - 服务器(C/S)通信” 等,以下结合完整代码说明实现逻辑。

实例 1:用命名管道实现文件拷贝

通过两个独立进程(无亲缘关系)配合:进程 A 读取源文件并写入管道,进程 B 从管道读取数据并写入目标文件,实现文件拷贝。

进程 A(读文件→写管道)
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>#define ERR_EXIT(m) \
do \
{ \perror(m); \exit(EXIT_FAILURE); \
} while(0)int main()
{// 1. 创建命名管道(若已存在可跳过,此处简化为直接创建)umask(0);if (mkfifo("tp", 0644) == -1 && errno != EEXIST) {ERR_EXIT("mkfifo failed");}// 2. 打开源文件(只读)int infd = open("abc", O_RDONLY);if (infd == -1) {ERR_EXIT("open source file failed");}// 3. 打开管道(只写,默认阻塞,等待读进程)int outfd = open("tp", O_WRONLY);if (outfd == -1) {ERR_EXIT("open pipe for write failed");}// 4. 循环读写数据char buf[1024];ssize_t n;  // 注意用ssize_t接收read/write返回值(支持-1)while ((n = read(infd, buf, sizeof(buf))) > 0) {write(outfd, buf, n);  // 写入管道}// 5. 释放资源close(infd);close(outfd);printf("文件数据已写入管道\n");return 0;
}
进程 B(读管道→写目标文件)
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>#define ERR_EXIT(m) \
do \
{ \perror(m); \exit(EXIT_FAILURE); \
} while(0)int main()
{// 1. 打开目标文件(只写,不存在则创建,存在则清空)int outfd = open("abc.bak", O_WRONLY | O_CREAT | O_TRUNC, 0644);if (outfd == -1) {ERR_EXIT("open target file failed");}// 2. 打开管道(只读,默认阻塞,等待写进程)int infd = open("tp", O_RDONLY);if (infd == -1) {  // 原代码此处误写为outfd,已修正ERR_EXIT("open pipe for read failed");}// 3. 循环读写数据char buf[1024];ssize_t n;while ((n = read(infd, buf, sizeof(buf))) > 0) {write(outfd, buf, n);  // 写入目标文件}// 4. 释放资源并删除管道文件close(infd);close(outfd);unlink("tp");  // 手动删除管道文件printf("文件拷贝完成,目标文件:abc.bak\n");return 0;
}
运行步骤:
  1. 编译两个程序:gcc write_pipe.c -o write_pipegcc read_pipe.c -o read_pipe
  2. 启动进程 B(读端):./read_pipe(此时阻塞,等待写端)。
  3. 启动进程 A(写端):./write_pipe(开始写入数据,进程 B 接收并写入目标文件)。
  4. 进程 A 运行结束后,进程 B 读取完数据也随之结束,管道文件被 unlink() 删除。
实例 2:用命名管道实现 Server&Client 通信

构建简单的 C/S 模型:Server 创建管道并监听读端,Client 通过管道向 Server 发送消息,实现单向通信。

1. 工程结构与 Makefile
$ ll
total 12
-rw-r--r-- 1 user user  450 Oct  2 14:30 clientPipe.c  # 客户端代码
-rw-r--r-- 1 user user  164 Oct  2 14:30 Makefile       # 编译脚本
-rw-r--r-- 1 user user  580 Oct  2 14:30 serverPipe.c  # 服务端代码
Makefile
PHONY: all clean
all: client serverclient: client.ccg++ -o $@ $^ -std=c++11server: server.ccg++ -o $@ $^ -std=c++11clean:rm -f client server
2. Server(服务端:创建管道→读消息)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>#define FIFO_FILE "fifo"int main()
{// 创建管道文件umask(0);int n = mkfifo(FIFO_FILE, 0666);if (n < 0) {std::cerr << "mkfifo error" << std::endl;return 1;}std::cout << "mkfifo success" << std::endl;// 打开管道文件std::cout << "Please wait..." << std::endl;int fd = open(FIFO_FILE, O_RDONLY);if(fd < 0){std::cerr << "open fifo error" << std::endl;return 2;}std::cout << "open fifo success" << std::endl;// 正常的 readwhile(true){char buf[1024];int num = read(fd, buf, sizeof(buf) - 1);if(num > 0){buf[num] = 0;std::cout << "client say# " << buf << std::endl;}else if(num == 0){std::cout << "Client quit! me too!" << std::endl;break;}else{std::cout << "read error" << std::endl;break;}}close(fd);// 删除管道文件n = unlink(FIFO_FILE);if (n < 0) {std::cerr << "unlink error" << std::endl;return 1;}std::cout << "unlink success" << std::endl;return 0;
}
3. Client(客户端:打开管道→发消息)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>#define FIFO_FILE "fifo"int main()
{// writeint fd = open(FIFO_FILE, O_WRONLY);if (fd < 0){std::cerr << "open fifo error" << std::endl;}std::cout << "open fifo sucess" << std::endl;// 写std::string message;int cnt = 0;pid_t id = getpid();while (true){std::cout << "请输入:" << std::endl;std::getline(std::cin, message);message += (", message number: " + std::to_string(cnt++) + ", [" + std::to_string(id) + "]");write(fd, message.c_str(), message.size());}return 0;
}
4. 运行结果
  1. 编译程序:make all,生成 client 和 server 可执行文件。
  2. 启动服务端:

  1. 启动客户端(新终端):

  1. 服务端接收消息:

  1. 客户端退出(Ctrl+C)后,服务端提示并退出:

代码封装:

Server.cc:

#include "comm.hpp"int main()
{// 创建管道文件NamedFifo fifo(".", FILENAME);// 文件操作了FileOper readerfile(PATH, FILENAME);readerfile.OpenForRead();readerfile.Read();readerfile.Close();return 0;
}

Client.cc:

#include "comm.hpp"int main()
{FileOper writerfile(PATH, FILENAME);writerfile.OpenForWrite();writerfile.Write();writerfile.Close();return 0;
}

comm.hpp:

#pragma once#include <iostream>
#include <cstdio>
#include <string>
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>#define PATH "."
#define FILENAME "fifo"#define ERR_EXIT(m)         \do                      \{                       \perror(m);          \exit(EXIT_FAILURE); \} while (0)// 创建管道
class NamedFifo
{
public:NamedFifo(const std::string path, const std::string name): _path(path), _name(name){_fifoname = _path + "/" + _name;umask(0);int n = mkfifo(_fifoname.c_str(), 0666);if (n < 0){ERR_EXIT("mkfifo");}std::cout << "mkfifo success" << std::endl;}~NamedFifo(){int n = unlink(_fifoname.c_str());if (n < 0){ERR_EXIT("unlink");}std::cout << "unlink success" << std::endl;}private:std::string _path;std::string _name;std::string _fifoname;
};// 打开文件
class FileOper
{
public:FileOper(const std::string& path, const std::string& name): _path(path), _name(name), _fd(-1){_fifoname = _path + "/" + _name;}void OpenForRead(){std::cout << "Please wait..." << std::endl;_fd = open(_fifoname.c_str(), O_RDONLY);if (_fd < 0){ERR_EXIT("open");}std::cout << "open fifo success" << std::endl;}void OpenForWrite(){_fd = open(_fifoname.c_str(), O_WRONLY);if (_fd < 0){ERR_EXIT("open");}std::cout << "open fifo sucess" << std::endl;}void Write(){std::string message;int cnt = 0;pid_t id = getpid();while (true){std::cout << "请输入:" << std::endl;std::getline(std::cin, message);message += (", message number: " + std::to_string(cnt++) + ", [" + std::to_string(id) + "]");write(_fd, message.c_str(), message.size());}}void Read(){while (true){char buf[1024];int num = read(_fd, buf, sizeof(buf) - 1);if (num > 0){buf[num] = 0;std::cout << "client say# " << buf << std::endl;}else if (num == 0){std::cout << "Client quit! me too!" << std::endl;break;}else{std::cout << "read error" << std::endl;break;}}}void Close(){if (_fd > 0)close(_fd);}~FileOper(){}private:std::string _path;std::string _name;std::string _fifoname;int _fd;
};

1.5.6 注意事项与常见问题

  1. 权限问题:创建管道时需确保进程有目标目录的写权限,打开管道时需确保有管道文件的读写权限,建议创建前用 umask(0) 清除权限屏蔽。
  2. 管道残留:命名管道文件不会随进程退出自动删除,若程序异常退出未执行 unlink(),需手动删除(rm 管道名),否则下次创建会报错 EEXIST
  3. 阻塞与非阻塞:默认阻塞模式下,读写端需配对才能正常工作;非阻塞模式需做好错误处理(如写端无读端时返回 ENXIO)。
  4. 数据完整性:由于管道是面向字节流,若需传输结构化数据(如消息),需自定义协议(如 “消息长度 + 消息内容”),避免数据粘连。
http://www.dtcms.com/a/435987.html

相关文章:

  • 域名所有人是网站名不能转出用凡科做网站要钱吗
  • asp.net网站开发上网站开发哪家公司电话
  • 个人做的网站有什么危险吗万网官网域名注册多少钱
  • 网站机房建设成本某公司网络营销现状分析
  • 服务器主机 网站安阳企业网站优化外包
  • DS container adapters <B> priority_queue in Huffman Algorithm 2/99
  • 英语学习-Saints042
  • 建立网站条件一级域名如何分发二级域名
  • wordpress发布失败手机网站优化需要注意什么
  • 北京模板网站建设wordpress远程本地化
  • 【P0】Spring Cloud 面试篇
  • freertos内部机制
  • 苏州公司做变更网站建筑工程网站建设
  • 广州最好网站建设公司石家庄网站制作仓谷
  • 腾讯企点客户通长沙网站seo推广公司哪家好
  • 如何查看网站是哪家公司做的?双wordpress自动同步文章
  • 婚礼策划公司优化推广网站怎么做最好
  • 天津企业设计网站建设wordpress注册不成功
  • 何为“类”?(Java基础语法)
  • 德城区建设局网站建设部特殊工种查询网站
  • 如何做优化网站的原创性文章济南移动互联网开发
  • 建设部标准定额研究所网站c2c网站建设方案
  • 济南公司做网站的价格搜索引擎内部优化
  • 网站有效内容的宣传及推广关于做网站的ppt
  • wap网站 微信登录工商银行在线登录入口
  • 没有网站可以icp备案wordpress回到旧版编辑器
  • PINN训练新思路:把初始条件和边界约束嵌入网络架构,解决多目标优化难题
  • 公司主网站百度收录大量网站之类的信息crm客户管理系统哪个好
  • [嵌入式] U-Boot 环境变量深度解析:从 QSPI 到 eMMC 的 Linux 启动完整指南
  • 左右左布局网站建设适合新手的网站开发