Linux应用进程间通信二(命名管道)

Linux应用进程间通信二(命名管道)

一、概述

命名管道(Named Pipe) 是一种特殊的文件类型,在操作系统中用于进程间通信(IPC)。它类似于普通的管道(Anonymous Pipe),但具有不同之处。命名管道有一个特定的名字,并且存在于文件系统中,可以被不同的进程通过该名称访问。

1.1、命名管道的特点

  1. 进程间通信:命名管道是用于不同进程之间交换数据的机制,允许一个进程向管道中写入数据,另一个进程从管道中读取数据。

  2. 文件系统中的存在:与匿名管道不同,命名管道在文件系统中有一个固定的路径。命名管道是通过文件系统中的文件名来标识的,这个文件名通常位于特殊的目录下,如 /tmp/dev(取决于操作系统)。

  3. 全双工通信:命名管道支持全双工通信,也就是可以进行双向数据交换,即一个进程可以同时从管道中读取数据,同时向管道中写入数据。通常,命名管道的读写操作是由不同的进程来完成的。

  4. 有名字的管道文件:命名管道是文件系统中的一类特殊文件。它的名字用于标识和访问该管道,因此多个进程可以通过相同的文件路径来进行通信。

  5. 持久性:命名管道的生命期不依赖于创建它的进程。它在文件系统中存在,直到显式地被删除。

1.2、命名管道与匿名管道的区别

特性命名管道 (Named Pipe)匿名管道 (Anonymous Pipe)
存在方式存在于文件系统中,有名字,可以跨进程使用不存在文件系统中,仅在父子进程间使用
进程通信范围支持不相关进程间通信(即使它们不是父子进程)只能在父子进程或相关进程间通信
生命周期在文件系统中存在,直到被删除生命周期与进程相关,进程结束时消失
通信方式支持全双工(双向通信)通常是半双工(一个方向写入,另一个方向读取)

1.3、命名管道的使用方法

命名管道的使用流程通常包括两个步骤:

  1. 创建命名管道:使用 mkfifo()mknod() 系统调用在文件系统中创建一个命名管道。

  2. 打开和读写管道:进程可以通过标准的文件 I/O 操作(open()read()write())来与命名管道进行交互。

二、创建命名管道

命名管道的主要用途:不相关的进程之间交换数据。

命令行上创建命名管道:

$ mkfifo filename

 程序中创建命名管道:

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

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

备注:#include <sys/types.h>是Unix/Linux系统的基本系统数据类型的头文件,含有Size_t,time_t,pid_t等类型。

   #include<sys/stat.h>是Unix/Linux系统定义文件状态所在的伪标准头文件。

例程:创建一个FIFO命名管道

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

int main(int argc, char *argv[])
{
    int res;

    res = mkfifo("./FIFO", 0777);
    if(res == 0)
    {
        printf("FIFO created\n");
    }

    exit(EXIT_SUCCESS);

}

备注:文件权限方面的知识有点模糊,需要回顾。

$ ls -lF(F选项会在显示目录条目时,在目录后加一个符号“/”表示文件夹 “|”表示管道)
prwxr-xr-x 1 root root     0 12月 17 17:02 FIFO|

创建出来的FIFO管道如上图所示,其中第一个字符p表示这是一个管道,其中最后一个|符号是由ls命令的-F选项所添加,也表示它是一个管道。

FIFO是以命名文件的形式存在,而不是打开的文件描述符,所以在对它进行读写操作之前必须先打开它。而且,对于FIFO来说,传递给open函数的第一个参数一定是一个FIFO的路径名,而不是一个文件名。

三、使用open打开FIFO文件

1.FIFO命名管道不能以O_RDWR的模式打开,会产生二义性。(通常使用FIFO只是为了单向传递数据)

2.如果确定需要程序之间双向传递数据。①最好使用一对FIFO或者管道,一个方向使用一个②采用先关闭再重新打开FIFO的方法来明确地改变数据流的去向(不常用)。

3.打开FIFO文件和打开普通文件的另一点区别是:对open_flag(open的第二个参数)的O_NONBLOCK的用法。

  • open(const char *path, O_RDONLY);

在这种情况下,open调用将阻塞,除非有一个进程以写方式打开一个FIFO,否则它是不会返回。

  • open(const char *path, O_RDONLY | O_NONBLOCK);

即使没有其他进程以写方式打开FIFO,open调用也会成功并且立即返回。

  • open(const char *path, WRONLY);

在这种情况下,open调用将阻塞,直到有一个进程以读方式打开进程。

  • open(const char *path,  WRONLY | O_NONBLOCK);

这个函数调用总是立刻返回,但如果没有进程以读方式打开FIFO文件,open调用将返回一个错误-1并且FIFO也不会被打开。如果确实有一个进程以读方式打开FIFO文件,那么我们就我再可以通过它返回的文件描述符对这个FIFO文件进行写操作。

  注意:由以上可知,必须先有进程以读方式打开FIFO文件,然后再有进程以写方式打开FIFO文件。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h> /* using string */
#include <fcntl.h> /* O_RDONLY in this file */

#define FILE_NAME "/home/test/readfifo"

int main(int argc, char *argv[])
{
    int res;
    
  /* 确保文件存在 */
    if(access(FILE_NAME, F_OK) == -1)
    {
        res = mkfifo(FILE_NAME, 0777);

        if(res != 0)
        {
            printf("the fifo file could not create\n");
            exit(EXIT_FAILURE);
        }
    }

    printf("read fifo file create successfull \n");

    open(FILE_NAME, O_RDONLY | O_NONBLOCK);

    printf("open the read fifo file is success \n");

    exit(EXIT_SUCCESS);
}

备注:string.h在使用到字符数组时需要使用。

fcntl.h是unix标准中通用的头文件,其中包含相关函数有open,fcntl,shutdown,unlink,fclose等。与unistd.h相比,后者定义了更多的函数原型。

在open的函数调用中,如果是非阻塞。

read fifo file create successfull 

open the read fifo file is success 

在open的函数调用中,如果是阻塞,即没有使用O_NONBLOCK

read fifo file create successfull 

就会阻塞在open函数上,然后等待写进程打开这个函数,然后才会返回。

 注意:Linux中进程被阻塞时,并不消耗CPU资源。

四、对FIFO进行读写操作

如果在open函数使用了O_NONBLOCK模式,会影响到对FIFO的read和write调用。

  • 对一个空的、阻塞的FIFO(即没有用O_NONBLOCK标志打开的)的read调用将等待,直到有数据可以读时才继续执行。
  • 对一个空的、非阻塞的FIFO的read调用将立刻返回0字节。
  • 对一个完全阻塞FIFO的write调用将等待,直到数据可以被写入才继续执行,如果FIFO不能接受所有写入数据,将按照下面的规则执行:①如果请求写入的数据长度小于等于PIPE_BUF字节,调用失败,数据不能写入。(表示有空间,但是不能写入,出现问题)。②如果请求写入的数据的长度大于PIPE_BUF字节,将写入部分数据,返回实际写入的字节数,返回值也可能是0(表示只是FIFO的空间不足,先写入部分数据)。

注意:PIPE_BUF是系统对FIFO的一个数据长度的限制,通常在头文件limits.h可以找到它,通常值为4096字节,也有特例。系统规定:在一个以O_WRONLY方式(即阻塞方式)打开的FIFO中,如果写入的数据长度小于等于PIPE_BUF,要么全部一次性写入,要么一个字节也不写入。

基于“注意”中的描述,如果出现一种情况:多个程序向一个FIFO中写入数据的时候,为了保证这些写入的数据不会相互交错重叠,那么就要求每次写入的数据长度要小于等于PIPE_BUF字节。

五、举例应用

5.1、示例代码:C语言中的命名管道

下面是一个简单的示例,展示如何在 C 语言中创建和使用命名管道:

1、写入端程序(Writer)
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    int fd;
    char *fifo = "/tmp/myfifo";
    char *message = "Hello from writer!";

    // 打开命名管道(写入)
    fd = open(fifo, O_WRONLY);
    if (fd == -1) {
        perror("Open FIFO for writing failed");
        exit(1);
    }

    // 写入数据到命名管道
    write(fd, message, strlen(message) + 1);

    // 关闭管道
    close(fd);

    return 0;
}
2、读取端程序(Reader)
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    int fd;
    char *fifo = "/tmp/myfifo";
    char buffer[100];

    // 打开命名管道(读取)
    fd = open(fifo, O_RDONLY);
    if (fd == -1) {
        perror("Open FIFO for reading failed");
        exit(1);
    }

    // 从管道读取数据
    read(fd, buffer, sizeof(buffer));

    // 输出读取到的数据
    printf("Received: %s\n", buffer);

    // 关闭管道
    close(fd);

    return 0;
}

5.2、命名管道的工作原理

  1. 创建管道:在文件系统中创建一个管道文件,通常使用 mkfifo() 函数。该文件实际上是一个特殊的管道文件,而不是普通的文件。

  2. 写入数据:一个进程打开这个管道文件并写入数据。该进程向管道写入数据时,如果没有进程从管道读取数据,写操作会被阻塞,直到有读取进程。

  3. 读取数据:另一个进程可以打开同一个管道文件,并从中读取数据。读取操作会阻塞,直到管道中有数据。

  4. 关闭管道:读写操作完成后,进程应该关闭管道文件。

5.3、管道的同步特性

  • 阻塞行为:如果管道满了(例如写入操作超过了管道的缓冲区大小),写入操作会被阻塞,直到有进程从管道中读取数据来腾出空间。
  • 管道的无缓冲特性:管道的默认行为是以字节流的形式进行数据传输,数据在读取端取出后会从管道中消失。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值