【Linux--管道】

2023-12-13 04:41:28

一、管道

  • 管道是Unix中最古老的进程间通信的形式。
  • 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
    例如我们通过who | wc -l命令可以看到who进程将数据传递给了wc -l进程,两个进程通过管道完成了简单的通信。
    在这里插入图片描述

二、匿名管道

2.1匿名管道的原理

当我们的一个父进程分别以读和写的方式打开一个同一管道文件,这时我们的进程中就有了两个文件描述符指向这个管道文件。
在这里插入图片描述
然后让父进程创建子进程。这时父进程与子进程的内核数据结构几乎相同,此时子进程也有两个文件描述符指向管道文件而且这个管道文件与父进程指向的管道文件是同一个管道文件。
这个时候我们的父子进程就可以一个向管道文件写入数据一个从管道中读取数据,这样两个进程就可以完成通信了。
在这里插入图片描述
最后一个步骤就是关闭不需要的文件描述符了,如果父进程进行写入,就关闭父进程的读端,关闭子进程的写端。反之则同理

2.2pipe函数

int pipe(int pipefd[2]);
  • pipe函数参数是一个输出型参数,数组pipefd由两个分别指向管道读端和写端的文件描述符组成
  • 返回值:返回值为0,代表打开管道文件成功,返回值为-1,代表打开管道文件失败了

2.3匿名管道的使用及理解

2.3.1匿名管道的使用

若想实现父子进程或者具有血缘关系进程间通信,需将pipe()和fork()搭配使用
代码实现:

#include<iostream>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
using namespace std;
void Write(int wfd)
{
    string s="hello child ,i am your father";
    char buf[1024];
    int cnt=0;
    while(true)
    {
        buf[0]=0;
        snprintf(buf,sizeof(buf),"%s\n",s.c_str());
        //cout<<buf<<endl;
        write(wfd,buf,strlen(buf));
        sleep(5);
    }

}
void Read(int rfd)
{
    char buf[1024];
    while(true)
    {
        buf[0]=0;
        ssize_t n=read(rfd,buf,sizeof(buf));
        if(n>0)
        {
            buf[n]=0;
            cout<<"child get a message:"<<buf<<endl;
        }
    }
}
int main()
{
    int pipefd[2]={0};
    int n=pipe(pipefd);
    if(n<0) return 1;
    //cout<<"pipefd[0]:"<<pipefd[0]<<"pipefd[1]:"<<pipefd[1]<<endl;
    pid_t pid=fork();
    if(pid==0)
    {
        //子进程  读
        close(pipefd[1]);
        Read(pipefd[0]);

    }
    else
    {
        //父进程 写 i am father 
        close(pipefd[0]);
        Write(pipefd[1]);
    }

    pid_t rid=waitpid(pid,nullptr,0);
    if(rid<0)   return 3;
    return 0;
}

2.3.2匿名管道的特点

根据现有的知识以及代码可以总结出一些管道的特点:
1.管道只能单向通信,管道的两侧只能一个进行写入、一个读取,一旦分工确定就不能在进行更改。
2.管道的本质是文件,文件描述符fd的生命周期是跟随进程的,进程退出时,文件描述符fd也会消失,所以管道的生命周期是跟随进程的。
3.用匿名管道进行通信,这个方式只能够让有血缘关系的进程进行通信。因为没有血缘关系的进程并不知道应该打开哪一个管道文件,有血缘关系的进程可以通过继承来打开同一个管道文件。

2.3.3匿名管道的4种情况

1.读写端正常,管道为空,读端阻塞
在这里插入图片描述
2.读写端正常,管道写满,写端阻塞
在这里插入图片描述
运行结果说明了,管道写满了就不能够再进行写入了,而且也说明了管道的大小是64KB。
通过1、2这两种特殊情况,我们能够总结出管道的第五个特点:管道有一定的协同能力,让读端和写端能够按照一定的步骤进行通信(自带同步机制)
3.读端正常,突然写端关闭,读端读到0,表示读到管道结尾,不会阻塞

#include<iostream>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
using namespace std;
void Write(int wfd)
{
    string s="hello father ,i am your child";
    char buf[1024];
    int cnt=0;
    while(true)
    {
        if(cnt++==5)    break;
        char c='x';
        buf[0]=0;
        snprintf(buf,sizeof(buf),"%s\n",s.c_str());
        write(wfd,buf,strlen(buf));
        //write(wfd,&c,1);
        //cout<<cnt++<<endl;
        sleep(1);
    }

}
void Read(int rfd)
{
   
    char buf[1024];
    int cnt=5;
    while(true)
    {
        buf[0]=0;
        ssize_t n=read(rfd,buf,sizeof(buf));

        buf[n]=0;
        cout<<"child get a message:"<<buf<<endl;
        cout<<n<<endl;
        if(n==0)    break;
    }
}
int main()
{
    int pipefd[2]={0};
    int n=pipe(pipefd);
    if(n<0) return 1;
    //cout<<"pipefd[0]:"<<pipefd[0]<<"pipefd[1]:"<<pipefd[1]<<endl;
    pid_t pid=fork();
    if(pid==0)
    {
        //子进程  写
        close(pipefd[0]);
        Write(pipefd[1]);

        close(pipefd[1]);
        exit(0);

    }
    else
    {
        //父进程 读 
        close(pipefd[1]);
        Read(pipefd[0]);
    }

    pid_t rid=waitpid(pid,nullptr,0);
    if(rid<0)   return 3;
    return 0;
}

在这里插入图片描述

4.写端正常,读端关闭,操作系统就会杀掉正在写入的进程(操作系统是不会做低效,浪费的工作)

  • List item

在这里插入图片描述

三、命名管道

3.1命名管道的原理

匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道,命名管道是一种特殊类型的文件。
注意:

  • 普通文件可以做到通信,但无法解决一些安全问题,并会发生落盘导致效率极低
  • 命名管道和匿名管道一样,都是内存文件,只不过命名管道在磁盘有一个简单的映像,但这个映像的大小永远为0,因为命名管道和匿名管道都不会将通信数据刷新到磁盘当中

3.2mkfifo

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

pathname:

  • 若pathname以路径的方式给出,则将命名管道文件创建在pathname路径下
  • 若pathname以文件名的方式给出,则将命名管道文件默认创建在当前路径下
    mode:
  • 表示创建命名管道文件的默认权限,受umask(0002)影响。实际创建出来文件的权限为:mode&(~umask)
    返回值:
  • 命名管道创建成功,返回0
  • 命名管道创建失败,返回-1
    创建代码:
#include <stdio.h>                                                                                                                                                               
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define FIFL_NAME "myfifo"
int main()
{
    umask(0);
    if(mkfifo(FIFL_NAME,0666)<0)
    {
        perror("mkfifi fail\n");
        return 1;
    }
    return 0;

}

在这里插入图片描述

3.2利用命名管道实现serve&&client通信

makefile

.PHONY:all
all:server client
client:client.cc
	g++ -o $@ $^ -std=c++11
server:server.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -f client server

command.hpp

#include <iostream>
using namespace std;
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

#define FIFL_NAME "myfifo"
int create_name_pipe()
{
    umask(0);
    if(mkfifo(FIFL_NAME,0666)<0)
    {
        perror("myfifo fail\n");
        return 1;
    }
    return 0;
}

client.cc

#include"command.hpp"
#include<string>

int main()
{
    //1.打开文件
    int fd=open("./myfifo",O_WRONLY);
    if(fd<0)
    {
        cout<<"open file fail"<<endl;
        return -2;
    }

    //2.开始发送消息
    string message;
    while(true)
    {
        cout<<"client# "<<endl;
        getline(cin,message);
        if(message=="quit")   break;

        ssize_t n=write(fd,message.c_str(),message.size());
        if(n<0)
        {
            cout<<"write fail"<<endl;
        }

    }

    //3.关闭文件描述符
    close(fd);
    return 0;

}

server.cc

#include"command.hpp"

int main()
{
    //1.创建一个有命管道
    int ret=create_name_pipe();
    if(ret==1)  return -1;
    cout<<"create fifo success"<<endl;

    //2.打开管道的读端
    int fd=open("./myfifo",O_RDONLY);
    if(fd<0)
    {
        cout<<"open file fail"<<endl;
        return -2;
    }
    cout<<"open fifo success, begin ipc"<<endl;

    //3.等待客户端消息
    char buffer[1024]={0};
    while(true)
    {
        ssize_t n=read(fd,buffer,sizeof(buffer));
        if(n>0)
        {
            buffer[n]='\0';
            cout<<"server# "<<buffer<<endl;
        }
        else if(n==0)
        {
            cout<<"客户端退出了,我也退出"<<endl;
            break;
        }
        else
        {
            cout<<"read fail"<<endl;
            return -3;

        }
    }

    //4.关闭文件描述符
    close(fd);

    //5.删除管道文件
    unlink("./myfifo");
    return 0;
}

代码结果:
在这里插入图片描述

文章来源:https://blog.csdn.net/qq_69544758/article/details/134928531
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。