从零开始学习管道:进程通信的概念,特点和示例

在这里插入图片描述

📟作者主页:慢热的陕西人

🌴专栏链接:Linux

📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言

本博客主要内容通过进程通信的概念,引入管道,实操了管道的五种特性和四种场景,以及对应的管道的特点最后我们写了一个例子让我们对于管道,重定向等的只是更加的印象深刻

文章目录

    • 1.进程通信的介绍
      • 1.1进程通信目的
      • 1.2进程间通信发展
      • 1.3进程通信的分类
    • 2.管道
      • 2.1什么是管道
      • 2.2实操一下见见管道
      • 2.3管道的原理
      • 2.4直接编写样例代码
      • 2.5做实验,推导出管道
        • a.五种特性
        • b.四种场景:
      • 2.6管道的特点
      • 2.7添加一点设计,来完成一个基本的多进程控制代码

1.进程通信的介绍

1.1进程通信目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变

1.2进程间通信发展

  • 管道

  • System V进程间通信

    System V 是一种基于本地版的进程通信,它是不能跨网络的,因为它只能本主机的内部进行进程间的通信,所以这也是它为什么会现在被边缘化的原因。关于System V我们只需要了解一个共享内存即可.

  • POSIX进程间通信

    POSIX 是一种基于网络版的进程通信。

    System V 和 POSIX相当于是进程间的通信的两套标准。

1.3进程通信的分类

管道

  • 匿名管道pipe
  • 命名管道

System V IPC

  • System V 消息队列
  • System V 共享内存
  • System V 信号量

POSIX IPC

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

①首先进程是具有独立性的—无疑增加了通信的成本

②要让两个不同的进程通信,进行通信,前提条件是:先让两个进程,看到同一份“资源”。(进程的本质和前提)

③任何进程通信手段都需要遵循如下的原则解决进程间的通信问题:

我们在操作系统内创建一份公共的资源,例如一段缓冲区,它既不属于进程A,也不属于进程B,那么我们这一份资源既可以被进程A看到,也可以被进程B看到。所以我们可以把进程A产生的数据放到缓冲区中,然后进程B就可以从缓冲区中拿到这部分数据从而完成了进程间的通信!

综上:

​ a.想办法,先让不同的进程,看到同一份资源。

​ b.让一方写入,一方读取,完成通信过程,至于,通信目的与后续工作需要结合具体场景。

2.管道

2.1什么是管道

  • 管道是Unix中最古老的进程间通信的形式
  • 我们把从一个进程连接到另一个进程的一个数据流成为一个“管道”

2.2实操一下见见管道

who命令:查看当前Linux系统中有用户登录信息

image-20231123212353868

who | wc -l其中的wc -l表示统计输出结果的行数然后输出,那么整体的运行结果就表示的是当前Linux系统中有多少个用户登录

image-20231123212546537

再看一个例子

 //其中的&代表让当前的命令在后台执行[mi@lavm-5wklnbmaja lesson12]$ sleep 10000 | sleep 20000 | sleep 30000 &
[1] 24952
//我们可以查看到三个sleep进程,那么这三个进程被称为我们的兄弟进程,父进程都是我们的bash
[mi@lavm-5wklnbmaja lesson12]$ ps axj | head -1 && ps axj | grep sleepPPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
22692 24950 24950 22692 pts/0    24995 S     1000   0:00 sleep 10000
22692 24951 24950 22692 pts/0    24995 S     1000   0:00 sleep 20000
22692 24952 24950 22692 pts/0    24995 S     1000   0:00 sleep 30000
22692 24996 24995 22692 pts/0    24995 S+    1000   0:00 grep --color=auto sleep

那么当我们使用管道执行命令的时候他是帮我们直接创建了这几个进程:比如上面例子中的sleep 10000 | sleep 20000 | sleep 30000who | wc -l都是如此,那么它是如何实现进程之间的数据进行通信的?

以上为例,首先管道也是文件,所以我们对应的who进程以写的形式打开管道,并且将自己的标准输出重定向管道

对应的wc -l进程就以读的形式打开管道,并且将自己的标准输入重定向到管道。

image-20231123213537515

2.3管道的原理

管道就是一个操作系统提供的纯内存级文件!

①父进程曾经打开的文件是不需要复制给子进程的,原因是子进程拷贝了一份父进程的struct files_struct,其中包含了父进程文件的描述符对应的数组,子进程可以通过这个数组依旧指向父进程的原来打开的文件,而不需要再打开一份造成资源的浪费。

②所以创建子进程的时候,fork子进程,只会复制进程相关的数据结构对象,不会复制父进程曾经打开的文件对象!

现象:这就是为什么fork之后,父子进程都printf,cout,都会向同一个显示器终端打印数据的原因!

因此我们的子进程也可以看到父进程创建的管道文件!完成了进程间通信的前提,让不同的进程看到了同一份资源!

这种管道只支持单向通信!!

③确定数据流向,关闭不需要的fd

所以我们在子进程关闭对应的读端,和父进程对应的写端,所以我们就可以通过管道将子进程的数据流向父进程。就可以进行正常的进程间的通信!

那么管道为什么是单向的?

原因是管道是基于文件进行通信的,文件读写缓冲区是分开的,当这种通信技术被发明出来的时候,我们发现进程的通信只能是单向的。首先作为父进程它打开管道是需要以读写方式打开,创建子进程之后才能关闭对应的读或者写,要不然子进程继承不到对应的读写方式打开文件,就不能进行一个写一个读了!

管道原理图

2.4直接编写样例代码

这里我们需要用到pipe接口:创建管道文件

其中它的参数int pipefd[2]被称为输出型参数,pipe接口将对应的读和写文件描述符写到这个数组里。

成功返回0,否则返回-1.

image-20231124150856768

#include<iostream>
#include<unistd.h>
#include<cerrno>
#include<assert.h>
#include<string.h>
#include<string>using namespace std;int main()
{//让不同的进程看到同一份资源!//任何一种任何一种进程间的通信中//,一定要 先 保证不同的进程之间看到同一份资源int pipefd[2] = { 0 };//1.创建管道int ret = pipe(pipefd);//创建失败if(ret < 0){cout << "pipe error," << errno << ": " << strerror(errno) << endl;return 1;}//打印对应的pipefdcout << "pipefd[0]: " << pipefd[0] << endl;  //读端cout << "pipefd[1]: " << pipefd[1] << endl;  //写端//2.创建子进程pid_t id = fork();assert(id != -1); //正常应该用判断//意料之外用if,意料之内用assertif(id == 0){//子进程//关闭对应的读端close(pipefd[0]);//4.开始通信---结合场景const string msg = "hello, 我是子进程";int cnt = 0;char buffer[1024];while(true){snprintf(buffer, sizeof(buffer), "%s, 计数器:%d, 我的PID: %d", msg.c_str(), cnt, getpid());write(pipefd[1], buffer, strlen(buffer));sleep(1);cnt++;}close(pipefd[1]);exit(0);}//父进程//3.关闭不需要的文件描述符,父读子写,关闭对应的写端close(pipefd[1]);//4.开始通信---结合场景char buffer[1024];while(true){//sleep(1);int n = read(pipefd[0], buffer, sizeof(buffer) - 1);if(n > 0){buffer[n] = '\0';cout << "我是父进程,我收到了子进程发给我的消息:" << buffer << endl;}}close(pipefd[0]);return 0;
}

image-20231124211108964

2.5做实验,推导出管道

a.五种特性

​ 1.单向通信

​ 2.管道的本质是文件,因为fd的声明周期随进程,管道的声明周期是随进程的

​ 3.管道通信,通常用来进行具有“血缘”关系的进程,进行进程间通信。常用于父子通信—pipe打开管道,并不清楚管道的名字,匿名管道。

​ 4.在管道通信中,写入的次数,和读取的次数,不是严格匹配的,读写次数的多少没有强相关—表现—字节流

​ 5. 具有一定的协同能力,让reader和writer能够按照一定的步骤进行通信—自带同步机制

b.四种场景:

1.如果我们read读取完毕了所有的管道数据,如果对方不发,我就只能等待

2.如果我们writer端管道写满了,我们还能写吗?不能!

我们每次写入四个字节,写入了65535次管道就满了!

snprintf(buffer, 4, "s");

image-20231124211820571

3.如果关闭了写端,读取完毕,在读,read就返回0,表明读到了文件结尾。

image-20231124191750420

4.写端一直写,读端关闭,会发生什么?没有意义!OS不会维护无意义,低效率,或者浪费资源的事情,OS会杀死一直在写入的进程!OS会通过信号来终止进程,13)SIGPIPE

我们收一条指令之后五秒之后关闭父进程对应的读端,发现子进程也退出了!

image-20231124214343431

2.6管道的特点

  • 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
  • 管道提供流式服务
  • 一般而言,进程退出,管道释放,所以管道的生命周期随进程
  • 一般而言,内核会对管道操作进行同步与互斥
  • 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

2.7添加一点设计,来完成一个基本的多进程控制代码

需求:父进程有多个子进程,父进程和这些子进程之间都有管道,父进程可以通过向子进程写入特定的消息,唤醒子进程,甚至让子进程执行某种任务。

首先搭建大体的框架:

#include<iostream>
#include<string>
#include<unistd.h>
#include<assert.h>
#include<vector>using namespace std;const int gnum = 5; //表示子进程数int main()
{//1.先进行构建控制结构,父进程写,子进程读for(int i = 0; i < gnum; ++i){//1.1创建管道int pipefd[2] = {0};int ret = pipe(pipefd);assert(ret == 0); //0正常 -1不正常(void)ret;//1.2创建进程pid_t id = fork();assert(id != -1);if(id == 0){//子进程//1.关闭对应的fd,也就是写close(pipefd[1]);close(pipefd[0]);exit(0);}//父进程//1.3关闭不要的fdclose(pipefd[0]);}return 0;
}

但是我们应该理解到,父进程有这么多的子进程,我们父进程在操作的时候,怎么分的清对应的子进程是哪一个?所以这时候我们应该将这些子进程组织起来,这就要利用到我们操作系统内部一直在践行的:先描述再组织

创建一个对应的结构体,然后用vector将其组织起来

//先描述
class EndPoint
{
public:pid_t _child; //子进程pidint _write_fd;//对应的文件描述符public://构造EndPoint(pid_t id, int fd) :_child(id), _write_fd(fd){}//析构~EndPoint(){}
};//在组织vector<EndPoint> end_points;

然后我们将以上的步骤封装成一个函数:

void creatProcesses(vector<EndPoint>& end_points)
{//1.先进行构建控制结构,父进程写,子进程读for(int i = 0; i < gnum; ++i){//1.1创建管道int pipefd[2] = {0};int ret = pipe(pipefd);assert(ret == 0); //0正常 -1不正常(void)ret;//1.2创建进程pid_t id = fork();assert(id != -1);if(id == 0){//子进程//1.3关闭不要的fdclose(pipefd[1]);//我们期望,所有的子进程读取“指令”的时候,都从标准输入读取//1.3.1所以我们进行输入重定向dup2(pipefd[0], 0);//1.3.2子进程开始等待获取命令WaitCommend();close(pipefd[0]);exit(0);}//父进程//1.3关闭不要的fdclose(pipefd[0]);//1.4将新的子进程和他的管道写端构建对象。end_points.push_back(EndPoint(id, pipefd[1]));}
}

主函数这样写:先让程序跑起来

int main()
{//在组织vector<EndPoint> end_points;creatProcesses(end_points);//2.那么我们这里就可以得到了五个子进程的id和对应的写端while(true){sleep(1);}return 0;
}

我们运行了之后用ps去监视查看进程确实生成了我们对应了五个子进程:

image-20231124231658337

设计 WaitCommend();函数

这个函数就是让子进程去一直去读取管道中信息,读取到之后执行对应的任务。

void WaitCommend()
{while (true){int command;int n = read(0, &command, sizeof(int));if (n == sizeof(int)) // 读取成功{t.Execute(command);}else if (n == 0) // 表示链接已经关闭{// 则不需要去读了break;}else // 读取错误{break;}}
}

主函数内部父进程调度发配任务:

主要分为三步:

  • 确定任务
  • 确定执行任务的子进程
  • 执行任务
int main()
{// 在组织vector<EndPoint> end_points;creatProcesses(end_points);// 2.那么我们这里就可以得到了五个子进程的id和对应的写端while (true){//1.确定任务int command = COMMAND_LOG;//2.确定执行任务的子进程int child = rand() % end_points.size();//3.执行任务write(end_points[child]._write_fd, &command, sizeof(command));sleep(1);}return 0;
}

运行结果:

image-20231125121913303

所有源码:

Task.hpp

#pragma once#include<iostream>
#include<vector>
#include<unistd.h>using namespace std;typedef void (*fun_t)(); //函数指针//任务对应的操作码,约定每一个command是四个字节
#define COMMAND_LOG 0
#define COMMAND_MYSQL 1
#define COMMAND_REQUEST 2//三个任务
void PrintLog()
{cout <<"进程的PID:" <<getpid()<< "打印日志任务,正在被执行" << endl;
}void InsertMySQL()
{cout << "执行数据库任务,正在被执行" << endl;
}void NetRequest()
{cout << "执行网络请求任务,正在被执行" << endl;
}class Task
{
public:Task(){funcs.push_back(PrintLog);funcs.push_back(InsertMySQL);funcs.push_back(NetRequest);}//任务执行函数void Execute(int command){if(command >= 0 && command < funcs.size()) funcs[command]();}~Task(){}public:vector<fun_t> funcs;};

myprocess.cc

#include <iostream>
#include <string>
#include <unistd.h>
#include <assert.h>
#include <vector>#include "Task.hpp"using namespace std;const int gnum = 5; // 表示子进程数// 定义对应的任务对象
Task t;// 先描述
class EndPoint
{
public:pid_t _child;  // 子进程pidint _write_fd; // 对应的文件描述符public:// 构造EndPoint(pid_t id, int fd): _child(id), _write_fd(fd){}// 析构~EndPoint(){}
};void WaitCommend()
{while (true){int command;int n = read(0, &command, sizeof(int));if (n == sizeof(int)) // 读取成功{t.Execute(command);}else if (n == 0) // 表示链接已经关闭{// 则不需要去读了break;}else // 读取错误{break;}}
}void creatProcesses(vector<EndPoint> &end_points)
{// 1.先进行构建控制结构,父进程写,子进程读for (int i = 0; i < gnum; ++i){// 1.1创建管道int pipefd[2] = {0};int ret = pipe(pipefd);assert(ret == 0); // 0正常 -1不正常(void)ret;// 1.2创建进程pid_t id = fork();assert(id != -1);if (id == 0){// 子进程// 1.3关闭不要的fdclose(pipefd[1]);// 我们期望,所有的子进程读取“指令”的时候,都从标准输入读取// 1.3.1所以我们进行输入重定向dup2(pipefd[0], 0);// 1.3.2子进程开始等待获取命令WaitCommend();close(pipefd[0]);exit(0);}// 父进程// 1.3关闭不要的fdclose(pipefd[0]);// 1.4将新的子进程和他的管道写端构建对象。end_points.push_back(EndPoint(id, pipefd[1]));}
}
int main()
{// 在组织vector<EndPoint> end_points;creatProcesses(end_points);// 2.那么我们这里就可以得到了五个子进程的id和对应的写端while (true){//1.确定任务int command = COMMAND_LOG;//2.确定执行任务的子进程int child = rand() % end_points.size();//3.执行任务write(end_points[child]._write_fd, &command, sizeof(command));sleep(1);}return 0;
}

到这本篇博客的内容就到此结束了。
如果觉得本篇博客内容对你有所帮助的话,可以点赞,收藏,顺便关注一下!
如果文章内容有错误,欢迎在评论区指正

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/169011.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【小技巧】复制一个模块到你的工程(学习阶段很实用)

问题描述&#xff1a; 当我们学习Springboot时&#xff0c;需要创建大量的模块&#xff0c;而这些模块的许多代码都是重复的&#xff0c;只有模块名等相关的信息不一样&#xff0c;现在就教你如何快速创建一个模块。 应用场景&#xff1a; ①进入项目文件夹&#xff1a; ②复…

如何利用4G路由器构建茶饮连锁店物联网

随着年轻消费群体的增长&#xff0c;加上移动互联网营销的助推&#xff0c;各类新式奶茶消费风靡大街小巷&#xff0c;也促进了品牌奶茶连锁店的快速扩张。 在店铺快速扩张的局势下&#xff0c;品牌总部对于各间连锁店的零售统计、营销规划、物流调配、卫生监测、安全管理等事务…

多功能PHP图床源码:Lsky Pro开源版v2.1 – 最新兰空图床

Lsky Pro是一款功能丰富的在线图片上传和管理工具&#xff0c;即兰空图床。它不仅可以作为个人云相册&#xff0c;还可以用作写作贴图库。 该程序的初始版本于2017年10月由ThinkPHP 5开发&#xff0c;经过多个版本的迭代&#xff0c;于2022年3月发布了全新的2.0版本。 Lsky Pro…

Spring Boot整合RabbitMQ

一、简介 在Spring项目中&#xff0c;可以使用Spring-Rabbit去操作RabbitMQ 尤其是在spring boot项目中只需要引入对应的amqp启动器依赖即可&#xff0c;方便的使用RabbitTemplate发送消息&#xff0c;使用注解接收消息。 一般在开发过程中&#xff1a; 生产者工程&#xf…

Go语言的学习笔记2——Go语言源文件的结构布局

用一个只有main函数的go文件来简单说一下Go语言的源文件结构布局&#xff0c;主要分为包名、引入的包和具体函数。下边是main.go示例代码&#xff1a; package mainimport "fmt"func main() { fmt.Println("hello, world") }package main就是表明这个文件…

(Matalb回归预测)GA-BP遗传算法优化BP神经网络的多维回归预测

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、部分代码&#xff1a; 四、分享本文全部代码数据说明手册&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于M…

命名空间、字符串、布尔类型、nullptr、类型推导

面向过程语言&#xff1a;C ——> 重视求解过程 面向对象语言&#xff1a;C ——> 重视求解的方法 面向对象的三大特征&#xff1a;封装、继承和多态 C 和 C 在语法上的区别 1、命名空间&#xff08;用于解决命名冲突问题&#xff09; 2、函数重载和运算符重载&#xf…

Unity阻止射线穿透UI的方法之一

if(UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject()) return; 作者&#xff1a;StormerZ https://www.bilibili.com/read/cv27797873/ 出处&#xff1a;bilibili

键入网址到网页显示,期间发生了什么?

文章目录 键入网址到网页显示&#xff0c;期间发生了什么&#xff1f;1. HTTP2. 真实地址查询 —— DNS3. 指南好帮手 —— 协议栈4. 可靠传输 —— TCP5. 远程定位 —— IP6. 两点传输 —— MAC7. 出口 —— 网卡8. 送别者 —— 交换机9. 出境大门 —— 路由器10. 互相扒皮 —…

Oracle SQL 注入上的 Django GIS 函数和聚合漏洞 (CVE-2020-9402)

漏洞描述 Django 于2020年3 月4日发布了一个安全更新&#xff0c;修复了 GIS 函数和聚合中的 SQL 注入漏洞。 参考链接&#xff1a; Django security releases issued: 3.0.4, 2.2.11, and 1.11.29 | Weblog | Django 该漏洞要求开发者使用 JSONField/HStoreField;此外&…

基于电力系统下的泛在电力物联网技术

安科瑞 华楠 摘 要&#xff1a;“三型两网”政策已成为我国电力行业发展的目标&#xff0c;构建泛在物联网是其发展目标的重要组成部分。无处不在的电力物联网建设将改变能源系统的技术、增长方式和运行方式&#xff0c;使电力行业转型升级&#xff0c;加快互联网能源企业建设…

小程序项目:node+vue基于微信小程序的校园盲盒小程序的设计与实现

项目介绍 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时…

Linux-Ubuntu环境下搭建SVN服务器

Linux-Ubuntu环境下搭建SVN服务器 一、背景二、前置工作2.1确定IP地址保持不变2.2关闭防火墙 三、安装SVN服务器四、修改SVN服务器版本库目录五、调整SVN配置5.1查看需要修改的配置文件5.2修改svnserve.conf文件5.3修改passwd文件&#xff0c;添加账号和密码&#xff08;window…

VRRP的交换机VRRP主备配置例子

拓朴如下&#xff1a; 主要配置如下&#xff1a; [S1] vlan batch 10 20 # interface Vlanif10ip address 10.1.1.1 255.255.255.0vrrp vrid 1 virtual-ip 10.1.1.254vrrp vrid 1 priority 200vrrp vrid 1 preempt-mode timer delay 20 # interface Vlanif20ip address 13.1.1…

89. 打家劫舍【动态规划】

题目 题解 class Solution:def rob(self, nums: List[int]) -> int:N len(nums)# 定义状态: dp[i]表示从第i间房子开始抢劫&#xff0c;最多能抢到的金额dp [0 for i in range(N)]for i in range(N-1, -1, -1):if i N-1:dp[i] nums[i]elif i N-2:dp[i] max(nums[i], …

Python是个什么鬼?朋友靠它拿了5个offer

闺蜜乐乐&#xff0c;外院科班出身&#xff0c;手持专八和CATTI证书&#xff0c;没想到找工作时却碰了钉子… 半夜12点&#xff0c;乐乐跟我开启了吐槽模式&#xff1a; 拿到offer的都是小公司的翻译活儿&#xff0c;只能糊个口。稍微好点的平台要求可就多了&#xff0c;不仅语…

apple macbook M系列芯片安装 openJDK17

文章目录 1. 查找openjdk版本2. 安装openjdk3. 多jdk之间的切换 在这里我们使用 brew 命令查找并安装。 1. 查找openjdk版本 执行&#xff1a;brew search openjdk&#xff0c;注意&#xff1a;执行命令后&#xff0c;如果得到的结果中没有红框内容&#xff0c;则需要更新一下…

JSP EL表达式之 empty

好 本文我们还是继续说EL表达式 我们来讲一个非空判断的好手 empty 我们直接编写代码如下 <% page contentType"text/html; charsetUTF-8" pageEncoding"UTF-8" %> <%request.setCharacterEncoding("UTF-8");%> <!DOCTYPE html&…

Error querying database. Cause: java.lang.reflect.InaccessibleObjectException:

最近开发过程中&#xff0c;居然碰到了一个Arrays.asList的错&#xff0c;怎么个场景呢&#xff1f;传参一个用固定符号拼接的字符串&#xff0c;需要转成集合然后再myBatis里in判断。然后就报错了。 一、代码层面 service层面&#xff1a; shortDetailUrlList Arrays.asLi…

基于java实现捕鱼达人游戏

开发工具eclipse,jdk1.8 文档截图&#xff1a; package com.qd.fish;import java.awt.Graphics; import java.awt.image.BufferedImage; import java.util.Random;public class Fish {//定义鱼的图片BufferedImage fishImage;//定义鱼的数组帧BufferedImage[] fishFrame;//…