Chromium源码阅读:深入理解Mojo框架的设计思想,并掌握其基本用法(1)

news2025/3/1 11:19:39

Mojo简介

Mojo 是一个运行时库的集合,提供与平台无关的通用 IPC 原语抽象、消息 IDL 格式以及具有针对多种目标语言的代码生成的绑定库,以便于跨任意进程间和进程内边界传递消息。
Mojo 分为清晰分离的层,子组件的基本层次结构如下:

请添加图片描述

分析Mojo之前,我们的思考

笔者在阅读源码前,喜欢会去思考,“如果让我来设计一个类似的功能的模块,我会怎么设计?”。然后对比文档去思考为什么会出现思路的差异,这种方式可以让我快速掌握一个开源库的设计精髓。
这次也是一样,我们想想,如果是我们自己设计Mojo,这会是什么样的架构和过程。

  1. 首先,Mojo是跨平台的,那么必然有一层platform的平台差异屏蔽层
  2. 其次,跨进程通信在不同平台上的最佳实现方案可能不一样,例如有的平台是管道,有的平台是共享内存,具体如何选择取决于不同平台的性能差异,因此,我必须将跨平台通信细节进行抽象,提取出一些概念,用于描述通信的应用层细节
  3. 之后,两个进程之间的通信之前,需要先建立连接,因此必然也需要定义一组规则和概念,来描述连接的应用层细节
  4. 为了增加通信的灵活性,我们可以定义一组观察者或者过滤器的规则,可以实现对数据流的监测和量化,也能实现更灵活的扩展。因此我们需要定义一组规则和概念来实现这个目标
  5. 为了让跨平台通信框架更加易于使用,我们需要提供一套序列化和反序列化的框架,这样可以让通信以自定义结构体的形式进行,而非数据流。
  6. 如果存在大量的通信消息,那么我们需要解决不同进程共享头文件,使用自定义结构体的形式;另外,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概念等。以下是从最底层到更高层的一些核心概念及其作用的介绍:

  1. Channel:
    Channel 是 Mojo IPC 系统中最底层的抽象。它代表了一个底层的通信通道,负责在两个进程之间传输原始的字节数据。Channel 封装了操作系统级别的 IPC 机制(如套接字或共享内存),以便在不同的平台上提供一致的 API。它负责序列化和反序列化消息,保证数据的完整性,并处理底层的传输细节。

  2. Router:
    Router 位于 Channel 之上,是一个稍高层次的抽象。它负责将发出的消息路由到正确的 Channel,并从 Channel 接收消息。Router 还管理消息的生命周期,确保消息按照正确的顺序发送和接收,并可能处理流控制和重试逻辑。

  3. InterfaceEndpointClient:
    InterfaceEndpointClient 是 Mojo IPC 中的一个组件,它代表了 Mojo 接口的端点。它与 Router 配合,以便在 Mojo 接口上发送和接收消息。通常,每个 Mojo 接口都有一个或多个方法,这些方法对应于可以通过该端点发送的消息类型。InterfaceEndpointClient 会序列化这些方法调用为消息,并将它们传递给 Router 进行传输。

  4. Message:
    Message 是代表 IPC 系统中传递的一个消息实体。它通常包含要传输的数据(例如,方法调用的参数),以及可能的元数据(例如,消息类型或优先级)。在 Mojo IPC 中,Message 是发送和接收的基本单位,由 RouterChannel 处理。

  5. ChannelMojo:
    ChannelMojoChannel 的一个具体实现,它利用 Mojo 系统的底层管道(如 MessagePipe)来传输数据。ChannelMojo 提供了一个适应 Mojo 管道特性的 Channel 接口,使得上层的 RouterInterfaceEndpointClient 能够通过 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;
#endif
    virtual 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_namepeer_port_name 成员变量:指定了事件应该从该 Port 路由到哪个节点和端口的地址。
  • prev_node_nameprev_port_name 成员变量:跟踪当前发送消息到这个 Port 的上一个端口,用于验证发送方节点是否有权限发送消息到这个端口,同时保持接收消息的顺序。
  • pending_merge_peer 成员变量:标记这个端口是否准备合并。
  • 一系列的序列号成员变量(next_control_sequence_num_to_sendnext_sequence_num_to_send
    等):用于跟踪控制和用户消息事件的序列号。
  • message_queue 成员变量:存储该 Port 接收到的用户消息队列。此队列只为 kBufferingkReceiving 状态的 Port 提供服务。
  • control_message_queue 成员变量:在 Port 处于 kBuffering 状态时,暂存即将发送的控制消息。
  • send_on_proxy_removal 成员变量:在某些边缘情况下,如果这个(代理中的)Port 被销毁,它可能需要记得路由一个特殊的事件。
  • user_data 成员变量:附加到 Port 的任意用户数据。在 Mojo 中,这通常用于存储通知有关 Port 状态变化的观察者接口。
  • remove_proxy_on_last_messagepeer_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、等功能逻辑。(未完待续…)

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

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

相关文章

GraphQL(7):ConstructingTypes

1 使用GraphQLObjectType 定义type&#xff08;类型&#xff09; 不使用ConstructingTypes定义方式如下&#xff1a; 使用ConstructingTypes定义方式如下&#xff1a; 更接近于构造函数方式 var AccountType new graphql.GraphQLObjectType({name: Account,fields: {name: …

微软Win10 21H2/22H2六月更新补丁KB5039211来了!附完整更新日志

系统之家于6月12日发出最新报道&#xff0c;微软为 Windows 10 用户发布了六月的安全更新补丁KB5039211。对于 21H2 版本的用户&#xff0c;系统版本号将升级至 19044.4529&#xff1b;而对于 22H2 版本&#xff0c;版本号则提升至 19045.4529。此次更新的亮点在于增强了系统的…

批量放大PNG图片至指定像素,画质优先,轻松提升图片品质与视觉体验!

在数字化时代&#xff0c;图片已经成为我们生活和工作中不可或缺的一部分。然而&#xff0c;很多时候我们面临着这样的问题&#xff1a;手头的Png图片尺寸太小&#xff0c;无法满足我们的需求&#xff1b;或者想要将图片放大&#xff0c;却又担心画质受损。别担心&#xff0c;现…

深度学习实战79-ChatTTS实现有感情有笑声的语音,无法分辨是人还是机器的语音

大家好,我是微学AI,今天给大介绍一下深度学习实战79-ChatTTS实现有感情有笑声的语音,本地部署运行,难以分辨是人还是机器的语音。ChatTTS是文本转语音模型,例如LLM助手对话任务。它支持英文和中文两种语言。ChatTTS模型代码已经开源,但是很多人启动不起来,会遇到各种的问…

R可视化:R语言基础图形合集

R语言基础图形合集 欢迎大家关注全网生信学习者系列&#xff1a; WX公zhong号&#xff1a;生信学习者Xiao hong书&#xff1a;生信学习者知hu&#xff1a;生信学习者CDSN&#xff1a;生信学习者2 基础图形可视化 数据分析的图形可视化是了解数据分布、波动和相关性等属性必…

深入浅出通信原理 | 通信系统中的性能指标评估

微信公众号上线&#xff0c;搜索公众号小灰灰的FPGA,关注可获取相关源码&#xff0c;定期更新有关FPGA的项目以及开源项目源码&#xff0c;包括但不限于各类检测芯片驱动、低速接口驱动、高速接口驱动、数据信号处理、图像处理以及AXI总线等 本节目录 一、通信系统中的性能指…

数据结构与算法笔记:基础篇 -递归树:如何借助树来求解递归算法的时间复杂度?

概述 我们都知道&#xff0c;递归代码的时间复杂度分析起来很麻烦。在《排序(下)》哪里讲过&#xff0c;如何用递推公式&#xff0c;求解归并排序、快速排序的时间复杂度&#xff0c;但是有些情况&#xff0c;比如快排的平均时间复杂度的分析&#xff0c;用递推公式的话&#…

远程主机强迫关闭了一个现有的连接redis

引言 在使用 Redis 进行开发和运维过程中&#xff0c;我们可能会遇到 Redis 连接被远程主机强制关闭的情况。本文将介绍造成这种情况的原因&#xff0c;并给出一些处理方法和建议。 远程主机强制关闭连接的原因 远程主机强制关闭连接通常是由于网络不稳定、连接超时、Redis 配…

Linux网络 - HTTP协议

文章目录 前言一、HTTP协议1.urlurl特殊字符 requestrespond 总结 前言 上一章内容我们讲了在应用层制定了我们自己自定义的协议、序列化和反序列化。 协议的制定相对来讲还是比较麻烦的&#xff0c;不过既然应用层的协议制定是必要的&#xff0c;那么肯定已经有许多计算机大佬…

看国足!不破不立!层次越低的家庭,语言攻击性越强——早读(逆天打工人爬取热门微信文章解读)

你昨晚看国足了吗&#xff1f; 引言Python 代码第一篇 洞见 层次越低的家庭&#xff0c;语言攻击性越强第二篇结尾 引言 昨天看了国足比赛 输了韩国一个球 剩下大概率的出线希望 除非泰国赢新加坡 且3个球或者以上 泰国稍强于新加坡 但并不到打进3个球的地步 都觉得2个球已经是…

每日5题Day22 - LeetCode 106 - 110

每一步向前都是向自己的梦想更近一步&#xff0c;坚持不懈&#xff0c;勇往直前&#xff01; 第一题&#xff1a;106. 从中序与后序遍历序列构造二叉树 - 力扣&#xff08;LeetCode&#xff09; class Solution {public TreeNode buildTree(int[] inorder, int[] postorder) {…

质数(素数)的几种判断方法

判断一个数是否为质数/合数是在数据处理中经常遇到的问题&#xff0c;如何解决这个问题&#xff0c;作者总结了如下几种算法。 质数的定义&#xff1a; 一个数如果除了1 和 其本身外&#xff0c;不能被其它数整除&#xff0c;就称这个数为质数&#xff08;或素数&#xff09;…

【计算机体系结构复习】

一.选择题&#xff08;2 X 15) 第五章第六章没有 第一章 第二章 第三章 第四章 第七章 第八章 二.主观题 第一章 1.7 1.11 怎么计算程序执行时间CPI、MIPS CPI变化&#xff0c;系统性能怎么变&#xff08;上课例子&#xff09; p9 例1.3 第二章 哈夫曼…

CVE-2012-2122-mysql未授权访问漏洞复现-vulhub

1.原理 参考&#xff1a;CVE-2012-2122 Mysql身份认证漏洞及利用-CSDN博客 简单来说&#xff0c;除了配置上的问题以外&#xff0c;是密码的验证出现了漏洞&#xff0c;导致尝试次数多了之后直接可以登入 使用&#xff1a;kalivulhub 2.复现 开一下镜像&#xff0c;用的是v…

Michael.W基于Foundry精读Openzeppelin第57期——ReentrancyGuard.sol

Michael.W基于Foundry精读Openzeppelin第57期——ReentrancyGuard.sol 0. 版本0.1 ReentrancyGuard.sol 1. 目标合约2. 代码精读2.1 constructor()2.2 modifier nonReentrant() 0. 版本 [openzeppelin]&#xff1a;v4.8.3&#xff0c;[forge-std]&#xff1a;v1.5.6 0.1 Reen…

docker-compose Install wiki

wiki 前言 最强大和可扩展的开源Wiki软件,使用Wiki.js漂亮而直观的界面,让编写文档成为一种乐趣 前提要求 安装 docker docker-compose 参考创建一键安装wiki wiki 安装目录/wikiwiki端口83admin 端口 84postgres 端口5432postgres 库 wikipostgres 用户 wikijspostgres 密…

【启明智显方案分享】ESP32-S3与GPT AI融合的智能问答嵌入式设备应用解决方案

一、引言 随着物联网&#xff08;IoT&#xff09;和人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;嵌入式设备正逐渐变得智能化。本解决方案是启明智显通过结合ESP32-S3的低功耗、高性能特性和GPT&#xff08;Generative Pre-trained Transformer&#xff09;…

简单了解java中线程的使用

线程 1、线程的相关概念 1.1、并行和并发 并行&#xff1a;在同一时刻&#xff0c;有多个任务在多个CPU上同时执行 并发&#xff1a;在同一时刻&#xff0c;有多个任务在单个CPU上交替执行 1.2、进程和线程 进程&#xff1a;就是在多任务管理系统中&#xff0c;每个独立执…

【MySQL】表的基本增删查改(结合案例)

文章目录 1.前言2.插入数据&#xff08;Create&#xff09;2.1案例2.2单行数据全列插入2.3多行数据指定列插入2.4插入否则更新2.5替换 3. 读取数据(Retireve)3.1案例3.2全列查询3.3指定列查询3.4查询字段为表达式3.5为查询结果起别名3.6去重3.7where条件3.7.1案例 3.8排序3.9筛…

Post Microsoft Build and AI Day 北京开发者日

Microsoft Build 开发者大会 Microsoft Build 开发者大会是微软每年一次的开发者技术盛会&#xff0c;旨在向全球开发者展示微软最新的技术、产品和服务。 刚刚过去的 2024 Microsoft Build 开发者大会围绕 Copilot、生成式 AI、应用程序安全、云平台、低代码等多个技术方向&a…