进程间通信-匿名管道与命名管道

1.进程间通信
进程间通信是两个或者多个进程之间进行通信,行为如下:
数据传输:一个进程需要将它的数据发送给另一个进程。
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
2.匿名管道
匿名管道的读写特征:
①.如果读取慢,写入快,write调用阻塞,直到有进程读走数据
②.如果写入慢,读取快,read调用阻塞,直到有进程写入数据
③.管道写端对应的文件描述符被关闭,则read返回0。
④.管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,让write进程退出。

a. 管道的4种情况
①. 正常情况,如果管道没有数据了,读端必须等待,直到有数据为止(写端写入数据了)
②. 正常情况,如果管道被写满了,写端必须等待,直到有空间为止(读端读走数据)
③. 写端关闭,读端一直读取, 读端会读到read返回值为0, 表示读到文件结尾
④. 读端关闭,写端一直写入,OS会直接杀掉写端进程,通过想目标进程发送SIGPIPE(13)信号,终止目标进程
b. 管道的5种特性
①. 匿名管道,可以允许具有血缘关系的进程之间进行进程间通信,常用与父子,仅限于此
②. 匿名管道,默认给读写端要提供同步机制
③. 面向字节流的
④. 管道的生命周期是随进程
⑤. 管道是单向通信的,半双工通信的一种特殊情况

#include <iostream>
#include <cassert>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#define MAX 1024
using namespace std;

int main()
{
    // 第1步,建立管道
    int pipefd[2] = {0};
    int n = pipe(pipefd);
    assert(n == 0);
    (void)n; /
    cout << "pipefd[0]: " << pipefd[0] << ", pipefd[1]: " << pipefd[1] << endl;
    // 第2步,创建子进程
    pid_t id = fork();
    if (id < 0)
    {
        perror("fork");
        return 1;
    }
    // 子写,父读
    // 第3步,父子关闭不需要的fd,形成单向通信的管道
    if (id == 0)
    {
        close(pipefd[0]);
        // w - 只向管道写入,没有打印
        int cnt = 0;
        while(true)
        {
            char message[MAX];
            snprintf(message, sizeof(message), "hello father, I am child, pid: %d, cnt: %d", getpid(), cnt);
            cnt++;
            write(pipefd[1], message, strlen(message));
            sleep(1);
        }
        cout << "child close w piont" << endl;
        exit(0);
    }
    // 父进程
    close(pipefd[1]);
    // r
    char buffer[MAX];
    while(true)
    {
        // sleep(2000);
        ssize_t n = read(pipefd[0], buffer, sizeof(buffer)-1);
        if(n > 0)
        {
            buffer[n] = 0; // '\0', 当做字符串
            cout << getpid() << ", " << "child say: " << buffer << " to me!" << endl;
        }
        else if(n == 0)
        {
            cout << "child quit, me too !" << endl;
            break;
        }
        cout << "father return val(n): " << n << endl;
        sleep(1);

        break;
    }
    cout << "read point close"<< endl;
    close(pipefd[0]);
    sleep(5);
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if (rid == id)
    {
        cout << "wait success, child exit sig: " << (status&0x7F) << endl;
    }
    return 0;
}

关于代码其中的函数
①.int pipe(int fd[2])
参数:
fd:文件描述符数组,
int pipefd[2]是一个输出型参数,是一个数组,该数组只有两个元素,下标分别为0和1。
下标为0的元素表示的是管道读端的文件描述符fd。
下标为1的元素表示的是管道写端的文件描述符fd。
返回值:成功返回0,失败返回错误代码

创建管道后返回的两个fd值,果然是3和4,因为0,1,2分别被stdin,stdout,stderr占用。
②.snprintf函数
int snprintf(char *str, size_t size, const char *format, …);
str:指向用于存储格式化后的字符串的字符数组的指针。
size:输出字符数组 str 的最大长度(包括终止空字符 \0),用于防止缓冲区溢出。
format:格式化字符串,包含了要插入到输出字符数组中的文本和格式说明符。
…:可变参数列表,用于提供格式化字符串中各个格式说明符所对应的值。
snprintf 函数根据指定的格式字符串 format,将格式化后的字符串写入到 str 指向的字符数组中,直到遇到空字符 \0 或达到最大长度 size - 1(最后一个字符保留给空字符 \0)。如果格式化后的字符串长度超过了指定的最大长度 size - 1,则会截断超出部分,并返回实际需要写入的字符数(不包括终止空字符 \0);否则,返回格式化后的字符串的长度(不包括终止空字符 \0)。
③.write函数 read函数
write 函数:
函数原型:ssize_t write(int fd, const void *buf, size_t count);
参数说明:
fd:文件描述符,表示要写入数据的文件或者其他类型的 I/O 设备。
buf:指向要写入数据的缓冲区的指针。
count:要写入的字节数。
返回值:如果成功,返回写入的字节数,如果出错,则返回 -1,并设置全局变量 errno 来指示错误的类型。
函数功能:write 函数将 buf 指向的数据写入到文件描述符 fd 指向的文件或者其他类型的 I/O 设备中。

read 函数:
函数原型:ssize_t read(int fd, void *buf, size_t count);
参数说明:
fd:文件描述符,表示要读取数据的文件或者其他类型的 I/O 设备。
buf:指向存放读取数据的缓冲区的指针。
count:要读取的最大字节数。
返回值:如果成功,返回读取的字节数,如果已到达文件末尾,则返回 0,如果出错,则返回 -1,并设置全局变量 errno 来指示错误的类型。
函数功能:read 函数从文件描述符 fd 指向的文件或者其他类型的 I/O 设备中读取数据,并将数据存储到 buf 指向的缓冲区中
④.waitpid函数

3.命名管道
可以在shell中通过命令的方式创建管道文件,两个进程直接去使用它。也可以像文件一样,在进程中创建管道文件,此时就需要用到系统调用。
int mkfifo(const char *pathname, mode_t mode);
pathname:要创建的命名管道的路径名。可以是相对路径或绝对路径。
mode:创建的文件权限,通常使用八进制数表示,如 0666。
返回值:
若成功,返回 0;
若失败,返回 -1,并设置 errno 来指示错误类型。
如果成功,mkfifo 函数将创建一个新的命名管道文件,并根据指定的权限设置其访问权限。如果文件已存在,则会报错并返回 -1。

//servePipe.c
#define ERR_EXIT(m) \
	do{\
	    perror(m);\
	    exit(EXIT_FAILURE);\
}while(0);
//这个函数可以显示错误 

int main(){
	umask(0);
	if(mkfifo("mypipe",0664)<0){
		ERR_EXIT("mkfifo");
	}  //如果失败返回-1 
	int rfd=open("mypipe",O_RDONLY);
	if(rfd<0){
		ERR_EXIT("open");
	}
	char buf[1024];
	while(1){
	  buf[0]=0;
	  printf("please wait..\n");
	  ssize_t s=read(rfd,buf,sizeof(buf)-1);
	  if(s>0){
	      buf[s-1]=0;
		  printf("client say# %s\n",buf);	
	  }	
	  //read
	  else if(s==0){
	  	printf("client quit,exit now!\n");
	  	exit(EXIT_SUCCESS);
	  }
	  //read函数返回0则表示已经读到文件结尾 
	  else{
	  ERR_EXIT("read"); 
	} 
}

//clientPipe.c
int main(){
	int wfd=open("mypipe",O_WRONLY);
	if(wfd<0){
		ERR_EXIT("open");
	}
	char buf[1024];
	while(1){
	  buf[0]=0;
	  printf("Please Enter# ");
	  fflush(stdout);
	  ssize_t s=read(0,buf,sizeof(buf)-1);
	  if(s>0){
	  	 buf[s]=0;
	  	 write(wfd,buf,strlen(buf));
	  }	
	  else if(s<=0){
	  	 ERR_EXIT("read");
	  }
	}
	close(wfd);
	
	return 0;
}

命名管道的读写特征:
①.当读端要打开管道,而写端没有打开时,会在读端的open处阻塞。
②.当写端要打开管道,而读端没有打开时,会在写端的open处阻塞。
除了读写时的特征,命名管道本身也有特征:命名管道的生命周期随内核。
我们可以看到,管道文件是可以直接在磁盘上存在的,和进程无关,这一点和命名管道不一样,其他的特征都一样。

匿名管道和命名管道的区别:
匿名管道由pipe函数创建并打开。
命名管道由mkfifo函数创建,打开用open,删除用unlink函数。
FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义

  • 29
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值