Chromium源码阅读:深入理解Mojo框架的设计思想,并掌握其基本用法(1)
Mojo简介
Mojo 是一个运行时库的集合,提供与平台无关的通用 IPC 原语抽象、消息 IDL 格式以及具有针对多种目标语言的代码生成的绑定库,以便于跨任意进程间和进程内边界传递消息。
Mojo 分为清晰分离的层,子组件的基本层次结构如下:

分析Mojo之前,我们的思考
笔者在阅读源码前,喜欢会去思考,“如果让我来设计一个类似的功能的模块,我会怎么设计?”。然后对比文档去思考为什么会出现思路的差异,这种方式可以让我快速掌握一个开源库的设计精髓。
这次也是一样,我们想想,如果是我们自己设计Mojo,这会是什么样的架构和过程。
- 首先,Mojo是跨平台的,那么必然有一层platform的平台差异屏蔽层;
- 其次,跨进程通信在不同平台上的最佳实现方案可能不一样,例如有的平台是管道,有的平台是共享内存,具体如何选择取决于不同平台的性能差异,因此,我必须将跨平台通信细节进行抽象,提取出一些概念,用于描述通信的应用层细节。
- 之后,两个进程之间的通信之前,需要先建立连接,因此必然也需要定义一组规则和概念,来描述连接的应用层细节。
- 为了增加通信的灵活性,我们可以定义一组观察者或者过滤器的规则,可以实现对数据流的监测和量化,也能实现更灵活的扩展。因此我们需要定义一组规则和概念来实现这个目标。
- 为了让跨平台通信框架更加易于使用,我们需要提供一套序列化和反序列化的框架,这样可以让通信以自定义结构体的形式进行,而非数据流。
- 如果存在大量的通信消息,那么我们需要解决不同进程共享头文件,使用自定义结构体的形式;另外,Mojo除了跨平台,还需要夸语言,那么,我们必然不能使用某种语言的结构体定义形式(例如C的结构体或者JavaScript友好的Json),而是需要定义一种新的规则,通过工具自动生成不同语言友好的结构体源码。
上面的5和6本质上可以看作是同一个问题,并且我首先想到的是protobuffer库可以实现这两个问题的解法。
带着我们自己的设计思路,再去看看Mojo的设计方案。
的Mojo方案:
源码目录体积一览:

源码重要文件一览(排除test源文件,按大小排序,top的文件如下图):

一般来说,体积大的源文件表示的功能都比较核心,因此这些文件里面对应的功能和概念,很大概率是Mojo框架的核心,值得每一项进行标注理解。
Mojo的底层Channel
在Mojo中的源码中,可以很明显的发现关键字Channel是最底层跨进程通信的关键概念,并且能找到平台相关的抽象和实现,直接搜索文件名关键字就一览无余了:

channel.h
看了一眼mojo\core\channel.h可以发现,抽象基类是mojo::core::Channel, 并且定义了许多核心概念,包括Message(A message to be written to a channel.)、Delegate( // Delegate methods are called from the I/O task runner with which the Channel
was created (see Channel::Create). 关键回调OnChannelMessage)等。
不同平台通过继承抽象基类mojo::core::Channel实现平台相关的读写,以mojo\core\channel_win.cc的ChannelWin为例,使用Win32API的ReadFile、WriteFile从base::win::ScopedHandle句柄中读写数据,这个句柄是构造函数的参数ConnectionParams(TakeEndpoint().TakePlatformHandle().TakeHandle())传入的获取的。
channel相关的重点类解析
-
PlatformChannel
是一个封装了两个交织在一起的端点的类或结构,这些端点属于特定平台的基本通信原语,比如在Windows上是管道,在Unix系统上是域套接字,在macOS上是Mach端口对。其中一个端点被指定为“本地”端点,由创建它的进程保留;另一个端点被指定为“远程”端点,应当传递给外部的进程。 -
PlatformChannel 可以用来在两个进程之间启动Mojo IPC(一种进程间通信机制)。通常情况下另一个进程是当前进程的子进程,PlatformChannel
提供辅助方法来将端点以这种方式传递给子进程;但这种设置在所有平台上并不是强制性的。
如果需要一个允许客户端通过名称来连接的通道(比如一个命名管道或者套接字服务器,这种类型仅在Windows和POSIX系统上被支持),那么可以参考
NamedPlatformChannel。 -
PlatformChannelServer 这个类负责持有一个 PlatformChannelServerEndpoint 实例,并监听一个单一的来自客户端的连接请求。这个类不是线程安全的,必须在运行I/O消息泵(Message Pump)的线程上使用。
-
PlatformChannelServer 和PlatformChannel 对比: 简而言之,PlatformChannel 负责创建和管理进程间通信的通道,而 PlatformChannelServer
则是在服务端监听和接受这些通道上的连接请求。PlatformChannel 可以看作是连接的“管道”,而 PlatformChannelServer 是“水龙头”,控制着连接的开启。 -
PlatformHandle平台句柄类,它带有一些额外的类型信息,用来表明它是一个通道端点(channel endpoint)。也就是说,它是一种句柄,可以被用来作为 MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL
发送或接收邀请到一个远程的 PlatformChannelEndpoint。
结合调用堆栈:
Channel的创建:

Channel的发送消息:

通过调用堆栈可以发现,Channel的概念几乎是Mojo最底层的概念了,往上走有Router、InterfaceEndpointClient、Message、ChannelMojo概念等。以下是从最底层到更高层的一些核心概念及其作用的介绍:
-
Channel:
Channel是 Mojo IPC 系统中最底层的抽象。它代表了一个底层的通信通道,负责在两个进程之间传输原始的字节数据。Channel封装了操作系统级别的 IPC 机制(如套接字或共享内存),以便在不同的平台上提供一致的 API。它负责序列化和反序列化消息,保证数据的完整性,并处理底层的传输细节。 -
Router:
Router位于Channel之上,是一个稍高层次的抽象。它负责将发出的消息路由到正确的Channel,并从Channel接收消息。Router还管理消息的生命周期,确保消息按照正确的顺序发送和接收,并可能处理流控制和重试逻辑。 -
InterfaceEndpointClient:
InterfaceEndpointClient是 Mojo IPC 中的一个组件,它代表了 Mojo 接口的端点。它与Router配合,以便在 Mojo 接口上发送和接收消息。通常,每个 Mojo 接口都有一个或多个方法,这些方法对应于可以通过该端点发送的消息类型。InterfaceEndpointClient会序列化这些方法调用为消息,并将它们传递给Router进行传输。 -
Message:
Message是代表 IPC 系统中传递的一个消息实体。它通常包含要传输的数据(例如,方法调用的参数),以及可能的元数据(例如,消息类型或优先级)。在 Mojo IPC 中,Message是发送和接收的基本单位,由Router和Channel处理。 -
ChannelMojo:
ChannelMojo是Channel的一个具体实现,它利用 Mojo 系统的底层管道(如MessagePipe)来传输数据。ChannelMojo提供了一个适应 Mojo 管道特性的Channel接口,使得上层的Router和InterfaceEndpointClient能够通过 Mojo 管道发送和接收消息。
这些概念共同构成了 Mojo IPC 系统的框架,其中每个层次都建立在下一个层次之上,提供了逐步更高级别的抽象和功能。开发者可以根据需要选择在哪个层次上与 IPC 系统交互,从直接使用 Channel 的字节级操作,到通过 InterfaceEndpointClient 的接口级调用。
Mojo的Node
在源码一览中,我们发现node.cc是最大的源文件,我们以此为线索展开对Node的理解和阅读,Node相关文件有:

Node.h中,NodeChannel是一个核心,注释只有一句:Wraps a Channel to send and receive Node control messages.:
// Wraps a Channel to send and receive Node control messages.
class MOJO_SYSTEM_IMPL_EXPORT NodeChannel: public base::RefCountedDeleteOnSequence<NodeChannel>,public Channel::Delegate {public:// .... 略
由此可见,Channel 是一种底层概念,用于抽象化和平滑处理不同平台之间的差异,而 Node 概念则在 Channel 的基础上进行了进一步的封装和抽象化。NodeChannel用于定义 Mojo 中与连接、广播、中介(Broker)、消息传输以及错误处理相关的实现细节。如果用计算机网络的术语进行类比,那么 Channel 类似于网络协议栈中的 IP 层,它提供了寻址和路由的能力;而 NodeChannel则相当于应用层的协议,例如 UDP,它在更高层次上处理数据的传输和相关逻辑。
那么,可以预见,Mojo的应用层概念将围绕Node为核心展开。
从上面代码中我们发现,NodeChannel中有个重要的嵌入类:Delegate。Delegate的概念在chromium广泛存在,其实可以理解为Delegate就是一组回调,在宿主对象处理逻辑的关键节点时,通过Delegate回调转移执行绪,以实现行为的定制和扩展的能力。 通过了解Delegate回调的函数组成,可以快速了解宿主类的主要功能和关键流程,是阅读源码的重要技巧。例如NodeChannel的Delegate的类定义如下:
class Delegate {public:virtual ~Delegate() = default;virtual void OnAcceptInvitee(const ports::NodeName& from_node,const ports::NodeName& inviter_name,const ports::NodeName& token) = 0;virtual void OnAcceptInvitation(const ports::NodeName& from_node,const ports::NodeName& token,const ports::NodeName& invitee_name) = 0;virtual void OnAddBrokerClient(const ports::NodeName& from_node,const ports::NodeName& client_name,base::ProcessHandle process_handle) = 0;virtual void OnBrokerClientAdded(const ports::NodeName& from_node,const ports::NodeName& client_name,PlatformHandle broker_channel) = 0;virtual void OnAcceptBrokerClient(const ports::NodeName& from_node,const ports::NodeName& broker_name,PlatformHandle broker_channel,const uint64_t broker_capabilities) = 0;virtual void OnEventMessage(const ports::NodeName& from_node,Channel::MessagePtr message) = 0;virtual void OnRequestPortMerge(const ports::NodeName& from_node,const ports::PortName& connector_port_name,const std::string& token) = 0;virtual void OnRequestIntroduction(const ports::NodeName& from_node,const ports::NodeName& name) = 0;virtual void OnIntroduce(const ports::NodeName& from_node,const ports::NodeName& name,PlatformHandle channel_handle,const uint64_t remote_capabilities) = 0;virtual void OnBroadcast(const ports::NodeName& from_node,Channel::MessagePtr message) = 0;
#if BUILDFLAG(IS_WIN)virtual void OnRelayEventMessage(const ports::NodeName& from_node,base::ProcessHandle from_process,const ports::NodeName& destination,Channel::MessagePtr message) = 0;virtual void OnEventMessageFromRelay(const ports::NodeName& from_node,const ports::NodeName& source_node,Channel::MessagePtr message) = 0;
#endifvirtual void OnAcceptPeer(const ports::NodeName& from_node,const ports::NodeName& token,const ports::NodeName& peer_name,const ports::PortName& port_name) = 0;virtual void OnChannelError(const ports::NodeName& node,NodeChannel* channel) = 0;};
通过这个代理类,就能很直观地理解NodeChannel的功能和作用。
mojo的Port
在阅读NodeChannel类的时候,有一个关键字出现了多次,那就是port。在Mojo中,port是一个命名空间,也是一个重要概念,port这个类的头文件注释如下:
在 Mojo IPC 系统中,“Port”本质上是一个地址的循环列表中的一个节点。为了本文档的目的,这样的列表将被称为“路由”(route)。路由是所有 Node 事件流通的基本媒介,因此是所有 Mojo 消息传递的骨干。
每个 Port 都由一个节点(参见 node.h)内的 128 位地址唯一标识。Port 本身并不真正“做”任何事情:它是一系列状态的命名集合,而拥有它的 Node 管理所有事件的产生、传输、路由和处理逻辑。有关 Port 如何被用来传输任意用户消息以及其他 Ports 的更多细节,请参见 Node。
Ports 可以处于几种状态(见下面的 State),这些状态决定了它们如何响应以它们为目标的系统事件。在最简单和最常见的情况下,Ports 最初是作为一对纠缠在一起的状态(即由两个 Ports 组成的简单循环)创建的,都处于 kReceiving 状态。我们这里将这些 Ports 标为 |A| 和 |B|,它们可以使用 Node::CreatePortPair() 创建:
+-----+ +-----+| |--------->| || A | | B || |<---------| |+-----+ +-----+
|A| 通过 |peer_node_name| 和 |peer_port_name| 引用 |B|,同时 |B| 反过来引用
|A|。请注意,一个 Node 永远不会知道是谁向给定的 Port 发送事件;它只知道必须从给定的 Port 路由事件到哪里。
为了方便文档描述,我们将路由中的一个接收端 Port 称为另一个的“共轭”(conjugate)。一个接收端 Port 的共轭在初始创建时也是它的对端,但由于代理,这种关系可能随着时间而改变。 对这个数据结构的所有访问必须通过获取 |lock_|来进行保护,这只能通过 PortLocker 实现。PortLocker 确保在单个线程上重叠的 Port 锁获取总是以全局一致的顺序进行。
通过头文件注释,感觉似懂非懂,看看Port这个类的主要成员和方法吧:
Port类是 Mojo IPC 系统中的一个核心组件,它代表了消息传递路径上的一个节点。这个类继承自
base::RefCountedThreadSafe<Port>,允许它在多个线程中安全地共享和管理其生命周期。以下是Port
类的主要功能和特性:
State枚举:定义了Port可能处于的状态,包括kUninitialized(未初始化)、kReceiving(接收中)、kBuffering(缓冲中)、kProxying(代理中)和
kClosed(已关闭)。state成员变量:存储当前Port的状态。peer_node_name和peer_port_name成员变量:指定了事件应该从该Port路由到哪个节点和端口的地址。prev_node_name和prev_port_name成员变量:跟踪当前发送消息到这个Port的上一个端口,用于验证发送方节点是否有权限发送消息到这个端口,同时保持接收消息的顺序。pending_merge_peer成员变量:标记这个端口是否准备合并。- 一系列的序列号成员变量(
next_control_sequence_num_to_send、next_sequence_num_to_send
等):用于跟踪控制和用户消息事件的序列号。message_queue成员变量:存储该Port接收到的用户消息队列。此队列只为kBuffering或kReceiving状态的Port提供服务。control_message_queue成员变量:在Port处于kBuffering状态时,暂存即将发送的控制消息。send_on_proxy_removal成员变量:在某些边缘情况下,如果这个(代理中的)Port被销毁,它可能需要记得路由一个特殊的事件。user_data成员变量:附加到Port的任意用户数据。在 Mojo 中,这通常用于存储通知有关Port状态变化的观察者接口。remove_proxy_on_last_message和peer_closed成员变量:标志位,用于指示Port的一些状态,如代理何时可以移除,以及它的对端Port是否已关闭。Port构造函数:用于初始化Port,设置初始的序列号。AssertLockAcquired方法:用于调试中检查是否已获取Port的锁。IsNextEvent方法:检查给定的事件是否应该根据序列号和发送方节点接下来处理。NextEvent方法:获取下一个要处理的缓冲事件。BufferEvent方法:将事件缓存以供后续处理。>TakePendingMessages方法:清空等待节点验证的事件队列,并返回所有用户事件。- 私有析构函数
~Port:确保Port只能通过引用计数安全地销毁。- 私有成员
lock_:用于确保对Port数据结构的线程安全访问。PortLocker友元类:用于确保在单个线程上以全局一致的顺序获取重叠的Port锁。
该类的设计允许它在 Mojo IPC 系统中作为消息的发送和接收点,管理消息的顺序和状态,并确保消息在正确的路径上流动。
结合其他源码,发现Port和Dispatcher相关逻辑结合紧密,另外,Port存储了Event的序号等数据信息,支持插入事件,并且许多数据成员用于Node.cc中实现消息处理,可见Port这个类做的事情确实很难和已有的概念类比出来,也难怪通过这个类的注释难以一下理解其作用。简而言之,Port这个类即负责一部分事件排序和派发相关的逻辑处理,也承载了一个寻址的功能。
在 Mojo IPC 系统中,Node 通常代表了一个独立的参与者,如一个进程,它是消息传递路径上的一个物理节点。Node 可以是消息的最初发送者或最终接收者。相比之下,Port 是 Node 内部的逻辑上的虚拟节点,它负责管理消息的复杂路由、转发以及过滤等操作。每个 Port 都由其所属的 Node 管理,并且可以与其他 Node 中的 Port 形成连接,从而构成消息传递的网络。
之所以这样设计,是为了让两个Node之间可以出现多个连接,每个连接就是一对“共轭”的Port。这样每个连接各自的序号(seq)就不会互相干扰。所以,序号的数据就存储在Port类里,这也就不奇怪了。正因为Port代表了连接,所以数据的过滤和代理也必须面向连接进行,因此Port也和相关的类紧密联系。
说实话,如果把Port改名为Connection,也许会更直观一些。
这里面出现了NodeName和Port Name,也顺便看看定义:
struct COMPONENT_EXPORT(MOJO_CORE_PORTS) PortName : Name {constexpr PortName() : Name(0, 0) {}constexpr PortName(uint64_t v1, uint64_t v2) : Name(v1, v2) {}
};
struct COMPONENT_EXPORT(MOJO_CORE_PORTS) NodeName : Name {constexpr NodeName() : Name(0, 0) {}constexpr NodeName(uint64_t v1, uint64_t v2) : Name(v1, v2) {}
};
接下来
接下来,我们继续阅读Mojo模块的代码。了解消息的过滤和派发、序列化和反序列化、Mojom、等功能逻辑。(未完待续…)
相关文章:
Chromium源码阅读:深入理解Mojo框架的设计思想,并掌握其基本用法(1)
Mojo简介 Mojo 是一个运行时库的集合,提供与平台无关的通用 IPC 原语抽象、消息 IDL 格式以及具有针对多种目标语言的代码生成的绑定库,以便于跨任意进程间和进程内边界传递消息。 Mojo 分为清晰分离的层,子组件的基本层次结构如下ÿ…...
通用大模型VS垂直大模型对比
通用大模型和垂直大模型的区分主要在于它们的设计目的、应用范围、训练数据、优化目标和使用场景。以下是一些关键点,用以区分这两种模型: 设计目的: 通用大模型:设计用于处理多种类型的任务,不特定于某一领域。垂直大…...
时尚解决方案来袭:几分钟即可生成高清商拍大片
在时尚行业,视觉展示的重要性不可小觑。商品图片不仅代表品牌的风格调性,而且直接影响消费者的购买行为。可以说,视觉营销在服装行业中的地位至关重要。 尽管如此,视觉营销的传统产出渠道——商业摄影,因其高成本、复杂…...
【每日一练】day1
✨✨谢谢大家捧场,祝屏幕前的小伙伴们每天都有好运相伴左右,一定要天天开心哦!✨✨ 🎈🎈作者主页: 🎈丠丠64-CSDN博客🎈 ✨✨ 帅哥美女们,我们共同加油!一起…...
GA/T 1400 (非标)视图库网关
GA/T 1400 (非标)视图库网关 应用概述: GAT1400视图库网关产品是公司“分布式综合安防管理平台”下的子系统 针对以下遇到应用场景定制开发、优化后形成的网关产品,具备兼容性高、可扩展、可功能定制、可OEM等优点。 视图库网关…...
QT安装及项目创建
一、QT安装 1、安装qt_creater 方法一: 镜像文件:在2024-6-12:版本已经更新到了6.7 下载地址:https://download.qt.io/archive/qt/ 方法二: 百度网盘:链接:https://pan.baidu.com/s/1D0EmH…...
15. STUN协议和ICE工作原理
NET介绍 NAT是一种地址转换技术,它可以将IP数据报文头中的IP地址转换为另一个IP地址,并通过转换端口号达到地址重用的目的。 在大多数网络环境中,我们都需要通过 NAT 来访问 Internet。 NAT作为一种缓解IPv4公网地址枯竭的过渡技术ÿ…...
JVM (一)内存模型
一。内存结构 1,JVM内存结构 堆内存:是JVM中最大的一块,由新生代和老年代组成。默认情况下新生代按照8:1:1的比例来分配; 方法区:存储类信息、常量、静态变量等数据,是线程共享的区域; 栈&#…...
Web前端职业描述:编织数字世界的绚丽画卷
Web前端职业描述:编织数字世界的绚丽画卷 在数字化浪潮席卷而来的今天,Web前端职业日益成为技术领域的璀璨明星。他们不仅是数字世界的建筑师,更是用户体验的缔造者。那么,Web前端职业究竟是怎样的呢?接下来ÿ…...
负氧离子监测站:打造健康生态的守护者
TH-FZ5随着人们对生活质量和健康水平的要求日益提高,空气质量成为了公众关注的焦点。其中,负氧离子作为空气中的一种重要成分,对人体健康有着显著的影响。负氧离子监测站作为监测空气中负氧离子浓度的专业设备,在现代环境监测和生…...
在调用接口上map与forEach的区别
在场景:一个表格数据需要上传,每行表格需要上传图片->这就需要在提交时对数据也就是数组进行处理(先将每个元素图片上传拿到图片id 这种情况我刚开始就用的map处理,然后问题来了,提交的接口调用了,但是…...
最短路:spfa算法
最短路:spfa算法 题目描述参考代码 题目描述 参考代码 输入示例 3 3 1 2 5 2 3 -3 1 3 4输出示例 2#include <iostream> #include <cstring> #inc…...
算法笔记 图论和优先级队列的笔记
图论 DFS stack O(h) 不具有最短性 BFS queue O(2^h) 最短路 迪杰斯特拉算法 初始化: 将起始节点 A 的距离设为 0。将其他所有节点的距离设为无穷大。创建一个优先队列,并将起始节点 A 加入优先队列。 处理队列: …...
6.每日LeetCode-数组类,找到所有数组中消失的数字
题目 448找到所有数组中消失的数字.go 给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。 示例 1: 输入:nums [4,3,2,7,8,2,…...
【Three.js】知识梳理十:Three.js纹理贴图
1. 纹理贴图 在Three.js中,纹理贴图是一种将二维图像贴到三维物体表面的技术,以增强物体的视觉表现。纹理贴图可以使物体表面更加真实、细腻,为场景增色不少。 在Three.js中,纹理贴图的加载主要通过THREE.TextureLoader类实现。…...
mysql order by后跟case when
在SQL中,ORDER BY子句用于对查询结果进行排序。当在ORDER BY后面使用CASE语句时,它的原理是:根据CASE语句中定义的条件和结果,为查询结果集中的每一行生成一个临时的排序值。然后,根据这些排序值对结果集进行排序。 具…...
数字孪生赋能的智慧园区物联网云平台建设方案(97页PPT)
方案介绍: 本方案通过数字孪生技术赋能智慧园区物联网云平台,实现了园区的智能化管理、优化资源配置、提高运营效率等目标。同时提升园区的安全性、环保性和可持续性。最后,该方案还充分考虑了系统的可扩展性、安全性和可靠性,为…...
TikTok小店运营策略
TikTok,作为一款全球知名的短视频社交平台,其用户基数庞大且日活跃用户持续增长,为商家提供了巨大的商机。欧洲作为TikTok的重要市场之一,其小店功能为商家提供了一个展示和销售产品的新渠道。本文将探讨如何有效地运营TikTok小店…...
Docker面试整理-如何查看和管理Docker容器的日志?
管理和查看 Docker 容器的日志是 Docker 容器管理的重要部分,有助于监控应用的行为和诊断问题。Docker 提供了几种方法来查看和管理容器日志。 查看容器日志 要查看 Docker 容器的日志,你可以使用 docker logs 命令。这个命令会打印容器的 STDOUT 和 STDERR 输出,这是大多数…...
Java从放弃到继续放弃
并发编程 为什么需要多线程? 由于硬件的发展,CPU的核数增多,如果仍然使用单线程对CPU资源会造成浪费。同时,单线程也会出现阻塞的问题。所以,选择向多线程转变。 多线程的使用使得程序能够并行计算,提高计…...
Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...
YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...
c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
论文阅读笔记——Muffin: Testing Deep Learning Libraries via Neural Architecture Fuzzing
Muffin 论文 现有方法 CRADLE 和 LEMON,依赖模型推理阶段输出进行差分测试,但在训练阶段是不可行的,因为训练阶段直到最后才有固定输出,中间过程是不断变化的。API 库覆盖低,因为各个 API 都是在各种具体场景下使用。…...
什么是VR全景技术
VR全景技术,全称为虚拟现实全景技术,是通过计算机图像模拟生成三维空间中的虚拟世界,使用户能够在该虚拟世界中进行全方位、无死角的观察和交互的技术。VR全景技术模拟人在真实空间中的视觉体验,结合图文、3D、音视频等多媒体元素…...
TJCTF 2025
还以为是天津的。这个比较容易,虽然绕了点弯,可还是把CP AK了,不过我会的别人也会,还是没啥名次。记录一下吧。 Crypto bacon-bits with open(flag.txt) as f: flag f.read().strip() with open(text.txt) as t: text t.read…...
Vue 实例的数据对象详解
Vue 实例的数据对象详解 在 Vue 中,数据对象是响应式系统的核心,也是组件状态的载体。理解数据对象的原理和使用方式是成为 Vue 专家的关键一步。我将从多个维度深入剖析 Vue 实例的数据对象。 一、数据对象的定义方式 1. Options API 中的定义 在 Options API 中,使用 …...
【Zephyr 系列 16】构建 BLE + LoRa 协同通信系统:网关转发与混合调度实战
🧠关键词:Zephyr、BLE、LoRa、混合通信、事件驱动、网关中继、低功耗调度 📌面向读者:希望将 BLE 和 LoRa 结合应用于资产追踪、环境监测、远程数据采集等场景的开发者 📊篇幅预计:5300+ 字 🧭 背景与需求 在许多 IoT 项目中,单一通信方式往往难以兼顾近场数据采集…...
