侧边栏壁纸
  • 累计撰写 32 篇文章
  • 累计创建 55 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

Linux IPC:Named Pipes(命名管道)

Testerfans
2022-05-20 / 0 评论 / 24 点赞 / 2,016 阅读 / 7,340 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-06-29,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

本文演示 CentOS 7.6:Linux testerfans 3.10.0-1160.45.1.el7.x86_64

前言

Pipes用于关联进程之间的通信,我们是否可以使用管道进行不相关的进程通信呢?例如,我们想从一个终端执行客户端程序,而从另一个终端执行服务器程序?答案是否定的。那我们如何才能实现无关进程的通信呢?答案就是Named Pipes(命名管道)。命名管道也适用于关联进程之间的通信,但实际中并不会这样使用。

关联进程中我们使用一根Pipe进行单向通信,使用两根Pipe进行双向通信。这种情况是否适用于命名管道呢?答案是否定的。因为我们可以使用单个命名管道就可以实现进程之间的双向通信(服务器和客户端之间的通信,同时加上客户端和服务器之间的通信)。命名管道的另一个名称是FIFO(先进先出)。

命名管道特点

  • 命名管道可以在无关的进程之间交换数据,与无名管道不同。
  • 命名管道有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。

系统调用

mkfifo()

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode)

mkfifo() 创建一个名为 pathname 的 FIFO 特殊文件。 mode 指定 FIFO 的权限。如果未给出完整路径名(或绝对路径),则将在执行进程的当前文件夹中创建文件。文件模式信息如 mknod() 系统调用中所述。

传参

  • pathname 创建一个名为 pathname 的 FIFO 特殊文件。
  • mode mode 指定 FIFO 的权限。
    返回
  • 0:成功。
  • -1:失败。

使用 errno 变量或 perror() 函数查看失败信息。

mknod()

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int mknod(const char *pathname, mode_t mode, dev_t dev);

系统调用 mknod() 创建一个名为 pathname 的文件系统节点(文件、设备专用文件或命名管道),其属性由 mode 和 dev 指定。

传参

  • pathname 存放命名管道文件路径,路径名是相对的,如果没有指定目录,它将在当前目录中创建。
  • mode 指定要使用的权限和要创建的节点的类型。它应该是下面列出的文件类型之一和新节点的权限的组合(使用按位或)。
文件类型 描述 文件类型 描述
S_IFBLK block device 块特别 S_IFREG regular file 常规文件
S_IFCHR character device 字符设备 S_IFDIR directory 目录
S_IFIFO FIFO 命名管道 S_IFLNK symbolic link 符号链接
文件模式 描述 文件模式 描述
S_IRWXU 所有者 读/写/执行/搜索 S_IWGRP 组 写权限
S_IRUSR 所有者 读权限 S_IXGRP 组 执行/搜索权限,
S_IWUSR 所有者 写权限 S_IRWXO 其他 读/写/执行/搜索
S_IXUSR 所有者 执行/搜索权限 S_IROTH 其他 读取权限
S_IRWXG 组 读/写/执行/搜索 S_IWOTH 其他 写权限
S_IRGRP 组 读权限 S_IXOTH 其他 执行/搜索权限

文件权限也可以用八进制表示,例如 0XYZ,其中 X 代表所有者,Y 代表组,Z 代表其他。X、Y 或 Z 的取值范围为 0 到 7。读、写和执行的值分别为 4、2、1。结合读取、写入和执行,进行相应负值。比如,如果我们写成0640,代表所有者具备读写 (4 + 2 = 6)权限,组具备读取 (4)权限,其他人没有权限 (0)。

  • dev 如果文件类型是 S_IFCHR 或 S_IFBLK,则 dev 指定新创建的设备特殊文件的主要和次要编号;否则将被忽略。

返回

  • 0:成功。
  • -1:失败。

使用 errno 变量或 perror() 函数查看失败信息。

示例程序 1-命名管道单向通信

我们设计两个在不同的两个终端分别运行客户端和服务端的程序,他们将执行单向通信,即:客户端写入,服务端读取。当客户端输入”end“后,客户端和服务端退出。

示意图

步骤
第1步 - 创建两个进程,一个是 fifoserver,另一个是 fifoclient。
第 2 步- 服务器进程执行以下操作 :

  • 如果未创建,则使用mknod()方法创建名为“MYFIFO”的命名管道。
  • 只读打开命名管道。
  • 所有者分配读/写权限,组分配读权限,其他人没有权限。
  • 无限等待来自客户端的消息。
  • 如果从客户端收到的消息不是“end”,则打印该消息。
  • 如果消息为“end”,则关闭 fifo 并结束进程。

第 3 步 - 客户端进程执行以下操作 :

  • 只写打开命名管道。
  • 接受来自用户的字符串。
  • 检查用户是否输入“end”或“end”以外的内容。
  • 无论哪种方式,它都会向服务器发送消息。但是,如果字符串是“end”,这将关闭 FIFO 并结束进程。
  • 无限重复,直到用户输入字符串“end”。

源代码
fifoserver.c

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define FIFO_FILE "MYFIFO"
int main() {
   int fd;
   char readbuf[80];
   char end[10];
   int to_end;
   int read_bytes;

   //如果不存在则创建名称为MYFIFO的命名管道,为所有者分配读/写权限 组分配读权限 其他人无权限
   mknod(FIFO_FILE, S_IFIFO|0640, 0);
   //将"end"字符串拷贝到end[]数组内
   strcpy(end, "end");
   while(1) {
      //只读方式打开FIFO,获取文件描述符
      fd = open(FIFO_FILE, O_RDONLY);
      //从FIFO中读取内容,返回读取到的字节数量
      read_bytes = read(fd, readbuf, sizeof(readbuf));
      //将读取到的内容最后设置为\0
      readbuf[read_bytes] = '\0';
      //打印读取内容
      printf("Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
      //对比读取到的内容是否为"end" 如果相等则关闭FIFO文件并跳出程序
      to_end = strcmp(readbuf, end);
      if (to_end == 0) {
         close(fd);
         break;
      }
   }
   return 0;
}

fifoclient.c

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FIFO_FILE "MYFIFO"
int main() {
   int fd;
   int end_process;
   int stringlen;
   char readbuf[80];
   char end_str[5];
   printf("FIFO_CLIENT: Send messages, infinitely, to end enter \"end\"\n");
   //读写方式打开一个MYFIFO文件 不存在则新建
   fd = open(FIFO_FILE, O_CREAT|O_WRONLY);
   strcpy(end_str, "end");

   while (1) {
      printf("Enter string: ");
      //从标准输入内读取信息
      fgets(readbuf, sizeof(readbuf), stdin);
      stringlen = strlen(readbuf);
      readbuf[stringlen - 1] = '\0';
      //判断输入是否为end
      end_process = strcmp(readbuf, end_str);

      //printf("end_process is %d\n", end_process);
      //如果输入不是end 像FIFO内写入标准输入的信息 如果是end依然写入
并且关闭文件并跳出程序
      if (end_process != 0) {
         write(fd, readbuf, strlen(readbuf));
         printf("Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
      } else {
         write(fd, readbuf, strlen(readbuf));
         printf("Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
         close(fd);
         break;
      }
   }
   return 0;
}

编译

[root@testerfans pipes]# gcc fifoserver.c -o fifoserver
[root@testerfans pipes]# gcc fifoclient.c -o fifoclient

执行/输出
我们打开两个shell,一个作为客户端,一个作为服务端,执行结果如截图。

示例程序 2-命名管道双向通信

示例1我们演示了命名管道的单向通信,即:客户端写入,服务端读取。接下来我们将演示命名管道的双向通信,即:客户端和服务端之间互相发送消息。当客户端输入”end“后,客户端和服务端停止运行。

示意图

步骤
第1步 - 创建两个进程,一个是 fifoserver_twoway,另一个是 fifoclient_twoway。
第 2 步- 服务器进程执行以下操作 :

  • 如果未创建,则使用mknod()方法在/tmp下创建名为“MYFIFO”的命名管道。
  • 读写权限打开命名管道。
  • 所有者分配读/写权限,组分配读权限,其他人没有权限。
  • 无限等待来自客户端的消息。
  • 如果从客户端收到的消息不是“end”,则打印该消息并将消息反转返回给客户端。
  • 如果消息为“end”,则关闭 fifo 并结束进程。

第 3 步 - 客户端进程执行以下操作 :

  • 读写权限打开命名管道。
  • 接受来自用户的字符串。
  • 检查用户是否输入“end”或“end”以外的内容。
  • 无论哪种方式,它都会向服务器发送消息。但是,如果字符串是“end”,这将关闭 FIFO 并结束进程。
  • 如果不是结束,等待服务端返回的信息。
  • 无限重复,直到用户输入字符串“end”。

源代码
fifoserver_twoway.c

#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define FIFO_FILE "/tmp/fifo_twoway"
void reverse_string(char *);
int main() {
   int fd;
   char readbuf[80];
   char end[10];
   int to_end;
   int read_bytes;

   //如果不存在在/tmp下创建fifo_twoway的FIFO文件 所有者具备读写权限 组具备读权限 其他无权限
   mkfifo(FIFO_FILE, S_IFIFO|0640);
   strcpy(end, "end");
   //读写方式打开文件
   fd = open(FIFO_FILE, O_RDWR);
   //从FIFO内读取文件 如果客户端输入end关闭文件并退出程序
   while(1) {
      read_bytes = read(fd, readbuf, sizeof(readbuf));
      readbuf[read_bytes] = '\0';
      printf("FIFOSERVER: Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
      to_end = strcmp(readbuf, end);

      if (to_end == 0) {
         close(fd);
         break;
      }
      //如果客户端输入的不是end 则将读取到的信息反转
      reverse_string(readbuf);
      printf("FIFOSERVER: Sending Reversed String: \"%s\" and length is %d\n", readbuf, (int) strlen(readbuf));
      //将反转后的信息写入到文件
      write(fd, readbuf, strlen(readbuf));
      /*
      sleep - This is to make sure other process reads this, otherwise this
      process would retrieve the message
      */
      sleep(2);
   }
   return 0;
}

void reverse_string(char *str) {
   int last, limit, first;
   char temp;
   last = strlen(str) - 1;
   limit = last/2;
   first = 0;

   while (first < last) {
      temp = str[first];
      str[first] = str[last];
      str[last] = temp;
      first++;
      last--;
   }
   return;
}

fifoclient_twoway.c

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define FIFO_FILE "/tmp/fifo_twoway"
int main() {
   int fd;
   int end_process;
   int stringlen;
   int read_bytes;
   char readbuf[80];
   char end_str[5];
   printf("FIFO_CLIENT: Send messages, infinitely, to end enter \"end\"\n");
   fd = open(FIFO_FILE, O_CREAT|O_RDWR);
   strcpy(end_str, "end");

   //向FIFO内写入文件并从中读取,因为服务端进行了两秒的sleep操作,确保客户端能正常读取到信息
   while (1) {
      printf("Enter string: ");
      fgets(readbuf, sizeof(readbuf), stdin);
      stringlen = strlen(readbuf);
      readbuf[stringlen - 1] = '\0';
      end_process = strcmp(readbuf, end_str);

      //printf("end_process is %d\n", end_process);
      if (end_process != 0) {
         write(fd, readbuf, strlen(readbuf));
         printf("FIFOCLIENT: Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
         read_bytes = read(fd, readbuf, sizeof(readbuf));
         readbuf[read_bytes] = '\0';
         printf("FIFOCLIENT: Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
      } else {
         write(fd, readbuf, strlen(readbuf));
         printf("FIFOCLIENT: Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
         close(fd);
         break;
      }
   }
   return 0;
}

编译

[root@testerfans pipes]# gcc fifoserver_twoway.c -o fifoserver_twoway
[root@testerfans pipes]# gcc fifoclient_twoway.c -o fifoclient_twoway

执行/输出
我们打开两个shell,一个作为客户端,一个作为服务端,执行结果如截图。

总结

和Pipes无名管道相比,我们演示的Names Pipe命名管道的单向通信和双向通信都没有使用到fork()方法,说明在不相关进程之间的通信可以使用命名管道。另外命名管道进行双向通信的时候并没有创建两个管道,说明使用一个命名管道即可实现不相关进程间的单向或双向通信。


本文参考:
Inter Process Communication - Named Pipes
man手册

24

评论区