当前位置: 首页 > news >正文

Linux下的系统编程——进程间的通信(九)

 一、进程间通信常用方式

IPC方式:

        Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。
 

        在进程间完成数据传递需要借助操作系统提供特殊的方法,如:文件、管道、信号、共享内存、消息队列、套接字、命名管道等。随着计算机的蓬勃发展,一些方法由于自身设计缺陷被淘汰或者弃用。现今常用的进程间通信方式有:


1.管道(使用最简单)

2.信号(开销最小)

3.共享映射区(无血缘关系)

4.本地套接字(最稳定)

二、管道

1.概念:

      

         管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:


(1).其本质是一个伪文件(实为内核缓冲区)

(2).由两个文件描述符引用,一个表示读端,一个表示写端,只能一次读取

(3).规定数据从管道的写端流入管道,从读端流出,单向流动


管道的原理:  管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现

管道的:

        1)数据不能进程自己写,自己读。·

        2)管道中数据不可反复读取。一旦读走,管道中不再存在。

        3)采用半双工通信方式,数据只能在单方向上流动

        4)只能在有公共祖先的进程间使用管道

常用的通信方式: 单工通信、半双工通信、全双工通信

创建管道文件:

(不占用磁盘空间)

*2.pipe函数:

函数功能:创建,并打开管道。

    int pipe(int fd[2]);

    参数:    

        fd[0]: 读端。

        fd[1]: 写端。

    返回值:

         成功: 0

         失败: -1 errno

管道通信:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>void sys_err(const char *str)
{perror(str);exit(1);
}int main(int argc, char *argv[])
{int ret,re;int fd[2];pid_t pid;char *str = "hello pipe\n";char buf[1024];ret = pipe(fd);if(ret == -1)sys_err("pipe error");pid = fork();if(pid > 0){            //父进程close(fd[0]);       //关闭读段write(fd[1],str,strlen(str));//写入数据sleep(1);close(fd[1]);            //关闭写段}else if(pid == 0){        //子进程close(fd[1]);          //关闭写段re = read(fd[0],buf,sizeof(buf)); //读取数据write(STDOUT_FILENO,buf,re);      //写到屏幕上close(fd[0]);        //关闭读段}return 0;}

*3.管道的读写行为:

读管道:
        1. 管道有数据,read返回实际读到的字节数。

        2. 管道无数据:    

                        1)无写端,read返回0 (类似读到文件尾)

                        2)有写端,read阻塞等待。

写管道:
        1. 无读端, 异常终止。 (SIGPIPE导致的)

        2. 有读端:    

                        1) 管道已满, 阻塞等待

                        2) 管道未满, 返回写出的字节个数。

 1)读管道,管道无数据(无写端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>void sys_err(const char *str)
{perror(str);exit(1);
}int main(int argc, char *argv[])
{int ret,re;int fd[2];pid_t pid;char *str = "hello pipe\n";char buf[1024];ret = pipe(fd);if(ret == -1)sys_err("pipe error");pid = fork();if(pid > 0){            //父进程close(fd[0]);       //关闭读段//	write(fd[1],str,strlen(str));//写入数据close(fd[1]);            //关闭写段}else if(pid == 0){        //子进程close(fd[1]);          //关闭写段re = read(fd[0],buf,sizeof(buf)); //读取数据printf("child read ret =%d\n",ret);write(STDOUT_FILENO,buf,re);      //写到屏幕上close(fd[0]);        //关闭读段}return 0;}

read返回0

4.父子间进程 :

        使用管道实现父子进程间通信,完成:ls | wc -l。假定父进程实现ls,子进程实现wc

ls | wc -l命令:

 实现流程:

(1)父进程创建管道 pipe()

(2)父进程创建子进程 fork()

(3)设置父进程执行ls命令,子进程执行wc命令 execlp()

(4)设置父子进程通过管道的单项流动(设置指向标准输出的指向管道dup2()

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>void sys_err(const char *str)
{perror(str);exit(1);
}int main(int argc,char *argv[])
{/***************dup2();fork();pipe();execlp();****************/int fd[2]; int ret;pid_t pid;//父进程创建管道ret = pipe(fd);if(ret == -1){sys_err("pipe error");}//父进程创建子进程 pid = fork();if(pid == -1){sys_err("fork error");}else if(pid > 0){close(fd[1]);                  //关闭写,设置单项流动dup2(fd[0],STDIN_FILENO);      //设置读管道信息execlp("wc","wc","-l",NULL);        //设置子进程wc命令sys_err("execlp wc error");}else if(pid == 0){close(fd[0]);                  //关闭读,设置单项流动dup2(fd[1],STDOUT_FILENO);     //设置写操作指向管道execlp("ls","ls",NULL);        //设置父进程执行ls命令sys_err("execlp ls error");}return 0;
}

 

 5.兄弟间进程通信:

使用管道实现兄弟进程间通信,完成:ls | wc -l。假定父进程实现ls,子进程实现wc

 实现流程:

(1)父进程创建管道 pipe()

(2)父进程创建俩个(兄弟)子进程 fork()

(3)设置兄进程执行ls命令,第进程执行wc命令 execlp() 

(4)设置兄弟进程通过管道的单项流动(设置指向标准输出的指向管道dup2()

(5)回收父进程残余文件  wait()

刚创建出的兄弟进程:

 设置兄弟进程通过管道的单项流动后

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>void sys_err(const char *str)
{perror(str);exit(1);
}int main(int argc,char *argv[])
{/***************dup2();fork();pipe();execlp();wait();****************/int fd[2]; int ret;int i;pid_t pid;//父进程创建管道ret = pipe(fd);if(ret == -1){sys_err("pipe error");}for(i = 0;i < 2;i++){       //表达式2 出口,仅限父进程使用pid = fork();if(pid == -1){sys_err("fork error");}if(pid == 0)           //子进程出口break;}if(i == 2){                //父进程 //不需要父进程所以需要关闭他的读写并且回收掉父进程close(fd[0]);          close(fd[1]);wait(NULL);wait(NULL);}else if(i == 0){          //兄进程close(fd[0]);dup2(fd[1],STDOUT_FILENO);execlp("ls","ls",NULL);    //兄进程执行ls命令sys_err("ececlp ls error");}else if(i == 1){		   //弟进程close(fd[1]);dup2(fd[0],STDIN_FILENO);execlp("wc","wc","-l",NULL);//弟进程执行wc命令sys_err("ececlp wc error");}return 0;
}

6.多个读写端操作管道

实现一个pipe有一个写端,多个读端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>void sys_err(const char *str)
{perror(str);exit(1);
}int main(int argc,char *argv[])
{/***************dup2();fork();pipe();execlp();****************/int fd[2],i,n; int ret;char buf[1024];pid_t pid;//父进程创建管道ret = pipe(fd);if(ret == -1){sys_err("pipe error");exit(1);}for(i = 0;i < 2;i++){pid = fork();if(pid == -1){sys_err("fork error");exit(1);}if(pid == 0)break;}if(i == 2){            //父进程close(fd[1]);       //父进程关闭写端,留读端读取数据sleep(1);n = read(fd[0],buf,1024);    //从管道中读取数据write(STDOUT_FILENO,buf,n); for(i == 0;i < 2;i++)        //两个儿子wait两次wait(NULL);}else if(i == 0){        //兄进程close(fd[0]);write(fd[1],"1.hello\n",strlen("1.hello\n"));}else if(i == 1){		 //弟进程close(fd[0]);write(fd[1],"2.world\n",strlen("2.world\n"));}return 0;
}

7.管道缓冲区大小:

可以使用 ulimIt -a 命令来查看当前系统中创建管道文件所对应的内核缓冲区大小。通常为:
        pipe size               ......(512 bytes,-p)  8 

也可以使用fpathconf函数,借助参数―选项来查看。使用该宏应引入头文件<unistd.h>
        long fpathconf(int fd, int name);成功:返回管道的大小―失败:-1,设置errno

.8.管道的优劣

优点:简单,相比信号,套接字实现进程间通信,简单很多。

缺点:

                1.只能单向通信,双向通信需建立两个管道。
                2.只能用父子、兄弟进程(有共同祖先)间通信。该问题后来使用fifo有名管道解决)

 三、FIFO:   

fifo管道:可以用于无血缘关系的进程间通信。

    命名管道:  mkfifo 

    无血缘关系进程间通信:

                        读端,open fifo O_RDONLY

                        写端,open fifo O_WRONLY

     

1.命名管道fifo的创建和原理:

使用命令:myfifo myfifo

 使用myfifo创建

#include<stdio.h>
#include<sys/stat.h>
#include<errno.h>
#include<pthread.h>
#include<stdlib.h>void sys_err(const char *str){perror(str);exit(1);
}int main(int argc,char *str)
{int ret = mkfifo("mytestfifo",0664);if(ret == -1)sys_err("mkfifo error");return 0;}

http://www.lryc.cn/news/156704.html

相关文章:

  • Qt QtableWidget、QtableView表格删除选中行、删除单行、删除多行
  • 【代码随想录day24】不同的二叉搜索树
  • 数学建模--Subplot绘图的Python实现
  • JMeter(三十九):selenium怪异的UI自动化测试组合
  • c++ 移动构造方法为什么要加noexcept
  • 鸿鹄工程项目管理系统 Spring Cloud+Spring Boot+前后端分离构建工程项目管理系统
  • 手把手教你搭建园林园艺小程序商城
  • Java Iterator(迭代器)
  • Logstash同步MySQL数据到ElasticSearch
  • 【C++】运算符重载的示例实现和应用
  • Kubernetes禁止调度
  • CocosCreator3.8研究笔记(七)CocosCreator 节点和组件的介绍
  • Ceph入门到精通-C++入门知识点
  • Ansible之playbook详解和应用实例
  • 经验萃取方法
  • 手写apply方法
  • Jenkins实现基础CD操作
  • 开源软件合集(Docker)
  • Ceph入门到精通-生产日志级别设置
  • 16-MyCat
  • RKNPU2通用API和零拷贝API
  • LeetCode 1123. 最深叶节点的最近公共祖先:DFS
  • 多线程应用——线程池
  • OPENCV+QT环境配置
  • Kafka3.0.0版本——文件清理策略
  • SRT参数说明
  • vue响应式原理
  • elk安装篇之 Kibana安装
  • MySQL 用户授权管理及白名单
  • pc-签字画板vue-esign的使用