云计算百科
云计算领域专业知识百科平台

【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 注意事项:

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

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

    • 我们在 服务端 中创建命名管道。

    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 总结:

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

    本文介绍了基于命名管道(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.cc
    g++ o $@ $^ std=c++11
    server:server.cc
    g++ o $@ $^ std=c++11

    .PHONY:clean
    clean:
    rm f client server

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

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

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【Linux篇】轻松搭建命名管道通信:客户端与服务器的互动无缝连接
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!