【Linux进程】进程间通信(共享内存、消息队列、信号量)

 

目录

前言

1. System V IPC

2. 共享内存

系统调用接口

shmget

 ftok

 shmat

 shmdt

shmctl

共享内存的读写

共享内存的描述对象

3. 消息队列

msgget

msgsnd

 msgctl

 消息队列描述对象

4. 信号量

 系统调用接口

 semget

semctl

信号量描述对象 

5. 系统层面IPC资源

6. 补充

总结


前言

        前边学习了管道通信,本文再来聊一聊其他的进程间通信方式:共享内存、消息队列、信号量;

在这里插入图片描述

1. System V IPC

        System V IPC(Inter-Process Communication)是 Unix 操作系统的一种进程间通信机制。它提供了一组系统调用,允许不同进程之间共享数据和信息;主要有三种通信方式:共享内存、消息队列、信号量;通常用于需要高效数据交换和同步的应用场景;

2. 共享内存

 ·        前边提到的的两种管道都可以实现进程之间单向通信,但对于一些数据量大且需要频繁传输数据时效率就显得有点低,为了满足需求,并统一标准,于是便出现共享内存这一通信标准;
        共享内存作为一种进程间通信的标准,主要是为了解决管道通信在大数据量和频繁传输数据时效率较低的问题。共享内存允许多个进程共享同一块内存区域,进程可以直接访问内存中的数据,避免了数据的复制和传输开销,从而提高了通信的效率;
        共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据;

进程间通信的前提:让不同的进程看到同一份资源(由操作系统提供);

 

操作系统只允许两个进程使用共享内存通信吗?

        肯定不是,操作系统允许系统中同时存在多个共享内存对于创建的多个共享内存当然也需要进行管理;先描述,再组织;存在一个struct shm的结构体;用于对共享内存的描述;(共享内存对象)每个新建的共享内存都有一个结构体描述对象,这样对多个共享内存的管理,就转换成了对结构体对象的管理;

构建信道:

  1. 创建共享内存区域。
  2. 将共享内存映射到进程地址空间
  3. 进行通信

关闭信道:

  1. 解除共享内存映射
  2. 减少引用计数。
  3. 释放共享内存

问题来了,如何保证第二个参与通信的进程,找到的就是同一个共享内存呢?匿名管道可以通过父子进程继承的方式找到,命名管道可以通过文件系统找到,共享内存如何保证信道成功建立呢?
存在一个唯一的标识,进行识别

系统调用接口

shmget

shmget 用于创建一个新的共享内存段,或者获取一个已经存在的共享内存段的标识符;

 

 shmflg标志参数,目前只需了解两个:

  • IPC_CREAT:共享内存不存在就创建(创建后把标识符写到共享内存属性中),存在就获取与返回共享内存标识符
  • IPC_EXCL:不单独使用,一般与IPC_CREAT一起使用(IPC_EXCL | IPC CREAT):如果不存在就创建,如果存在就出错返回;主要用于确保创键的是一个新的共享内存;

 补充:

  • 共享内存(IPC资源)的生命周期是随内核的;
  • ipcrm -m 删除共享内存
  • ipcs -m查看存在的共享内存
  • shmid:共享内存id,使用这个共享内存时,我们一般使用shmid来进行操作共享内存
  • Key:一般不在应用层使用,只用来标识shm的唯一性;

 

 ftok

        前边我们提到如何让第二个进程找到共享内存,答案就是标识符,这里的key就是标识符;通信双方约定一个标识符,一方将标识符写入到共享内存对象中,另一方可以遍历进行查找标识符,就可以保证它们找到的是同一份资源;
        这里的标识符,理论上是可以随便写一个,只不过随便写的方式很容易与系统中已经存在的标识符冲突,所以一般会使用算法来生成唯一的标识符;这个接口就是ftok ;

         ftok()函数会使用指定的路径名和项目标识符来计算一个key值,该key值在系统中是唯一的,返回的key值可以作为参数传递给shmget()函数,从而指定共享内存段的标识符;

        pathname参数是一个指向以null结尾的路径名的指针。它的作用是为了生成一个唯一的key值。ftok()函数会使用指定的路径名来获取文件的inode号,然后将其与proj id参数合并,生成一个唯一的key值;
        这个路径名并不需要对应一个真实存在的文件,它只是用作生成key值的一个标识。通常情况下会选择一个在系统中唯一的、不会轻易更改的路径名来确保生成的key值是唯一的,以避免不同的进程之间出现key冲突;
        pathname参数要指向一个可靠的文件路径,比如一个特定的系统文件或者应用程序中的固定路径。这样可以确保在不同的系统中或者不同的程序中,使用相同的路径名生成的key值是相同的;

 shmat

shmat() 用于将共享内存段连接到调用进程的地址空间,形成一个虚拟地址的映射

 接口返回值void*返回共享内存在共享区映射的起始地址;

 shmdt

        shmdt 用于将共享内存段从进程的地址空间中分离,即解除该共享内存段与进程地址空间的映射关系
成功,shmdt()函数返回0;如果失败,返回-1,并设置errno来指示错误类型 ;

shmctl

 

shmctl控制共享内存段的函数,包含获取共享内存段信息、设置共享内存段的权限、删除共享内存段等;它的功能很多,当前我们主要使用删除的功能;

IPC RMID:删除共享内存段

 为什么我们使用标识符key需要使用者自己生成,为什么OS不直接自己生成?

        就是为了让使用者知道,然后好让目标进程通过这个值找到共享内存;如果让系统生成,使用者不知道标识符,那目标进程又如何知道这个标识符呢?内存中有多个共享内存,目标进程又如何去找自己的共享内存呢?根本不可能;要想找到共享内存就必须两进程提前约定好标识符,这样才能找到共享内存;

共享内存的读写

 共享内存在用户空间,如何控制读写呢?

在共享内存的开头或者结尾,留了一块区域,用来存储共享内存的属性,比如:读位置、写位置;

删除数据呢?和vector删除一样,不需要清空数据,只需要数据变无效即可;

特点:

  • 共享内存通信不提供同步机制,它是直接裸露给使用者的,所以一定要注意共享内存的使用安全问题;
  • 共享内存是所有进程通信速度最快的;
  • 共享内存可以提供较大的空间;

 为什么说共享内存是最快的通信方式?

对比一下管道:

进程A要想把数据通过管道发送给进程B,先从用户空间拷贝到管道(内核空间),进程B通过read把数据从管道再拷贝一份到自己的用户空间;

这里是很影响效率的具体分析一下:先把数据写到自定义的缓冲区,然后再把缓冲区的数据拷贝到内核空间的管道,读方再把数据从内核空间拷贝到自己定义的缓冲区;

 共享内存:

        进程A只要写入数据,就直接写入到了共享内存中,而进程B立马就能看到;共享内存的写入原理和malloc申请的空间使用类似,都是直接写入到内存中;不需要什么缓冲区,更不需要来回进行拷贝;共享内存不需要内核级别的拷贝,所以相对于管道来说效率更高,在应用场景中减少了拷贝的次数 ;

共享内存的描述对象

 以上是部分属性;

 

 编写一个简单的程序使用并验证一下:

 comm.hpp

#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cstring>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>const std::string pathname = "/home/test/test/share_memory";
const int proj_id = 0x11223344;
//共享内存的大小建议设置成4096的整数倍
const int size = 4096;
key_t Getkey()
{key_t k = ftok(pathname.c_str(), proj_id);//把字符串转化成C字符串风格if (k < 0){std::cerr << "error: " << errno << " errorstring: " << strerror(errno) << std::endl;exit(1);}//std::cout << "key: " << k << std::endl;return k;
}std::string ToHex(int id)
{char buffer[1024];snprintf(buffer, sizeof(buffer), "0x%x", id);return buffer;
}
int CreateShmHelper(key_t key, int flag)
{int shmid = shmget(key, size, flag);if (shmid < 0){std::cerr << "error: " << errno << " errorstring: " << strerror(errno) << std::endl;exit(2);}return shmid;
}int CreateShm(key_t key)
{return CreateShmHelper(key, IPC_CREAT | IPC_EXCL | 0664);
}int GetShm(key_t key)
{return CreateShmHelper(key, IPC_CREAT);
}

 test.cc

class Init
{
public:Init(){key_t key = Getkey();std::cout << "key: " << ToHex(key) << std::endl;shmid = CreateShm(key);// sleep(5);std::cout << "shmid: " << shmid << std::endl;std::cout << "开始将shm映射到进程的地址空间中" << std::endl;// sleep(5);s = (char*)shmat(shmid, nullptr, 0);// 打开管道用于发送信息进行控制,如果管道写端没有连接就一直阻塞// fd = open(filename.c_str(), O_RDONLY);}~Init(){shmdt(s);std::cout << "开始将shm从进程的地址空间中移除" << std::endl;// sleep(5);shmctl(shmid, IPC_RMID, nullptr);std::cout << "开始将shm从OS中删除" << std::endl;// sleep(10);// close(fd);// unlink(filename.c_str());}public:int shmid;int fd;char* s;
};int main(){Init init;//获取共享内存属性struct shmid_ds ds;shmctl(init.shmid,IPC_STAT, &ds);std::cout << ToHex(ds.shm_perm.__key) << std::endl;std::cout << ds.shm_segsz << std::endl;std::cout << ds.shm_atime << std::endl;std::cout << ds.shm_nattch << std::endl;
}

 使用命令查看:

 这也就验证了之前所提到的共享内存对象,共享内存 = 共享内存空间 +共享内存属性

3. 消息队列

 消息队列:提供一个进程给另一个进程发送数据块的能力;

结构:

        消息队列是OS中维护的一个数据结构当一个进程想要把数据发送给另一个进程,就可以把数据块连接在消息队列上,成为消息队列的一个节点;

msgget

msgget接口可以用于获取已经存在的消息队列的标识符,还可以用于创建新的消息队列;

key需要在用户层进行设置,并且这个key可以被A进程看到,也可以被B进程看到还要写到消息队列的属性中;和shmget中的类似;

msgflg也是一样的,示例:

int msgid = msgget(key, IPC_CREAT | IPC_EXCL);

这个队列中,不仅A进程可以加入数据块,B进程也可以;这时就产生了新的问题:它们如何知道哪个是自己想要的节点?


为了分辨出哪个是节点是进程需要的,所以队列中的数据块需要有类型;

msgsnd

 发送消息

 消息节点:

 

 使用示例:

struct msgbuf {  long mtype;        // 消息类型  char mtext[256];   // 消息内容  
};int msgid = msgget(key, IPC_CREAT | 0666);// 填充内容
struct msgbuf message;  
message.mtype = 1; // 设置消息类别  
snprintf(message.mtext, sizeof(message.mtext), "Hello, World!");  
msgsnd(msgid, &message, strlen(message.mtext) + 1, 0)

 msgctl

这里依然和shmctl的接口很类似,当前主要使用它的删除功能(删除消息队列)

 使用示例:

msgctl(msgid, IPC_RMID, nullptr); // 删除消息队列
msgctl(msgid, IPC_STAT, &ds); // 获取消息队列状态

         消息队列的属性和接口,根共享内存都是十分相似;在系统中,也是允许同时有多个消息队列存在的;所以在内核中也需要把消息队列管理起来;先描述,再组织;消息队列 =队列 +队列属性

 消息队列描述对象

 也有一个 struct ipc_perm类型的对象;

消息队列中的struct_ipc_perm 和共享内存中的struct ipc_perm两个其实是完全一样的用于记录该对象的权限、拥有者、访问权限等信息;

4. 信号量

 如何理解信号量?信号量本质其实就是一个计数器;作用:用来保护共享资源

 举个例子:
        比如:看电影,我们去电影院看电影,去看之前需要先买票,是买到票后,指定的座位属于你,还是坐到座位上后座位才属于你?(排除没素质的情况)当然是买完票以后,那个座位就属于你了;

        电影院和座位就是多人共享公共资源;买票的本质就是对资源的预定!电影院中有固定的座位,那么在购票系统中就会存在一个计数器,用于记录剩下的座位数;如果买票成功计数器就减减;而这里的座位和电影院就是OS公共资源,每个人就是进程;而购票系统的计数器就是信号量;规定好信号量初始值,就可以控制访问资源的进程数量;

信号量:表示资源数目的计数器,每个执行流(进程)想要访问公共资源内的某一份资源,不能让执行流直接访问,而是应该先申请信号量资源,本质就是对信号量计数器做--操作。只要--成功就完成了对资源的预定机制;
如果申请不成功?执行流就会被挂起

         访问公共资源的代码执行前,先申请信号量,如果申请成功才让访问,申请失败就挂起阻塞;这样就形成了保护机制;

        当信号量的数值设为1;int sem = 1;这时就只允许一个执行流读取公共资源;执行流在读取时,其他执行流只能阻塞等待 sem 就只有两种状态:1、0;二元信号量---互斥锁---完成互斥功能;

         信号量存在的本质就是为了保护公共资源,信号量是公共资源,那谁来保护信号量?信号量都没法保护,那又怎么保护其他公共资源?
但好在信号量公共资源内容较为简单,申请时只需要--操作,--操作只要符合原子性就可以;也就是说要么申请完成,要么不申请;不存在其他情况;这样就避免了出现其他情况;

申请信号量的操作 —P

释放信号量的操作—V 

细节分析:

  1. 信号量是公共资源的计数器,那么就需要先让所有进程都能看到信号量资源 —— 只能由操作系统提供——纳入到IPC体系;
  2. 信号量本质也是公共资源

 对信号量管理的理解

// 单个信号量
{struct sem;int count;struct task struct *wait queue;
}

 系统调用接口

 semget

 nsems表示要创建的几个信号量;其余两个参数和shmget一样的;

示例:

int semid = semget(key, n, IPC_CREAT | 0666); // 创建一个信号量 0666 是读写权限
// n表示创建信号量的个数

 system V 允许创建一批(至少一个)信号量;如何支持创建一批?在创建时设置n(创建个数)

semctl

创建了信号量,如何设置信号量的计数?使用 semctl 函数来实现;

 示例:

// 设置信号量的值为初始值  
// 初始化计数为1
// 0 表示您要设置第一个信号量的值
semctl(semid, 0, SETVAL, 1);

 设置一组信号量呢?

 int initial_values[] = {1, 1, 1}; // 假设每个信号量的初始值为 1  for (int i = 0; i < num_semaphores; i++) {  semctl(semid, i, SETVAL, initial_values[i]) {}  

 当然也可以删除信号量:

semctl(semid, 0, IPC_RMID)

信号量描述对象 

 

         信号量允许存在多个,那么就需要对其进行管理,那么自然也有信号量描述对象;发现他也有struct ipc_perm对象;

5. 系统层面IPC资源

站在OS角度,内核是如何看待ICP资源的?它是OS内单独设计的模块
        共享内存、消息队列、信号量,它们在自己的描述对象开始都有一个ipc_perm 结构体通过对这个结构体的管理就可以达到对这三种IPC资源的管理 ;

 回顾一下柔性数组:

 kern_ipc_perm跟共享内存、消息队列以及信号量中的ipc_perm的内容一致,可以把它们看待成同一个结构体;

        统一使用该数组实现对IPC资源的管理,遍历这个数组,就可以通过 key 来判断某个IPC资源是否存在;

        数组中存放的是各种IPC资源的struct ipc_perm 结构体对象地址,每当需要增加一个IPC对象(共享内存、消息队列、信号量)时,就只需在数组中新增一个struct ipc_perm对象地址即可;

比如:想要访问共享内存对象中除struct ipc_perm 外的其他资源怎么办?直接使用*p肯定不行;只需要强转一下类型就可以;比如:p[1]存放的是消息队列的ipc_perm 

((msg_queue*)p[1])->...

把struct ipc perm*类型的指针强转成消息队列对象类型的指针,这样就可以访问了; 

这个设计模式很类似于多态;通过对相同类型的对象访问,就可以实现对不同IPC资源的管理 ;

想要更深入的了解可以拜读参考这篇文章:进程间通信:共享内存和信号量的统一封装机制原理与实现

6. 补充

进程间通信流程:

进程间通信 -->多个执行流看到同一份资源(公共资源) --> 并发访问 --> 数据干扰问题 --> 保护数据
--> 同步和互斥;

  • 互斥:任何时刻只允许一个执行流(进程)访问公共资源,比如:管道,只允许一个读一个写;互斥可以通过加锁完成;
  • 同步:多个执行流执行时按照一定顺序执行 ;

被保护起来的公共资源--临界资源;

访问临界资源的代码,被称为临界区;


总结

         好了以上便是本文的全部内容,希望对你有所帮助或启发,感谢阅读!

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

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

相关文章

模型 八角行为分析法(行为激发)

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。激发行为的八大心理驱动力模型。 1 八角行为分析法的应用 1.1 支付宝蚂蚁森林 支付宝的蚂蚁森林是一个旨在鼓励用户参与环保活动的产品。用户通过日常的低碳行为&#xff08;如步行、线上支付等&…

【数据结构练习题】链表与LinkedList

顺序表与链表LinkedList 选择题链表面试题1. 删除链表中等于给定值 val 的所有节点。2. 反转一个单链表。3. 给定一个带有头结点 head 的非空单链表&#xff0c;返回链表的中间结点。如果有两个中间结点&#xff0c;则返回第二个中间结点。4. 输入一个链表&#xff0c;输出该链…

网安瞭望台第16期

国内外要闻 Apache Struts 文件上传漏洞&#xff08;CVE - 2024 - 53677&#xff09; 近日&#xff0c;Apache Struts 被发现存在文件上传漏洞&#xff08;CVE - 2024 - 53677&#xff09;&#xff0c;安恒 CERT 评级为 2 级&#xff0c;CVSS3.1 评分为 8.1。 漏洞危害&#x…

基于python使用UDP协议对飞秋进行通讯—DDOS

基于飞秋的信息传输 声明&#xff1a;笔记的只是方便各位师傅学习知识&#xff0c;以下代码、网站只涉及学习内容&#xff0c;其他的都与本人无关&#xff0c;切莫逾越法律红线&#xff0c;否则后果自负。 老规矩&#xff0c;封面在文末&#xff01; 飞秋介绍 &#xff08;…

JAVA:组合模式(Composite Pattern)的技术指南

1、简述 组合模式(Composite Pattern)是一种结构型设计模式,旨在将对象组合成树形结构以表示“部分-整体”的层次结构。它使客户端对单个对象和组合对象的使用具有一致性。 设计模式样例:https://gitee.com/lhdxhl/design-pattern-example.git 2、什么是组合模式 组合模式…

LeetCode:222.完全二叉树节点的数量

跟着carl学算法&#xff0c;本系列博客仅做个人记录&#xff0c;建议大家都去看carl本人的博客&#xff0c;写的真的很好的&#xff01; 代码随想录 LeetCode&#xff1a;222.完全二叉树节点的数量 给你一棵 完全二叉树 的根节点 root &#xff0c;求出该树的节点个数。 完全二…

MaxKB基于大语言模型和 RAG的开源知识库问答系统的快速部署教程

1 部署要求 1.1 服务器配置 部署服务器要求&#xff1a; 操作系统&#xff1a;Ubuntu 22.04 / CentOS 7.6 64 位系统CPU/内存&#xff1a;4C/8GB 以上磁盘空间&#xff1a;100GB 1.2 端口要求 在线部署MaxKB需要开通的访问端口说明如下&#xff1a; 端口作用说明22SSH安装…

基于指纹图像的数据隐藏和提取matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 2.算法运行软件版本 matlab2022a 3.部分核心程序 &#xff08;完整版代码包含详细中文注释和操作步骤视频&#xff09…

kubeadm一键部署K8S 集群架构

kubeadm一键部署K8S 集群架构(centos7) https://www.k8src.cn/ https://kubernetes.io/zh-cn/docs/home/ https://blog.csdn.net/m0_58709145/article/details/140128179 https://blog.csdn.net/jiaqijiaqi666/article/details/129745828 Kubeadm init报错[ERROR CRI]: contai…

直流电机驱动电路分享(HIP4082)

一、原理图分享 注意&#xff1a;M2_INA、M2_INB可直接接3.3V电平信号。 二、芯片介绍 1、HIP4082 HIP4082是一款高频驱动器&#xff0c;专为半桥和全桥应用而设计。它具有四个高/低侧驱动输出&#xff0c;可以提供高达100V的驱动电压。HIP4082还具有逻辑级输入和反馈输入&a…

企业版 YashanDB 23.2.4 分布式集群 数据库一主二备集群安装部署指南

一、概述 1.1 文档目标 本部分旨在为技术人员提供崖山数据库企业版 23.2 在 CentOS 7 x86_64 操作系统上进行安装部署操作的全面且清晰的指引。通过对系统架构、集群拓扑和部署需求的精确阐述&#xff0c;使读者能够在安装过程开始前形成系统的概念架构&#xff0c;为后续的详…

性能】JDK和Jmeter的安装与配置

一、JDK环境配置 1. 下载JDK 官网下载地址&#xff1a;http://www.oracle.com/technetwork/java/javase/downloads/java-archive-downloads-javase7-521261.html 选择对应系统的安装包&#xff0c;下载后安装&#xff0c;安装中记录JDK安装的地址&#xff0c;之后一直点击下一…

Mysql之YUM安装时GPG 密钥报错问题处理

一、背景说明 使用YUM安装mysql5.7的时候报错&#xff0c;报错信息提示未安装公钥。博主查看/etc/yum.repos.d/mysql-community.repo配置文件中关于公钥的配置&#xff0c;确实启用了公钥验证&#xff0c;博主再排查过程中还是走了一些弯路&#xff0c;最终顺利解决了&#xff…

启动报错java.lang.NoClassDefFoundError: ch/qos/logback/core/status/WarnStatus

报错信息图片 日志&#xff1a; Exception in thread "Quartz Scheduler [scheduler]" java.lang.NoClassDefFoundError: ch/qos/logback/core/status/WarnStatus先说我自己遇到的问题&#xff0c;我们项目在web设置了自定义的log输出路径&#xff0c;多了一个 / 去…

Elasticsearch-分词器详解

什么是分词器 1、分词器介绍 对文本进行分析处理的一种手段&#xff0c;基本处理逻辑为按照预先制定的分词规则&#xff0c;把原始文档分割成若干更小粒度的词项&#xff0c;粒度大小取决于分词器规则。 常用的中文分词器有ik按照切词的粒度粗细又分为:ik_max_word和ik_smart&…

Docker 入门:如何使用 Docker 容器化 AI 项目(一)

引言 在人工智能&#xff08;AI&#xff09;项目的开发和部署过程中&#xff0c;环境配置和依赖管理往往是开发者遇到的挑战之一。开发者通常需要在不同的机器上运行同样的代码&#xff0c;确保每个人使用的环境一致&#xff0c;才能避免 “在我的机器上可以运行”的尴尬问题。…

ExcelVBA编程输出ColorIndex与对应颜色色谱

标题 ExcelVBA编程输出ColorIndex与对应颜色色谱 正文 解决问题编程输出ColorIndex与对应色谱共56&#xff0c;打算分4纵列输出&#xff0c;标题是ColorIndex,Color,Name 1. 解释VBA中的ColorIndex属性 在VBA&#xff08;Visual Basic for Applications&#xff09;中&#xff…

2024年11月 蓝桥杯青少组 STEMA考试 Scratch真题

2024年11月 蓝桥杯青少组 STEMA考试 Scratch真题&#xff08;选择题&#xff09; 题目总数&#xff1a;5 总分数&#xff1a;50 选择题 第 1 题 单选题 Scratch运行以下程宇后&#xff0c;小兔子会&#xff08; &#xff09;。 A. 变小 B. 变大 C. 变色 D. …

springboot470基于协同过滤算法的东北特产销售系统的实现(论文+源码)_kaic

摘 要 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全性&#xff0c;还是可操作性等各个方面来讲&#xff0c;遇到了互联网时代才发现能补上自古…

37. Three.js案例-绘制部分球体

37. Three.js案例-绘制部分球体 实现效果 知识点 WebGLRenderer WebGLRenderer 是Three.js中的一个渲染器类&#xff0c;用于将3D场景渲染到网页上。 构造器 WebGLRenderer( parameters : Object ) 参数类型描述parametersObject渲染器的配置参数&#xff0c;可选。 常用…