二.有命管道(FIFO)

目录

1.1 有名管道概述

1.1.1有名管道特点

1.2 有名管道的创建 

1.3 有名管道的基本读写操作

1.4有名管道实现进程间通信

1.5有名管道的读写规律(阻塞)

1.5.1 读写端都存在,只读不写

1.5.2 读写端都存在

,只写不读

1.5.3 在一个进程中,只有读端,没有写端

1.5.4 在一个进程中,只有写端,没有读端


1.1 有名管道概述

   有名管道(Named Pipe)是一种在文件系统中存在的特殊文件,它允许不相关的进程之间进行通信。与无名管道不同,有名管道通过一个路径名在文件系统中存在,并且可以由多个进程同时访问

1.1.1有名管道特点

有名管道概述 命名管道(FIFO)和管道(pipe)基本相同,但也有一些显著的不同, 其特点是:

1、半双工,数据在同一时刻只能在一个方向上流动。

2、写入FIFO中的数据遵循先入先出的规则。

3、FIFO所传送的数据是无格式的,这要求FIFO的读出方与写入方必须事先约定好数据的格式,如多少字节算一个消息等。

4、FIFO在文件系统中作为一个特殊的文件而存在并且在文件系统中可见,所以有名管道可以实现不相关进程间通信,但FIFO中的内容却存放在内存中

5、管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。

6、从FIFO读数据是一次性操作,数据一旦被读,它就从FIFO中被抛弃,释放空间以便写更 多的数据。

7、当使用FIFO的进程退出后,FIFO文件将继续保存在文件系统中以便以后使用。

8、FIFO有名字,不相关的进程可以通过打开命名管道进行通信。

1.2 有名管道的创建 

查看系统man手册查看无名管道原型:

man 3 mkfifo

二.有命管道(FIFO)_第1张图片

方法1:用过shell命令mkfifo创建有名管道 :mkfifo[空格]文件名。

1 方法2:使用函数mkfifo 2

#include
#include

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

功能:创建一个有名管道,产生一个本地文件系统可见的文件pathname。

参数: pathname:有名管道创建后生成的文件,可以带路径。

 mode:管道文件的权限,一般通过八进制数设置即可,例如0664。

返回值:成功:0  失败:‐1

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char const *argv[])
{
    //通过mkfifo函数创建有名管道
    if(mkfifo("fifo_file", 0664) == -1)
    {
        //printf("errno = %d\n", errno);
        //如果管道文件已经存在,不需要报错退出,直接使用即可,所以需要在错误输出之前把
        //因为文件存在的错误排除
        if(errno != EEXIST)//EEXIST文件已存在错误(17)。
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }

    return 0;
}

1.3 有名管道的基本读写操作

      由于有名管道在本地创建了一个管道文件,所以系统调用的IO函数基本都可以对有名管道 进行操作, 但是不能使用lseek修改管道文件的偏移量。

       注意:有名管道创建的本地的文件只是起到标识作用,真正有名管道实现进程间通信还是在 内核空间开辟内存,所以本地产生的文件只是一个标识,没有其他作用,对本地管道文件的 操作实质就是对内核空间的操作。

        操作 FIFO 文件时的特点 系统调用的 I/O 函数都可以作用于 FIFO,如 open、close、read、write 等。 打开 FIFO 时,非阻塞标志(O_NONBLOCK)产生下列影响:

特点一: 不指定 O_NONBLOCK(即 open 没有位或 O_NONBLOCK)

1、open 以只读方式打开 FIFO 时,要阻塞到某个进程为写而打开此 FIFO

2、open 以只写方式打开 FIFO 时,要阻塞到某个进程为读而打开此 FIFO。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define FIFONAME "fifo_file"

int main(int argc, char const *argv[])
{
    //通过mkfifo函数创建有名管道
    if(mkfifo(FIFONAME, 0664) == -1)
    {
        if(errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }

    //对有名管道进行操作
    //管道后写入的数据会保存在之前写入数据的后面,不会替换
    //如果管道中没有数据了,读操作会阻塞

    //通过open函数打开管道文件并得到文件描述符
    int fd;
    fd = open(FIFONAME, O_RDWR);
    if(fd == -1)
    {
        perror("fail to open");
        exit(1);
    }

    //通过write函数向管道中写入数据
    if(write(fd, "hello world", strlen("hello world")) == -1)
    {
        perror("fail to write");
        exit(1);
    }

    write(fd, "nihao beijing", strlen("nihao beijing"));

    //通过read函数读取管道中的数据
    char buf[32] = "";
    if(read(fd, buf, sizeof(buf)) == -1)
    {
        perror("fail to read");
        exit(1);
    }
    printf("buf = [%s]\n", buf);

    if(read(fd, buf, sizeof(buf)) == -1)
    {
        perror("fail to read");
        exit(1);
    }
    printf("buf = [%s]\n", buf);

    //使用close函数关闭文件描述符
    close(fd);

    return 0;
}

1.4有名管道实现进程间通信

由于有名管道在本地创建了一个管道文件,所以不相关的进程间也可以实现通信

为了实现两个进程都可以收发数据,所以需要创建两个有名管道!

二.有命管道(FIFO)_第2张图片

send.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char const *argv[])
{
    //如果没有创建有名管道,则创建有名管道
    //为了实现两个进程都可以收发数据,所以需要创建两个有名管道
    if(mkfifo("myfifo1", 0664) == -1)
    {
        if(errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }

    if(mkfifo("myfifo2", 0664) == -1)
    {
        if(errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }

    //打开两个有名管道并得到文件描述符
    int fd_w, fd_r;
    if((fd_w = open("myfifo1", O_WRONLY)) == -1)
    {
        perror("fail to open");
        exit(1);
    }

    if((fd_r = open("myfifo2", O_RDONLY)) == -1)
    {
        perror("fail to open");
        exit(1);
    }

    char buf[128] = "";
    ssize_t bytes;
    while(1)
    {
        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf) - 1] = '\0';

        //send进程负责将数据写入myfifo1,接着从myfifo2中读取数据
        if((bytes = write(fd_w, buf, sizeof(buf))) == -1)
        {
            perror("fail to write");
            exit(1);
        }

        if((bytes = read(fd_r, buf, sizeof(buf))) == -1)
        {
            perror("fail to read");
            exit(1);
        }

        printf("from recv: %s\n", buf);
    }

    return 0;
}

recv.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char const *argv[])
{
    if(mkfifo("myfifo1", 0664) == -1)
    {
        if(errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }

    if(mkfifo("myfifo2", 0664) == -1)
    {
        if(errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }

    int fd_w, fd_r;
    
    if((fd_r = open("myfifo1", O_RDONLY)) == -1)
    {
        perror("fail to open");
        exit(1);
    }
    
    if((fd_w = open("myfifo2", O_WRONLY)) == -1)
    {
        perror("fail to open");
        exit(1);
    }

    char buf[128] = "";
    ssize_t bytes;
    while(1)
    {
        if((bytes = read(fd_r, buf, sizeof(buf))) == -1)
        {
            perror("fail to read");
            exit(1);
        }

        printf("from send: %s\n", buf);

        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf) - 1] = '\0';

        write(fd_w, buf, sizeof(buf));
    }

    return 0;
}

1.5有名管道的读写规律(阻塞)

1.5.1 读写端都存在,只读不写

read.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char const *argv[])
{
    if(mkfifo("myfifo", 0664) == -1)
    {
        if(errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }

    //读写端都存在,只读不写
    //如果原本管道中有数据,则正常读取
    //如果管道中没有数据,则read函数会阻塞等待
    
    int fd;
    if((fd = open("myfifo", O_RDWR)) == -1)
    {
        perror("fail to open");
        exit(1);
    }

    write(fd, "hello world", 11);

    char buf[128] = "";
    read(fd, buf, sizeof(buf));
    printf("buf = %s\n", buf);

    read(fd, buf, sizeof(buf));
    printf("buf = %s\n", buf);

    return 0;
}

1.5.2 读写端都存在

,只写不读

write.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char const *argv[])
{
    if(mkfifo("myfifo", 0664) == -1)
    {
        if(errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }

    //读写端都存在,只写不读
    //当有名管道的缓冲区写满后,write函数会发生阻塞
    //默认有名管道的缓冲区为64K字节

    int fd;
    if((fd = open("myfifo", O_RDWR)) == -1)
    {
        perror("fail to open");
        exit(1);
    }

    int num = 0;
    while(1)
    {
        write(fd, "", 1024);
        num++;
        printf("num = %d\n", num);
    }

    return 0;
}

1.5.3 在一个进程中,只有读端,没有写端

onlyRead.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char const *argv[])
{
    if(mkfifo("myfifo", 0664) == -1)
    {
        if(errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }

    //在一个进程中,只有读端,没有写端
    //会在open函数的位置阻塞

    printf("***********************\n");

    int fd;
    if((fd = open("myfifo", O_RDONLY)) == -1)
    {
        perror("fail to open");
        exit(1);
    }

    printf("------------------------\n");

    char buf[128] = "";
    ssize_t bytes;
    while(1)
    {
        if((bytes = read(fd, buf, sizeof(buf))) == -1)
        {
            perror("fail to read");
            exit(1);
        }

        printf("bytes = %ld\n", bytes);
        printf("buf = %s\n", buf);
    }

    return 0;
}

1.5.4 在一个进程中,只有写端,没有读端

onlyWrite.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char const *argv[])
{
    if(mkfifo("myfifo", 0664) == -1)
    {
        if(errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }

    //在一个进程中,只有写端,没有读端
    //会在open函数的位置阻塞

    printf("*****************************\n");

    int fd;
    if((fd = open("myfifo", O_WRONLY)) == -1)
    {
        perror("fail to open");
        exit(1);
    }

    printf("-----------------------------\n");

    while(1)
    {
        write(fd, "hello world", 11);
        printf("666\n");

        sleep(1);
    }

    return 0;
}

1.5.5 一个进程只读,一个进程只写

将上面onlyRead.c和onlyWrite.c两个代码一起运行,保证有名管道读写端都存在

规律:

只要保证有名管道的读写端都存在,不管是几个进程,都不会再open这阻塞了。

如果一个进程只读,一个进程只写,都运行后,如果关闭写端,读端read会返回0。

如果一个进程只读,一个进程只写,都运行后,如果关闭读端,写端会立即产生 SIGPIPE信号,默认的处理方式是退出进程。

1.6 有名管道的读写规律(非阻塞)

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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

	if(mkfifo("myfifo", 0664) == -1)
    {
        if(errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }
#if 0
	//如果open标志位设置为非阻塞,并且以只读的方式打开管道文件
	//open函数和read函数都不会阻塞
    fd = open("myfifo", O_RDONLY | O_NONBLOCK);
	if(fd < 0)
	{
		perror("open fifo");
		exit(1);
	}

	while(1)
	{
		char recv[100];
		
		bzero(recv, sizeof(recv));
		read(fd, recv, sizeof(recv));
		printf("read from my_fifo buf=[%s]\n",recv);
		sleep(1);
	}
#endif

#if 1
	//如果open标志位设置为非阻塞,并且以只写的方式打开管道文件
	//open函数会直接报错
	//如果open设置为可读可写,那么跟阻塞是一样的效果
    char send[100] = "Hello I love you";
	
	fd = open("myfifo", O_WRONLY | O_NONBLOCK);
	//fd = open("myfifo", O_RDWR | O_NONBLOCK);
	if(fd < 0)
	{
		perror("open fifo");
		exit(1);
	}
	write(fd, send, strlen(send));
	
	char recv[100];
    read(fd, recv, sizeof(recv));
	printf("read from my_fifo buf=[%s]\n",recv);
#endif

	return 0;
}

一:不指定 O_NONBLOCK(即 open 没有位或 O_NONBLOCK)

1、open 以只读方式打开 FIFO 时,要阻塞到某个进程为写而打开此 FIFO。

2、open 以只写方式打开 FIFO 时,要阻塞到某个进程为读而打开此 FIFO。

二:指定 O_NONBLOCK(即 open 位或 O_NONBLOCK)

1、先以只读方式打开:如果没有进程已经为写而打开一个 FIFO, 只读 open 成功,并且 open 不阻塞。

2、先以只写方式打开:如果没有进程已经为读而打开一个 FIFO,只写 open 将出错返回-1。

3、read、write 读写命名管道中读数据时不阻塞。

4、通信过程中,读进程退出后,写进程向命名管道内写数据时,写进程也会(收到 SIGPIPE 信号)退出。

你可能感兴趣的:(linux)