Linux系统:进程间通信-匿名与命名管道
本节重点
- 匿名管道的概念与原理
- 匿名管道的创建
- 命名管道的概念与原理
- 命名管道的创建
- 两者的差异与联系
- 命名管道实现EchoServer
一、管道
管道(Pipe)是一种进程间通信(IPC, Inter-Process Communication)机制,用于在不同进程之间传递数据。它是一种抽象的数据通道,允许一个进程的输出直接作为另一个进程的输入,类似于现实中的管道将水流从一个地方输送到另一个地方。管道是操作系统提供的一种高效、轻量级的通信方式,广泛应用于命令行工具、父子进程协作等场景。
在Linux中我们知道,当一个指令运行起来时就是一个进程。who命令用来显示当前登录系统的用户信息,而wc是一个统计工具其中-l选项表示统计行数。当两个指令分别运行时就是两个进程,我们可以通过 | (管道)将who指令运行的结果传递给wc,此时wc就可以帮我们统计当前登录系统的用户个数。
who | wc -l
可以用下图来表示这种关系:
二、匿名管道
匿名管道(Anonymous Pipe)是一种进程间通信(IPC, Inter-Process Communication)机制,用于在具有亲缘关系的进程(如父子进程或兄弟进程)之间传递数据。它是一种单向的、半双工的通信通道,数据只能在一个方向上流动(若需双向通信,需创建两个管道)。匿名管道没有名字标识,因此仅适用于具有亲缘关系的进程,无法用于无关进程间的通信。
半双工与全双工:
半双工通信允许数据在两个方向上传输,但同一时刻只能单向传输。即通信双方可以轮流发送和接收数据,但不能同时进行。
全双工通信允许数据在两个方向上同时传输。即通信双方可以同时发送和接收数据。
特性 半双工 全双工 传输方向 同一时刻只能单向传输 双向同时传输 效率 较低(需切换方向) 较高(无需切换方向) 延迟 较高(方向切换可能引入延迟) 较低(无方向切换) 资源占用 较低(单方向占用资源) 较高(双方向占用资源) 应用场景 对讲机、老式电台、早期网络 电话、现代网络、USB接口
2.1 管道的创建
在Linux系统中我们可以系统调用pipe来创建管道文件,返回值是两个文件描述符分别表示文件的读端与写端。
函数原型:
#include <unistd.h>
int pipe(int fd[2]);
参数解析:
- fd 是一个长度为2的整数数组,用来存储管道文件的读端与写端的文件描述符
- fd[ 0 ]:管道的读端(用于读取数据)。
- fd[ 1 ]:管道的写端(用于写入数据)。
返回值:成功时返回 0,失败时返回 -1 并设置 errno。
数据传输的特点:
- 写操作:进程通过
write(pipefd[1], data, len)
将数据拷贝到内核缓冲区。若缓冲区满,写进程阻塞(默认阻塞模式)。 - 读操作:进程通过
read(pipefd[0], buffer, size)
从内核缓冲区拷贝数据到用户空间。若缓冲区空,读进程阻塞(默认阻塞模式)
管道的关闭:
- 读端先关闭:此时写入无意义,操作系统会向写进程发送SIGPIPE信号终止进程。
- 写段先关闭:此时读进程会读到EOF,用于指示文件或数据流已到达末尾。
代码示例:父子进程通过匿名管道实现进程间通信
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{int fd[2] = {0};int ret = pipe(fd);if (ret == -1){printf("pipe error!\n");}pid_t id = fork();if (id > 0){// 父进程读close(fd[1]);while (1){char read_buf[1024];int ret = read(fd[0], read_buf, sizeof(read_buf));if (ret > 0){read_buf[ret] = '0';printf("child say: %s", read_buf);}else if (ret == 0){// 写端退出:break;}else{printf("read error!\n");}}//退出前记得回收子进程waitpid(id,NULL,0);}else if (id == 0){// 子进程写close(fd[0]);int cnt=5;while (cnt){char buffer[1024];read(0, buffer, sizeof(buffer));int ret = write(fd[1], buffer, sizeof(buffer));cnt--;}exit(1);}else{// 子进程创建失败:printf("fork error!\n");}return 0;
}
代码说明:
当父进程调用pipe系统调用时,内核会创建一个管道文件(或开辟一段环形缓冲区)并暴露读端与写端。进程通过分配文件描述符来控制管道的读写操作,我们通过一个整数数组来记录两个文件描述符,通常下标为0的元素记录读端的文件描述符,下标为1的元素记录写端的文件描述符。
之后我们fork创建子进程时操作系统会拷贝一份文件描述符表继承给子进程,此时父子进程就可以看到同一个管道文件。之后父子进程根据读写工作的分配关闭不需要的文件描述符后就可以进行进程间通信了。
结束进程间通信时可以关闭写进程,此时读进程就会读到EOF也就是文件末尾,此时read的返回值为0,我们可以根据read的返回值判断写端是否关闭来结束读端。最后不要忘记父进程等待子进程的返回结果。
2.2 原理与特性
2.2.1 工作原理
匿名管道本质是内核维护的环形缓冲区(或文件),用于临时存储待传输的数据。缓冲区大小由系统决定(如Linux默认通常为64KB),数据以先进先出(FIFO)方式处理。与以往的缓冲区不同的是这类缓冲区由内核提供且不会刷盘(将数据刷新到磁盘)。
2.2.2 基础特性
单向通信
默认是半双工(单向),若需双向通信需创建两个管道(如pipe1
和pipe2
)。
示例:
- 进程A通过
pipe1
写,进程B通过pipe1
读(A→B)。 - 进程B通过
pipe2
写,进程A通过pipe2
读(B→A)。
阻塞行为
- 默认阻塞:读写操作在缓冲区满/空时阻塞。
- 非阻塞模式:通过
fcntl(fd, F_SETFL, O_NONBLOCK)
设置后,若缓冲区满/空,读写操作立即返回错误(EAGAIN
或EWOULDBLOCK
)。
亲缘关系限制
仅适用于父子进程或兄弟进程(通过fork()
派生)。无关进程无法直接访问匿名管道(因无名字标识符)。
数据拷贝开销
数据需在用户空间和内核空间之间拷贝两次(写→内核缓冲区,内核缓冲区→读),可能影响性能。
缓冲区容量限制
若写进程速率远高于读进程,缓冲区可能填满,导致写进程阻塞。
三、命名管道
命名管道(Named Pipe),也称为FIFO(First In First Out,先进先出),是一种进程间通信(IPC,Inter-Process Communication)机制,允许不相关的进程通过文件系统中的一个特殊文件(即命名管道文件)进行数据交换。与匿名管道(Anonymous Pipe)不同,命名管道具有一个明确的名称,因此可以在不同进程之间、甚至不同用户或不同主机之间(通过网络命名管道实现)进行通信。
3.1 命名管道的创建
在Linux系统中我们可以通过系统调用mkfifo来创建和设置命名管道,具体的函数原型以及参数解析如下:
函数原型:
#include <sys/types.h>
#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);
参数解析:
pathname
:指定命名管道的路径名,可以是相对路径或绝对路径,默认在当前路径。mode
:用于指定管道的权限,是一个八进制数,表示文件的权限掩码。
通常在设置管道文件权限之前,我们将文件掩码暂时设为0。
返回值与错误处理:
- 成功时返回 0,失败时返回 -1 并设置
errno
以指示错误原因。 - 常见错误码:
EACCES
:参数pathname
所指定的目录路径无可执行的权限。EEXIST
:参数pathname
所指定的文件已存在。ENAMETOOLONG
:参数pathname
的路径名称太长。ENOENT
:参数pathname
包含的目录不存在。ENOSPC
:文件系统的剩余空间不足。ENOTDIR
:参数pathname
路径中的目录存在但却非真正的目录。EROFS
:参数pathname
指定的文件存在于只读文件系统内。
代码示例:客户端与服务端通过命名管道实现进程间通信
//Common.hpp
#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>class Name_Fifo
{
public:Name_Fifo(const std::string path, const std::string name): _Name(name), _Path(path){umask(0);_fifoname = _Path + "/" + _Name;int ret = mkfifo(_fifoname.c_str(), 0666);if (ret == 0){std::cout << "mkfifo success!\n"<< std::endl;}else{std::cout << "mkfifo fail!\n"<< std::endl;exit(1);}}~Name_Fifo(){unlink(_fifoname.c_str());}private:std::string _Path;std::string _Name;std::string _fifoname;
};// 对指定命名管道进行的操作
class oper_fifo
{
public:oper_fifo(const std::string path, const std::string name): _Path(path), _Name(name), _fd(-1){_fifoname = _Path + "/" + _Name;}~oper_fifo(){}void OpenForRead(){_fd = open(_fifoname.c_str(), O_RDONLY);if (_fd < 0){std::cout << "open fifo failed!" << std::endl;exit(2);}else{std::cout << "open fifo success!" << std::endl;}}void OpenForWrite(){_fd = open(_fifoname.c_str(), O_WRONLY);if (_fd < 0){std::cout << "open fifo failed!" << std::endl;exit(2);}else{std::cout << "open fifo success!" << std::endl;}}void Read(){// 具体的读取操作在这里定义while (true){char rd_buffer[1024];int ret = read(_fd, rd_buffer, sizeof(rd_buffer) - 1);if (ret == 0){// 读到了文件末尾表示写入端退出:std::cout << "write exit me too!" << std::endl;break;}else if (ret < 0){std::cout << "read failed!" << std::endl;}else{rd_buffer[ret] = '0';std::cout << "Client say:" << rd_buffer << std::endl;}}}void Write(){// 具体的写入操作在这里定义while (1){std::cout<<"Please Enter:"<<std::endl;std::string messager;int cnt = 1;pid_t id = getpid();std::getline(std::cin, messager);messager += (", message number: " + std::to_string(cnt++) + ", [" + std::to_string(id) + "]");int ret=write(_fd,messager.c_str(),messager.size());}}void Close(){if(_fd>0){close(_fd);}}
private:std::string _Path;std::string _Name;std::string _fifoname;int _fd; // 命名管道的文件描述符
};
//Server.cc
#include"Common.hpp"int main()
{//创建管道:Name_Fifo Fifo(".","myfifo");oper_fifo my_read(".","myfifo");my_read.OpenForRead();my_read.Read();my_read.Close();return 0;
//Client.cc
#include"Common.hpp"int main()
{oper_fifo my_write(".","myfifo");my_write.OpenForWrite();my_write.Write();my_write.Close();return 0;
}
代码说明:
在我们的代码中,Common.hpp中定义了两个类一个是命名管道Name_Fifo,一个是对命名管道的操作oper_fifo。在服务端我们创建命名管道后进行读操作,此时由于写端还没有将管道文件打开服务端会阻塞于my_read.OpenForRead();代码处。
这时我们运行客户端代码,需要注意的是客户端不再需要进行mkfifo而是直接对创建的命名管道进行写操作。此时当客户端以写方式打开命名管道时并等待用户输入时,读端才会解除阻塞状态。
3.2 原理与特性
3.2.1 核心特性
命名管道在文件系统中以特殊文件形式存在,进程通过路径名访问,无需亲缘关系(如父子进程)。
单个命名管道默认是单向的,但可通过创建两个管道(一个读、一个写)实现双向通信。
读操作:若管道无数据,读取进程会阻塞,直到有数据写入。写操作:若管道缓冲区满,写入进程会阻塞,直到有空间可用。同步:通过内核的等待队列管理阻塞进程,确保数据有序传输。
命名管道独立于创建它的进程,即使进程退出,管道仍存在,直到被显式删除(如Linux的unlink
)。
3.2.2 工作原理
创建与初始化
通过mkfifo
或mknod
创建,内核在文件系统中生成一个inode
,但不占用磁盘空间,仅维护管道的元数据(如缓冲区大小、等待队列)。
数据传输流程
写入端:进程调用write
,数据被复制到内核缓冲区。若缓冲区满,写入进程阻塞。
读取端:进程调用read
,从内核缓冲区复制数据到用户空间。若缓冲区空,读取进程阻塞。
内核缓冲:数据在内核中临时存储,确保读写操作的原子性。
进程阻塞与唤醒
内核通过等待队列管理阻塞的读写进程。当数据可用或缓冲区有空间时,内核唤醒对应的进程。
关闭与清理
所有读写端关闭后,管道被销毁(Linux)或标记为无效(Windows)。
四、区别与联系 (命名&匿名)
4.1 区别
特性 | 命名管道(Named Pipe) | 匿名管道(Anonymous Pipe) |
---|---|---|
命名方式 | 通过文件系统路径命名(如/tmp/pipe 或\\.\pipe\name ) | 无名称,仅通过文件描述符(如fd[0] 、fd[1] )引用 |
进程关系 | 不相关进程可通过路径名通信 | 仅限亲缘进程(如父子进程、兄弟进程) |
持久性 | 独立于进程存在,需显式删除(如unlink ) | 随进程退出而自动销毁 |
网络通信 | 支持(Windows可通过网络路径访问) | 不支持,仅限本地进程 |
安全性 | 支持访问控制(如Windows的ACL) | 无内置安全机制,依赖进程间信任 |
实现复杂度 | 需创建管道文件(mkfifo 或CreateNamedPipe ) | 简单,通过pipe() 系统调用自动创建 |
数据方向 | 默认半双工(可通过双向管道模拟全双工) | 半双工,需两个管道实现双向通信 |
可见性 | 在文件系统中可见(如ls 命令可列出) | 不可见,仅在内核中维护 |
典型应用场景 | 跨进程、跨网络通信(如日志服务、远程服务) | 本地进程间短生命周期通信(如父子进程协作) |
命名与路径
命名管道通过文件系统路径标识,类似普通文件,但仅用于通信。匿名管道无路径,通过文件描述符传递,通常由pipe()
系统调用创建。
进程关系
命名管道允许无亲缘关系的进程通信(如服务进程与客户端)。匿名管道仅限有亲缘关系的进程(如fork()
创建的子进程)。
持久性与清理
命名管道需手动删除(如Linux的unlink
或Windows的CloseHandle
)。匿名管道随进程退出自动销毁,无需额外清理。
4.2 共同联系
特性 | 命名管道与匿名管道的共同点 |
---|---|
内核机制 | 均依赖内核缓冲区存储数据,支持阻塞式I/O |
数据传输方式 | 均为字节流(默认,可通过消息模式扩展) |
同步机制 | 均通过内核的等待队列管理阻塞的读写进程 |
进程通信模型 | 均属于进程间通信(IPC)机制 |
性能开销 | 均涉及用户态与内核态的上下文切换 |
适用场景 | 均适用于进程间数据交换(匿名适合短生命周期,命名适合长生命周期或跨进程) |
4.3 总结
命名管道更灵活,适合跨进程、跨网络通信,但需手动管理生命周期。
匿名管道更简单,适合亲缘进程间短生命周期通信,无需额外清理。
根据场景选择合适的管道类型,可平衡复杂度与功能需求。
相关文章:

Linux系统:进程间通信-匿名与命名管道
本节重点 匿名管道的概念与原理匿名管道的创建命名管道的概念与原理命名管道的创建两者的差异与联系命名管道实现EchoServer 一、管道 管道(Pipe)是一种进程间通信(IPC, Inter-Process Communication)机制,用于在不…...

使用python进行图像处理—图像变换(6)
图像变换是指改变图像的几何形状或空间位置的操作。常见的几何变换包括平移、旋转、缩放、剪切(shear)以及更复杂的仿射变换和透视变换。这些变换在图像配准、图像校正、创建特效等场景中非常有用。 6.1仿射变换(Affine Transformation) 仿射变换是一种…...

使用homeassistant 插件将tasmota 接入到米家
我写一个一个 将本地tasmoat的的设备同通过ha集成到小爱同学的功能,利用了巴法接入小爱的功能,将本地mqtt转发给巴法以实现小爱控制的功能,前提条件。1需要tasmota 设备, 2.在本地搭建了mqtt服务可, 3.搭建了ha 4.在h…...
VUE3 ref 和 useTemplateRef
使用ref来绑定和获取 页面 <headerNav ref"headerNavRef"></headerNav><div click"showRef" ref"buttonRef">refbutton</div>使用ref方法const后面的命名需要跟页面的ref值一样 const buttonRef ref(buttonRef) cons…...

【笔记】结合 Conda任意创建和配置不同 Python 版本的双轨隔离的 Poetry 虚拟环境
如何结合 Conda 任意创建和配置不同 Python 版本的双轨隔离的Poetry 虚拟环境? 在 Python 开发中,为不同项目配置独立且适配的虚拟环境至关重要。结合 Conda 和 Poetry 工具,能高效创建不同 Python 版本的 Poetry 虚拟环境,接下来…...

多模态学习路线(2)——DL基础系列
目录 前言 一、归一化 1. Layer Normalization (LN) 2. Batch Normalization (BN) 3. Instance Normalization (IN) 4. Group Normalization (GN) 5. Root Mean Square Normalization(RMSNorm) 二、激活函数 1. Sigmoid激活函数(二分类&…...

[10-1]I2C通信协议 江协科技学习笔记(17个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17...

AWSLambda之设置时区
目标 希望Lambda运行的时区是东八区。 解决 只需要设置lambda的环境变量TZ为东八区时区即可,即Asia/Shanghai。 参考 使用 Lambda 环境变量...

RFID推动新能源汽车零部件生产系统管理应用案例
RFID推动新能源汽车零部件生产系统管理应用案例 一、项目背景 新能源汽车零部件场景 在新能源汽车零部件生产领域,电子冷却水泵等关键部件的装配溯源需求日益增长。传统 RFID 溯源方案采用 “网关 RFID 读写头” 模式,存在单点位单独头溯源、网关布线…...

[C++错误经验]case语句跳过变量初始化
标题:[C错误经验]case语句跳过变量初始化 水墨不写bug 文章目录 一、错误信息复现二、错误分析三、解决方法 一、错误信息复现 write.cc:80:14: error: jump to case label80 | case 2:| ^ write.cc:76:20: note: crosses initialization…...

Unity-ECS详解
今天我们来了解Unity最先进的技术——ECS架构(EntityComponentSystem)。 Unity官方下有源码,我们下载源码后来学习。 ECS 与OOP(Object-Oriented Programming)对应,ECS是一种完全不同的编程范式与数据架构…...

uni-app学习笔记二十七--设置底部菜单TabBar的样式
官方文档地址:uni.setTabBarItem(OBJECT) | uni-app官网 uni.setTabBarItem(OBJECT) 动态设置 tabBar 某一项的内容,通常写在项目的App.vue的onLaunch方法中,用于项目启动时立即执行 重要参数: indexnumber是tabBar 的哪一项&…...

7种分类数据编码技术详解:从原理到实战
在数据分析和机器学习领域,分类数据(Categorical Data)的处理是一个基础但至关重要的环节。分类数据指的是由有限数量的离散值组成的数据类型,如性别(男/女)、颜色(红/绿/蓝)或产品类…...

【字节拥抱开源】字节团队开源视频模型 ContentV: 有限算力下的视频生成模型高效训练
本项目提出了ContentV框架,通过三项关键创新高效加速基于DiT的视频生成模型训练: 极简架构设计,最大化复用预训练图像生成模型进行视频合成系统化的多阶段训练策略,利用流匹配技术提升效率经济高效的人类反馈强化学习框架&#x…...

本地部署drawDB结合内网穿透技术实现数据库远程管控方案
文章目录 前言1. Windows本地部署DrawDB2. 安装Cpolar内网穿透3. 实现公网访问DrawDB4. 固定DrawDB公网地址 前言 在数字化浪潮席卷全球的背景下,数据治理能力正日益成为构建现代企业核心竞争力的关键因素。无论是全球500强企业的数据中枢系统,还是初创…...

可视化预警系统:如何实现生产风险的实时监控?
在生产环境中,风险无处不在,而传统的监控方式往往只能事后补救,难以做到提前预警。但如今,可视化预警系统正在改变这一切!它能够实时收集和分析生产数据,通过直观的图表和警报,让管理者第一时间…...

多模态大语言模型arxiv论文略读(112)
Assessing Modality Bias in Video Question Answering Benchmarks with Multimodal Large Language Models ➡️ 论文标题:Assessing Modality Bias in Video Question Answering Benchmarks with Multimodal Large Language Models ➡️ 论文作者:Jea…...

【向量库】Weaviate概述与架构解析
文章目录 一、什么是weaviate二、High-Level Architecture1. Core Components2. Storage Layer3. 组件交互流程 三、核心组件1. API Layer2. Schema Management3. Vector Indexing3.1. 查询原理3.2. 左侧:Search Process(搜索流程)3.3. 右侧&…...
PostgreSQL 对 IPv6 的支持情况
PostgreSQL 对 IPv6 的支持情况 PostgreSQL 全面支持 IPv6 网络协议,包括连接、存储和操作 IPv6 地址。以下是详细说明: 一、网络连接支持 1. 监听 IPv6 连接 在 postgresql.conf 中配置: listen_addresses 0.0.0.0,:: # 监听所有IPv4…...
python数据结构和算法(1)
数据结构和算法简介 数据结构:存储和组织数据的方式,决定了数据的存储方式和访问方式。 算法:解决问题的思维、步骤和方法。 程序 数据结构 算法 算法 算法的独立性 算法是独立存在的一种解决问题的方法和思想,对于算法而言&a…...
视觉slam--框架
视觉里程计的框架 传感器 VO--front end VO的缺点 后端--back end 后端对什么数据进行优化 利用什么数据进行优化的 后端是怎么进行优化的 回环检测 建图 建图是指构建地图的过程。 构建的地图是点云地图还是什么信息的地图? 建图并没有一个固定的形式和算法…...

统计按位或能得到最大值的子集数目
我们先来看题目描述: 给你一个整数数组 nums ,请你找出 nums 子集 按位或 可能得到的 最大值 ,并返回按位或能得到最大值的 不同非空子集的数目 。 如果数组 a 可以由数组 b 删除一些元素(或不删除)得到,…...
npm install 相关命令
npm install 相关命令 基本安装命令 # 安装 package.json 中列出的所有依赖 npm install npm i # 简写形式# 安装特定包 npm install <package-name># 安装特定版本 npm install <package-name><version>依赖类型选项 # 安装为生产依赖(默认&…...
Spring Boot 与 Kafka 的深度集成实践(二)
3. 生产者实现 3.1 生产者配置 在 Spring Boot 项目中,配置 Kafka 生产者主要是配置生产者工厂(ProducerFactory)和 KafkaTemplate 。生产者工厂负责创建 Kafka 生产者实例,而 KafkaTemplate 则是用于发送消息的核心组件&#x…...
【学习记录】使用 Kali Linux 与 Hashcat 进行 WiFi 安全分析:合法的安全测试指南
文章目录 📌 前言🧰 一、前期准备✅ 安装 Kali Linux✅ 获取支持监听模式的无线网卡 🛠 二、使用 Kali Linux 进行 WiFi 安全测试步骤 1:插入无线网卡并确认识别步骤 2:开启监听模式步骤 3:扫描附近的 WiFi…...
后端下载限速(redis记录实时并发,bucket4j动态限速)
✅ 使用 Redis 记录 所有用户的实时并发下载数✅ 使用 Bucket4j 实现 全局下载速率限制(动态)✅ 支持 动态调整限速策略✅ 下载接口安全、稳定、可监控 🧩 整体架构概览 模块功能Redis存储全局并发数和带宽令牌桶状态Bucket4j Redis分布式限…...

vue3 手动封装城市三级联动
要做的功能 示意图是这样的,因为后端给的数据结构 不足以使用ant-design组件 的联动查询组件 所以只能自己分装 组件 当然 这个数据后端给的不一样的情况下 可能组件内对应的 逻辑方式就不一样 毕竟是 三个 数组 省份 城市 区域 我直接粘贴组件代码了 <temp…...
Angular中Webpack与ngx-build-plus 浅学
Webpack 在 Angular 中的概念 Webpack 是一个模块打包工具,用于将多个模块和资源打包成一个或多个文件。在 Angular 项目中,Webpack 负责将 TypeScript、HTML、CSS 等文件打包成浏览器可以理解的 JavaScript 文件。Angular CLI 默认使用 Webpack 进行项目…...
大模型智能体核心技术:CoT与ReAct深度解析
**导读:**在当今AI技术快速发展的背景下,大模型的推理能力和可解释性成为业界关注的焦点。本文深入解析了两项核心技术:CoT(思维链)和ReAct(推理与行动),这两种方法正在重新定义大模…...
信息系统分析与设计复习
2024试卷 单选题(20) 1、在一个聊天系统(类似ChatGPT)中,属于控制类的是()。 A. 话语者类 B.聊天文字输入界面类 C. 聊天主题辨别类 D. 聊天历史类 解析 B-C-E备选架构中分析类分为边界类、控制类和实体类。 边界…...