Linux系统中的管道通信

目录

管道如何通信

管道的访问控制机制:

匿名管道

匿名管道数据传输的原理

如何使用(代码案例)

用C/C++代码编译实现父子进程间通信案例 :

思路

实现

命名管道

为什么要有命名管道

回归进程间通信的本质

匿名管道的短板

命名管道的原理

如何创建命名管道

如何使用(代码案例)

用C/C++代码编译实现 client进程 和 server进程的通信案例 :

思路 

实现


管道如何通信

管道,是Linux下常用的进程间通信手段,具体的通信方法是 父进程打开管道文件,被子进程继承,通过文件描述符fd,父子进程能看到相同的内存级文件,而后 一个进程往管道中写,另一个进程读,完成信息的传送。

而打开管道文件与普通文件不同的是,前者具有访问控制,即同步和互斥机制,也就是具有原子性:

管道的访问控制机制:

1.从管道中读取数据时,如果管道为空,或正在被写入,执行读取的进程会阻塞等待

2.往管道中写入数据时,如果管道满了,或正在被读取,执行写入的进程会阻塞等待

3.当写端关闭后,读端读取到EOF 

以上三点机制,使得管道能像文件一样操作的同时,避同时免了使用普通文件进程间通信的 极度的 不安全性。并且,数据在管道文件中的读写是彻底的内存级别的,即不与磁盘交互,读和写皆是在内存中,效率也有一定的保证。

注意管道文件只是单向的,也就是一个进程读,一个进程写的模式,原生规定好的,使用需要符合规范

匿名管道

匿名管道数据传输的原理

匿名管道的通信特点是,通过系统调用pipe(int fd[2]),直接在内核中创建一个内存级文件,并自动被调用它的函数分别以读和写的方式打开两次,获得两个fd。存放在输出型参数fd数组中,fd[0]代表读端fd,fd[1]代表写端fd。两个参与通信的进程能分别通过readwrite这样的系统调用接口,以文件的方式对管道进行读写操作,实现数据的传递。

pipe在内存中创建匿名管道文件

如何使用(代码案例)

用C/C++代码编译实现父子进程间通信案例 :

1.子进程 进程从标准输入按行读取信息

2.通过管道传入到父进程进程中

3.由父进程进程读取并输出到标准输出流。

思路

1.调用pipe(int*fd[2])创建匿名通信管道

2.fork()创建子进程,子进程会继承fd[2]。

子进程:

  (1)关闭读端:close(fd[0])

注:管道只支持单向通信,这里要求子写父读,你也可以实现为父写子读.

  (2)从标准输入(键盘)读取按行读入

  (3)将从键盘读入的内容写入管道

父进程

(1)关闭写端 close(fd[1])

(2)从管道读取数据知道读到文件结尾

(3)等待子进程退出

实现

#include<string.h>
#include<stdlib.h>
#include<errno.h>
#include<assert.h>
#include<stdio.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<unistd.h>

#define buffer_size (128)
int main(){
    int fd[2]={0,0};
    int pipe_ret=pipe(fd);
    if(pipe_ret) return errno;//调用出错返回错误码
    pid_t child_pid=fork();
    if(child_pid==-1) return errno;
    if(child_pid==0){
        //child
        //关闭不需要的端口
        close(fd[0]);
        char child_buffer[buffer_size];
        while(1){
            //从键盘读入数据
            ssize_t read_size=read(0,child_buffer,buffer_size-1);
            //保证安全性
            assert(read_size<buffer_size);  
            child_buffer[read_size]=0;
            //结束条件
            if(strcmp(child_buffer,"_exit\n")== 0)
                break;
            //发送给父进程
            write(fd[1],child_buffer,read_size+1);
        }
        close(fd[1]);
        exit(0);
    }
    else{
        //father
        close(fd[1]);
        char father_buffer[buffer_size];       
        while(1){
            //从管道读取数据
            ssize_t read_size=read(fd[0],father_buffer,buffer_size-1);
            //判断是否为文件结尾(子进程关闭写端)
            if(read_size==0)
                break;
            //保证安全性
            assert(read_size<=buffer_size);
            //输出内容  
            printf("父进程收到:%s",father_buffer);
        }
        close(fd[0]);
        //回收子进程
        pid_t wait_ret=waitpid(child_pid,(int*)0,0);
        if(wait_ret==-1) return errno;       
    }

    return 0;
}

运行效果:

  

命名管道

为什么要有命名管道

回归进程间通信的本质

首先明确 进程间通信的本质是 "让不同的进程,访问到同一份资源"

匿名管道的短板

匿名管道如何使不同进程访问到同一份资源?

父进程分别以读和写两次打开管道文件,而后创建子进程继承文件表述符父子进程能够访问到同一份资源此时能够实现一个进程写一个进程读,进而就具备了通信间通信的客观条件。

但是这样通过继承管道文件的文件描述符fd,来访问到同一份资源的方式有一个明显的缺陷,那就匿名管道只能用于有血缘关系的进程间通信

命名管道则解决了这个问题,能让没有血缘关系的进程也能访问到同一个管道文件

命名管道的原理

在磁盘上创建一个命名管道文件,利用文件名来表示管道的唯一性,只要打开相同的文件名,就能访问到相同的管道文件两个进程通过open分别以读和写打开文件,利用write和read就能完成进程间通信,也不需要血缘关系。

需要注意的是,文件名只是用来标识唯一性,数据的传输更匿名管道一样,完全是内存级的

如何创建命名管道

方法一:系统指令 mkfifo

方法二:系统调用 mkfifo(const char* 文件名,mode_t 文件权限)

成功返回0,失败返回-1(包括文件已经存在的情况),并设置好退出码。

如何使用(代码案例)

用C/C++代码编译实现 client进程 和 server进程的通信案例 :

1.client 进程从标准输入按行读取信息

2.通过管道传入到server进程中

3.由server进程读取并输出到标准输出流。

思路 

1.创建 client.c server.c myfifo.hpp 在头文件中定义命名管道的路径

2.client 和 server 都先调用mkfifo。若返回-1根据errno判断是否存在。若errno不为-1则终止进程返回退出码

3.client 调用open 以只写的方式打开,server以只写的方式打开管道文件

4.client调用write server调用read 完成通信

实现

头文件: myfifo.hpp

#pragma once
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/fcntl.h>
#include<errno.h>
#include<stdio.h>
//文件路径 隐藏文件
#define fifo_path "./.myfifo"
#define fifo_mode 0666
//缓冲区大小
#define buffer_size 128

源文件:client.c server.c

client.c: 

#include"myfifo.hpp"
int main(){
    //创建命名管道
    int mkfifo_ret=mkfifo(fifo_path,fifo_mode);
    //判断是否存在
    if(mkfifo_ret==-1&&errno!=EEXIST)
        return errno;
    //打开管道 只写
    int myfifo_fd= open(fifo_path,O_WRONLY);
    char client_buffer[buffer_size];
    while(1){
        //从标准输入读取数据
        printf("请输入:> ");
        fflush(stdout);
        ssize_t read_ret=read(0,client_buffer,buffer_size-1);
        //保证安全性
        assert(read_ret<buffer_size);
        client_buffer[read_ret]=0;
        //像管道写入
        write(myfifo_fd,client_buffer,read_ret+1);
        if(strcmp(client_buffer,"_exit\n")==0)
            break;
    }
    //关闭写端 server会读取到EOF
    close (myfifo_fd);
    return 0;
}

server.c 

#include"myfifo.hpp"
int main(){
    //创建命名管道
    int mkfifo_ret=mkfifo(fifo_path,fifo_mode);
    //判断是否存在
    if(mkfifo_ret==-1&&errno!=EEXIST)
        return errno;
    //打开管道 只读
    int myfifo_fd= open(fifo_path,O_RDONLY);
    char server_buffer[buffer_size];
    //从管道接收数据
    while(1){
        ssize_t read_ret=read(myfifo_fd,server_buffer,buffer_size-1);
        if(read_ret==0)
            break;
        //保证安全性
        assert(read_ret<=buffer_size);
        //输出到标准输出
        printf("服务器收到:> %s",server_buffer);
    }
    close(myfifo_fd);
    return 0;
}

运行效果

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值