命名管道通信

概述

该代码演示了使用命名管道进行进程间通信的方法。命名管道是一种在无关的进程之间创建双向通信通道的 IPC(进程间通信)机制。在这个例子中,我们创建了一个服务器和一个客户端,它们可以通过命名管道交换数据。

文件

  • comm.hpp:包含用于创建和移除命名管道的实用函数的头文件。
  • client.cpp:客户端应用程序,将用户输入写入命名管道。
  • server.cpp:服务器应用程序,从命名管道读取数据并显示。

使用方法

  1. 编译:使用 C++ 编译器编译代码。例如,使用 g++:

    1
    2
    g++ client.cpp -o client
    g++ server.cpp -o server
  2. 运行

    • 在一个终端中启动服务器:

      1
      ./server
    • 在另一个终端中启动客户端:

      1
      ./client
  3. 通信:客户端提示用户输入,输入的数据会被写入命名管道,服务器会读取并显示接收到的数据。

  4. 结束:要退出程序,使用适当的退出命令(例如,Ctrl+C)。

工作原理

命名管道创建和移除

  • 使用 createFifo 函数创建命名管道。在这之前,通过 umask(0) 确保文件权限不受限制。
  • 使用 removeFifo 函数移除命名管道。这两个函数确保了正确的权限和错误处理。

客户端写入

  • 客户端通过 open 函数以只写方式打开命名管道。
  • 使用 write 将用户输入写入管道。

服务器读取

  • 服务器通过 open 函数以只读方式打开命名管道。
  • 使用 read 从管道中读取数据,并在控制台上显示。

附加说明

  • 代码中的异常处理确保了在发生错误时程序能够优雅地退出并输出错误信息。
  • 这个例子是一个基本的命名管道通信演示,可以根据需要扩展和修改以满足更复杂的通信需求。

comm.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string>
#include <cstring>
#include <cerrno>
#include <stdexcept>
#include <cassert>

// 定义命名管道的路径
#define NAMED_PIPE "./myPipe"

// 创建命名管道的函数
bool createFifo(const std::string &path)
{
// 设置文件权限掩码为0,确保后续创建的文件权限不受限制
umask(0);

// 使用 mkfifo 函数创建命名管道,权限为 0600
if (mkfifo(path.c_str(), 0600) == 0)
return true;
else
throw std::runtime_error("无法创建命名管道: " + std::string(strerror(errno)));
}

// 移除命名管道的函数
void removeFifo(const std::string &path)
{
// 使用 unlink 函数删除命名管道
if (unlink(path.c_str()) != 0)
throw std::runtime_error("无法删除命名管道: " + std::string(strerror(errno)));
}

client.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include "comm.hpp"

int main()
{
std::cout << "客户端启动!\n";

// 以只写方式打开命名管道
int wfd = open(NAMED_PIPE, O_WRONLY);

// 检查文件描述符是否有效
if (wfd < 0)
exit(1);

while (true)
{
char buffer[1024];
memset(buffer, 0, sizeof(buffer));

std::cout << "请输入: ";
fgets(buffer, sizeof(buffer), stdin);

// 去掉输入字符串的换行符
if (strlen(buffer) > 0)
buffer[strlen(buffer) - 1] = 0;

// 将用户输入写入命名管道
ssize_t writeBytes = write(wfd, buffer, strlen(buffer));

// 使用断言确保写入成功
assert(writeBytes == strlen(buffer));
(void)writeBytes; // 防止未使用的变量警告
}

// 关闭文件描述符
close(wfd);
}

server.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include "comm.hpp"

int main()
{
// 创建命名管道
bool res = createFifo(NAMED_PIPE);
assert(res);
(void)res;

std::cout << "服务器启动!等待客户端启动中...\n";

// 以只读方式打开命名管道
int rfd = open(NAMED_PIPE, O_RDONLY);
std::cout << "客户端已启动,可以开始通信!\n";

// 检查文件描述符是否有效
if (rfd < 0)
exit(1);

while (true)
{
char buffer[1024];
memset(buffer, 0, sizeof(buffer));

// 从命名管道中读取数据
ssize_t readBytes = read(rfd, buffer, sizeof(buffer) - 1);

if (readBytes > 0)
{
// 显示从客户端接收到的数据
std::cout << "客户端 -> 服务器: " << buffer << "\n";
}
else if (readBytes == 0)
{
// 客户端已关闭连接,退出循环
std::cout << "客户端退出,服务器也退出!\n";
break;
}
else
{
// 处理读取错误
std::cout << "错误: errno " << errno << " | 错误信息: " << strerror(errno) << std::endl;
break;
}
}

// 关闭文件描述符
close(rfd);

// 移除命名管道
removeFifo(NAMED_PIPE);
}

makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 将 "all" 目标声明为伪目标
.PHONY: all

# 定义 "all" 目标,其依赖于 "client" 和 "server"
all: client server

# 从 "shm_client.cpp" 编译 "client" 程序
client: client.cpp
g++ -o $@ $^ -std=c++11

# 从 "shm_server.cpp" 编译 "server" 程序
server: server.cpp
g++ -o $@ $^ -std=c++11

# 将 "clean" 目标声明为伪目标
.PHONY: clean

# 定义 "clean" 目标以删除已编译的二进制文件
clean:
rm -f server client