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

【Linux篇】轻松搭建命名管道通信:客户端与服务器的互动无缝连接

从零开始:基于命名管道实现客户端与服务器的实时通信

  • 一. 命名管道
    • 1.1 基本概念
    • 1.2 创建命名管道
      • 1.2.1 创建方法
      • 1.2.2 示例代码:
      • 1.2.3 注意事项:
      • 1.3 与匿名管道区别
    • 1.4 打开原则
      • 1.4.1 管道打开顺序
      • 1.4.2 阻塞行为
      • 1.4.3 管道的关闭
      • 1.4.4 关闭读写端的规则
    • 1.5 特点
  • 二. 客户端与服务器的实现
    • 2.1 简介
    • 2.2 基本工作原理
    • 2.3 实现通信
      • 2.3.1 创建并设置命名管道
      • 2.3.2 服务端实现(读取命名管道中的消息)
      • 2.3.3 客户端实现(向命名管道写入消息)
      • 2.3.4 定义常量和文件路径
      • 2.3.5 总结:
  • 三. 最后

命名管道是一种用于进程间通信(IPC)的机制,它通过特定的文件路径进行数据传输。客户端和服务器可以通过打开命名管道进行双向数据交换。服务器通常会创建一个命名管道并等待客户端连接,而客户端则需要访问这个管道进行通信。通信的过程基于FIFO(先进先出)原则,即先写入的数据会先被读取。通过这种方式,客户端和服务器能够实现异步通信,且数据传输简单高效。命名管道的优势在于它不需要显式的网络连接,使得在同一台机器上的进程间通信变得更加便捷。

💬 欢迎讨论:如果你在学习过程中有任何问题或想法,欢迎在评论区留言,我们一起交流学习。你的支持是我继续创作的动力!
👍点赞、收藏与分享:觉得这篇文章对你有帮助吗?别忘了点赞、收藏并分享给更多的小伙伴哦!你们的支持是我不断进步的动力!
🚀分享给更多人:如果你觉得这篇文章对你有帮助,欢迎分享给更多对Linux OS感兴趣的朋友,让我们一起进步!

一. 命名管道

1.1 基本概念

命名管道(Named Pipe)是一种用于进程间通信(IPC)的机制,它允许不同进程之间通过一个命名的通道交换数据。与匿名管道不同,命名管道是通过一个系统级的文件路径来标识和访问,允许不同的进程在不同的时间、甚至不同的机器上进行通信。

允许不具有血缘关系的进程之间进行通信,数据使用FIFO原则进行数据传输。

1.2 创建命名管道

1.2.1 创建方法

  • 方法一:在命令行创建语法:

mkfifo filename

该指令将会创建命名管道filename(管道名称)。

  • 方法二: 使用相关的函数创建,语法如下:

int mkfifo(const char *pathname, mode_t mode);

参数说明:

  • pathname:这是要创建的命名管道的路径。这个路径需要是一个有效的文件路径,且在文件系统中应该是一个文件名。
  • mode:指定管道的权限,类似于文件的权限。它通常是一个由数字组成的权限值,类似于文件的权限位,例如 S_IRUSR | S_IWUSR表示用户可读可写。权限的设置遵循Linux的文件权限规则。

返回值:

  • 如果命名管道创建成功,返回 0。
  • 如果失败,返回 -1 并设置 errno 以指示错误原因。

1.2.2 示例代码:

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>int main() {const char *fifo_path = "/tmp/my_fifo";//命名管道路径// 创建命名管道if (mkfifo(fifo_path, 0666) == -1) {perror("mkfifo failed");return 1;}printf("FIFO created successfully\n");//成功是打印该内容return 0;
}

在这个示例中,mkfifo 创建了一个名为 /tmp/my_fifo 的命名管道,权限设置为 0666,即文件的所有者、用户组和其他用户均可读写。

1.2.3 注意事项:

  1. 创建的管道必须由至少一个进程进行读写操作,否则可能会导致另一个进程调用 open() 时被阻塞,直到有进程打开该管道。
  2. 命名管道是一种特殊的文件,进程可以像普通文件一样对其进行读写,但它实际上是在进程之间传递数据的通道。

1.3 与匿名管道区别

特性命名管道(Named Pipe)匿名管道(Anonymous Pipe)
标识方式有文件路径,可以通过路径访问没有文件路径,仅通过管道描述符访问
作用范围支持不同进程间通信,甚至跨系统通信
创建方式使用mkfifo()创建使用pipe()创建
生命周期用户控制,管道文件存在于文件系统中进程结束后自动销毁
阻塞行为支持阻塞,写操作阻塞直到有读进程支持阻塞,写操作阻塞直到有读进程
使用场景不同进程间通信父子进程之间通信

命名管道具有更强的灵活性,适用于跨进程、跨系统的复杂场景;而匿名管道则更简单,适用于父子进程间的简单数据传输。

  • 如何理解用户控制:

"用户控制”则表示,管道的生命周期由用户管理,用户可以决定管道的创建、删除时机,并且管道不会自动销毁,直到用户删除它。

1.4 打开原则

1.4.1 管道打开顺序

  • 写进程:可以在没有读进程存在的情况下打开管道,数据被放入管道缓冲区,直至有读进程打开并读取读取数据。如果写进程在管道缓冲区已满时继续写入,写端会发生阻塞,直到有数据可以读取。如果没有任何进程写入数据,读进程将永久阻塞。
  • 读进程:写进程必须存在,没有时读端将发生阻塞,直至有数据可以读取。如果没有任何进程写入数据,读进程将会发生永久性阻塞。

1.4.2 阻塞行为

  • 阻塞读取:如果读进程尝试从命名管道读取数据,但管道中没有数据,它会被阻塞,直到有写进程向管道写入数据。
  • 阻塞写入:如果写进程向管道写入数据,而没有任何读进程准备好读取数据(或者管道缓冲区已满),写进程将被阻塞,直到有读进程读取数据或者管道中有空闲空间。

1.4.3 管道的关闭

  • 进程关闭管道:进程在使用完管道后应该调用 close()来关闭管道。如果管道被完全关闭(即读写端都关闭),则其他试图读取或写入的进程会遇到 EOF 或 EPIPE 错误。
  • 当管道中的数据被全部读取后,读进程会收到一个“文件结束标志(EOF)”。如果继续尝试读取,将会返回 0,表示没有更多数据可读取。

1.4.4 关闭读写端的规则

  • 写端关闭时:如果写端关闭而读端仍在读取数据,读端会得到一个 EOF(文件结束符)信号,表明没有更多数据。
  • 读端关闭时:如果读端关闭而写端继续写入数据,写进程会得到一个 EPIPE 错误,表明管道的另一端已经关闭。

1.5 特点

特点与匿名管道相似,唯一不同的是它可以让毫不相干的进程之间进行通信。

二. 客户端与服务器的实现

2.1 简介

用命名管道(Named Pipe)实现客户端与服务端通信是一种常见的进程间通信(IPC)方式。在这种模式下,服务端和客户端通过一个共享的命名管道进行数据交换。命名管道提供了一个通信通道,使得它们可以在不同的进程间进行双向数据传输。由于命名管道是一种通过文件系统实现的通信机制,因此客户端和服务端可以通过文件路径访问该管道。

2.2 基本工作原理

命名管道的工作原理基于先进先出规则,即数据写入管道的顺序会按照写入的顺序被读取。它在进程间提供一个缓冲区,允许数据从一个进程流向另一个进程。

在客户端和服务端通信中,服务端通常是管道的创建者,而客户端则是通过管道进行读取和写入操作。

2.3 实现通信

2.3.1 创建并设置命名管道

为什么要使用命名管道(FIFO)?

  1. 命名管道是通过文件系统提供的通信机制,不同的进程可以通过这个管道进行数据交换。
  2. 创建管道时,可以通过文件路径访问和管理管道,因此可以跨进程通信。

创建命名管道:

  • 我们在 服务端 中创建命名管道。
int n = mkfifo(FIFO_FILE, 0666);  // 创建管道,文件名为 FIFO_FILE
if (n != 0) {std::cerr << "mkdir fifo error" << std::endl;return 1;
}

2.3.2 服务端实现(读取命名管道中的消息)

  • 服务端的主要功能:

服务端的工作是打开命名管道文件,读取客户端发送过来的消息并显示。

int fd = open(FIFO_FILE, O_RDONLY);  // 以只读方式打开命名管道
if (fd < 0) {std::cerr << "open fifo error" << std::endl;return 2;
}
  • 解释:

open() 用于打开已经创建的命名管道文件。这里我们以 O_RDONLY(只读)方式打开它,意味着服务端只是从管道中读取数据。

如果管道无法打开,输出错误信息并返回 2。

  • 读取信息
char buffer[1024];
while (true) {int n = read(fd, buffer, sizeof(buffer) - 1);  // 从管道读取数据if (n > 0) {buffer[n] = 0;  // 确保字符串结束std::cout << "client say# " << buffer << std::endl;  // 打印客户端消息}
}
  • 解释:

read() 函数从命名管道中读取数据,直到管道中没有数据可读。buffer 存储读取到的数据。

如果读取成功,将数据输出到控制台。

通过 n 判断读取的字节数,如果大于 0,说明读取成功。

  • 关闭管道
close(fd);  // 关闭管道
  • 解释:

完成数据读取后,关闭管道文件描述符。

2.3.3 客户端实现(向命名管道写入消息)

  • 客户端的主要功能:

客户端需要向命名管道写入消息。在每次输入后,客户端会将用户输入的消息写入管道,供服务端读取。

int fd = open(FIFO_FILE, O_WRONLY);  // 以只写方式打开命名管道
if (fd < 0) {std::cerr << "open fifo error" << std::endl;return 2;
}
  • 解释:

open() 用于打开管道文件,O_WRONLY 表示只写打开管道。客户端仅通过该管道写入数据。

如果打开失败,输出错误并返回 2。

发送信息:

while (true) {std::cout << "Please Enter# ";std::string message;std::cin >> message;  // 从用户输入读取消息int n = write(fd, message.c_str(), message.size());  // 将消息写入管道if (n > 0) {// 写入成功}
}
  • 解释:

std::cin >> message 用于从用户输入获取消息。

write() 用于将消息写入管道。如果消息成功写入,n 返回写入的字节数。

关闭管道:

close(fd);  // 关闭管道
  • 解释:

客户端发送完消息后,关闭管道。

2.3.4 定义常量和文件路径

#pragma once
#define FIFO_FILE "fifo"  // 定义命名管道的文件路径
  • 解释:

在 common.hpp 文件中定义一个宏 FIFO_FILE,用于存储命名管道的路径。在客户端和服务端中都使用这个路径,确保一致性。

2.3.5 总结:

  1. 服务端:创建并打开命名管道,通过 read() 从管道读取客户端发送的消息,并输出到控制台。
  2. 客户端:打开命名管道,通过 write() 将用户输入的消息发送到管道。
  3. 进程之间通过 命名管道(FIFO)共享数据,命名管道提供了一个简单的方式来进行进程间通信。
  4. 客户端和服务端各自控制着管道的一端,数据的传递是同步的,即写入和读取是交替进行的。

三. 最后

本文介绍了基于命名管道(FIFO)实现客户端与服务器实时通信的过程。命名管道是一种通过文件路径进行进程间通信的机制,支持不同进程之间的数据传输,基于FIFO(先进先出)原则。通过 mkfifo() 函数可以创建命名管道,客户端和服务端通过管道文件进行数据交换。服务端创建并打开管道以读取数据,客户端通过管道向服务端写入消息。命名管道的优势在于不需要网络连接,适用于同一台机器上的进程通信。通过合适的同步与阻塞机制,客户端与服务端能够顺利实现数据传输。

完整代码(如下):

server.cc

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstring>
#include <unistd.h>
#include "common.hpp"int main()
{umask(0);int n = mkfifo(FIFO_FILE, 0666);if(n != 0){std::cerr << "mkdir fifo error" << std::endl;return 1;}int fd = open(FIFO_FILE,O_RDONLY);if(fd < 0){std::cerr << "open fifo error" << std::endl;return 2;}char buffer[1024];while(true){int n = read(fd,buffer,sizeof(buffer) -1);if(n > 0){buffer[n] = 0;std::cout << "client say# " << buffer << std::endl;}}close(fd);return 0;
}

client.cc

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstring>
#include <unistd.h>
#include "common.hpp"int main()
{int fd = open(FIFO_FILE,O_WRONLY);if(fd < 0){std::cerr << "open fifo error" << std::endl;return 2;}while(true){std::cout << "Please Enter# ";std::string message;std::cin >> message;int n = write(fd, message.c_str(),message.size());if(n > 0){}}close(fd);return 0;
}

comm.hpp

#pragma once#define FIFO_FILE "fifo"

makefile

.PHONY:all
all:client server
client:client.ccg++ -o $@ $^ -std=c++11
server:server.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f client server

上述代码可以实现两者进行通信。

演示图片(如下图):
在这里插入图片描述

相关文章:

  • 卷积神经网络--手写数字识别
  • day33和day34图像处理OpenCV
  • 教育行业网络安全:守护学校终端安全,筑牢教育行业网络安全防线!
  • FastGPT Docker Compose本地部署与硅基流动免费AI接口集成指南
  • 【计算机网络】第五章 局域网技术
  • GPT,Genini, Claude Llama, DeepSeek,Qwen,Grok,选对LLM大模型真的可以事半功倍!
  • 形象理解华为云物联网iotDA开发流程
  • 批量导出多个文件和文件夹名称与路径信息到Excel表格的详细方法
  • AgentGPT开源程序可以在浏览器中组装、配置和部署自主人工智能代理
  • 高并发场景下的淘宝 API 开发实践:商品数据实时采集与性能优化
  • 修改IP地址能否精确到地级市的县?——全面解析
  • 基于ueditor编辑器的功能开发之重写ueditor的查找和替换功能,支持滚动定位
  • ts中的类型
  • CSS零基础入门笔记:狂神版
  • 前端实战-AJAX
  • ubuntu 22.04 安装和配置 mysql 8.0,设置开机启动
  • 力扣热题100——矩阵
  • Spring Boot 断点续传实战:大文件上传不再怕网络中断
  • 74.搜索二维矩阵
  • 学习海康VisionMaster之垂线查找
  • 陕西一批干部任职公示:西安市未央、雁塔、阎良区委书记拟调整
  • 民生访谈|“AI推广是把学生教聪明还是教笨了?这个问题必须回答好”
  • 沙龙 | 新书分享:中国电商崛起的制度密码
  • 习近平致电祝贺诺沃亚当选连任厄瓜多尔总统
  • 中远海运:坚决反对美方对中国海事物流及造船业301调查的歧视性决定
  • 田野调查|“心青年”的日常秩序与归属之地