GoLong的学习之路,进阶,微服务之序列化协议,Protocol Buffers V3

2023-12-16

这章是接上一章,使用 RPC包 ,序列化中没有详细去讲,因为这一块需要看的和学习的地方很多。并且这一块是RPC中可以说是最重要的一块,也是性能的重要影响因子。今天这篇主要会讲其使用方式。

Protocol Buffers V3 背景以及概念

序列化是系统通信的基础组件,在 大数据 AI框架 云原生 分布式系统 中广泛使用。

当对象需要 跨进程 跨语言 跨节点传输 持久化 状态读写 复制 时,都需要进行 序列化 ,其 性能 易用性 影响 运行效率 开发效率

但是对于序列化框架而言,业内将其分为两类:静态序列化框架,动态序列化框架。

其中各有优缺点:

  • 静态序列化框架
    • 不支持对象引用和多态、需要提前生成代码等原因, 无法作为领域对象直接面向应用进行跨语言开发
    • 常见的有: protobuf flatbuffer thrift
  • 动态序列化框架
    • 提供了易用性和动态性,但 不支持跨语言,且性能存在显著不足 ,并不能满足高吞吐、低延迟和大规模数据传输场景需求
    • 常见的有: JDK序列化 Kryo Fst Hessian Pickle

但是前几天阿里推出了Fury,号称比 JDK快了170倍 ,并且兼具静态序列化和动态序列化的优点。这个下一章会讲。目前的 gRPC框架 是用 protobuf 搭建的。所以咱们先让自己的项目成功运行起来再说。所以这个后面再说。

Protocol Buffers google开源 的一种结构数据序列化机制,可跨语言、跨平台。

相比 XML JSON Thrift 等其他序列化格式,Protocol Buffers的序列化和反序列化性能是很高的,且Protocol Buffers序列化后是二进制流,因此数据大小和传输速度是很好的。

所以它非常适合在 数据存储 RPC 数据交换 的场景下使用。

以下使用手法是翻译自 官网

如何使用?

定义一个 .proto 文件

定义一个搜索请求消息格式,其中每个搜索请求都包含:

  • 一个查询词字符串
  • 你感兴趣的查询结果所在的特定页码数
  • 每一页应展示的结果数
syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
  • 文件的第一行指定使用 proto3 语法。

如果不这样写,protocol buffer编译器将假定你使用 proto2。这个声明必须是文件的第一个非空非注释行。

  • SearchRequest 消息定义指定了三个字段(名称/值对) ,每个字段表示希望包含在此类消息中的每一段数据。每个字段都有一个名称和一个类型

指定字段类型:

  • 两个整数 page_number result_per_page 和一个字符串 query (上面的例子)
  • 但是也可以为字段指定组合类型,包括枚举和其他消息类型

proto3语法主要包括:

  1. 消息( message )定义
  2. 服务( service )定义
  3. 其他部分语法

一个message内的字段一般包含:

  • 数据类型
  • 字段名
  • 字段编号tag

添加更多消息类型:

可以在一个 .proto 文件中定义多个消息类型。定义与 SearchRequest 消息类型对应的应答消息格式SearchResponse,就可以将其添加到同一个.proto文件中。

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

message SearchResponse {
 ...
}

添加注释:

要给你的 .proto 文件添加注释,需要使用 C/C++ 风格的 // /* ... */ 语法。

保留字段:

  • 如果通过完全删除字段或将其注释掉来更新消息类型,那么未来的用户在对该类型进行自己的更新时可以重用字段号。
  • 如果其他人以后加载旧版本的相同 .proto 文件,这可能会导致严重的问题,包括数据损坏,隐私漏洞等等。

分配字段编号

消息定义中的每个字段都有一个 唯一的编号

这些字段编号用来在消息二进制格式中标识字段,在消息类型使用后 就不能再更改

注意

  • 范围 1 15 中的字段编号需要 一个字节 进行编码,包括字段编号和字段类型
  • 范围 16 2047 的字段编号采用 两个字节

应该为经常使用的消息元素保留数字1到15的编号。切记为将来可能添加的经常使用的元素留出一些编号。

可以指定的最小字段数是 1 ,最大的字段数是 2 29 − 1 可以指定的最小字段数是1,最大的字段数是 2^{29}−1 可以指定的最小字段数是 1 ,最大的字段数是 2 29 1
即536,870,911。

也不能使用 19000 19999 ,它们是预留给 Protocol Buffers 协议实现的。

如果你在你的 .proto 文件中使用了预留的编号 Protocol Buffers 编译器就会报错。

同样,你也不能使用任何之前保留的字段编号。

指定字段规则

消息字段可以是下列字段之一:

  • singular : 格式正确的消息可以有这个字段的零个或一个(但不能多于一个)。这是 proto3语法的默认字段规则。
  • repeated : 该字段可以在格式正确的消息中重复任意次数(包括零次)。重复值的顺序将被保留。

确保这种情况不会发生的一种方法是 指定已删除字段 的字段编号(和 / 名称 ,这也可能导致 JSON 序列化问题)是保留的 reserved

如果将来有任何用户尝试使用这些字段标识符, protocol buffer编译器 将发出提示。

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

注意:不能在同一个 reserved 语句中 混合字段名 字段编号

当你使用 protocol buffer 编译器 来运行 .proto文件 时,编译器用你选择的语言生成你需要使用文件中描述的消息类型,包括获取和设置字段值,将消息序列化为输出流,以及从输入流解析消息的代码。

  • C++ 来说,编译器会为每个.proto文件生成一个.h文件和一个.cc文件,.proto文件中的每一个消息有一个对应的类。
  • 对于 Java ,编译器生成一个.java 文件,每种消息类型都有一个类,还有一个特殊的 Builder 类用于创建消息类实例。
  • 对于 Kotlin ,除了 Java 生成的代码之外,编译器还生成一个每种消息类型的 .kt 文件,包含一个 DSL,可用于简化消息实例的创建。
  • Python 稍有不同ー Python 编译器为.proto文件中的每个消息类型生成一个带静态描述符的模块,然后与 metaclass 一起使用,在运行时创建必要的 Python 数据访问类。
  • 对于 Go ,编译器为文件中的每种消息类型生成一个类型(type)到一个.pb.go 文件。
  • 对于 Ruby ,编译器生成一个.rb 文件,其中包含一个包含消息类型的 Ruby 模块。
  • 对于 Objective-C ,编译器从每个.proto文件生成一个 pbobjc.h 和 pbobjc.m 文件,.proto文件中描述的每种消息类型都有一个类。
  • 对于 C# ,编译器生从每个.proto文件生成一个.cs 文件。.proto文件中描述的每种消息类型都有一个类。
  • 对于 Dart ,编译器为文件中的每种消息类型生成一个.pb.dart 文件。

数据类型

数据类型: 标量类型 复合类型

标量类型

在这里插入图片描述
当解析消息时,如果编码消息不包含特定的 singular 元素,则解析对象中的相应字段将设置为该字段的默认值。

  • 对于字符串,默认值为空字符串。
  • 对于字节,默认值为空字节。
  • 对于布尔值,默认值为 false。
  • 对于数值类型,默认值为零。
  • 对于枚举,默认值是第一个定义的枚举值,该值必须为0。
  • 对于消息字段,未设置该字段。其确切值与语言有关。
  • repeated 字段的默认值为空(通常是适当语言中的空列表)。

请注意 ,对于标量消息字段,一旦消息被解析,就无法判断字段是显式设置为默认值(例如,是否一个布尔值是被设置为 false )还是根本没有设置: 在定义消息类型时应该牢记这一点。例如,如果你不希望某个行为在默认情况下也发生,那么就不要设置一个布尔值,该布尔值在设置为 false 时会开启某些行为。还要注意,如果将标量消息字段设置为默认值,则该值将不会在传输过程中序列化。

复合类型

复合类型包括:枚举、嵌套其他message、Any(Map,Oneof)等

枚举

在定义消息类型时,你可能希望其中一个字段只能是预定义的值列表中的一个值。

可以通过在消息定义中添加一个枚举,为每个可能的值添加一个常量来非常简单地完成这项工作。

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 4;
}

Corpus enum 的第一个常量映射为零: 每个 enum 定义必须包含一个常量,该常量映射为零作为它的第一个元素。

  1. 必须有一个零值,这样我们就可以使用0作为数值默认值。
  2. 零值必须是第一个元素,以便与 proto2语义兼容,其中第一个枚举值总是默认值。

你可以通过将相同的值分配给不同的枚举常量来定义别名。为此,你需要将 allow _ alias 选项设置为 true ,否则,当发现别名时, protocol 编译器 将生成错误消息。

内部定义

message MyMessage1 {
  enum EnumAllowingAlias {
    option allow_alias = true;
    UNKNOWN = 0;
    STARTED = 1;
    RUNNING = 1;
  }
}
message MyMessage2 {
  enum EnumNotAllowingAlias {
    UNKNOWN = 0;
    STARTED = 1;
    // RUNNING = 1;  // Uncommenting this line will cause a compile error inside Google and a warning message outside.
  }
}

注意:枚举的常数必须在32位整数的范围内。

由于枚举值在传输时使用 变长编码 ,因此负值效率低,因此不推荐使用。

可以在消息定义中定义枚举,如上面的例子所示,也可以在外面定义——这样就可以在 .proto 文件中的消息定义中重用这些枚举。

外部定义

enum Ezarten {
        option allow_alias = true; //开启枚举值重复开关
        ZARTEN1 = 0;
        ZARTEN2 = 1;
        ZARTEN3 = 2;
        ZARTEN4 = 2; //开启option allow_alias = true后枚举值可以重复
    }

//定义一个message类型
message ZartenOne {
    string name = 1;
    int32 age = 2;
    int32 height = 3;
    Ezarten ezarten = 4;
}

可以使用 _MessageType_._EnumType_ 语法,使用在一个消息中声明的 enum类型 作为不同消息中的字段类型。

使用消息内的枚举
若枚举定义在内部,其他message要使用这个枚举,可以使用 “message名.枚举名”的形式:

//定义一个message类型
message ZartenOne {
    string name = 1;
    int32 age = 2;
    int32 height = 3;
    enum Ezarten {
        ZARTEN1 = 0;
        ZARTEN2 = 1;
        ZARTEN3 = 2;
    }
    Ezarten ezarten = 4;
}

//定义一个message类型
message ZartenTwo {
    string name = 1;
    int32 age = 2;
    int32 height = 3;
    ZartenOne.Ezarten ezarten = 4;
}

当对一个使用了枚举的 .proto文件 运行 protocol buffer 编译器的时候,对于 Java Kotlin ,或 C++ 生成的代码中将有一个对应的 enum ,或者对于 Python 会生成一个特殊的 EnumDescriptor 类,它被用于在运行时生成的类中创建一组带有整数值的符号常量。

生成的代码可能会受到特定于语言的枚举数限制(单种语言的数量低于千)

反序列化 过程中,不可识别的枚举值将保留在消息中,尽管当消息被反序列化时,这种值的表示方式 依赖 于语言。

  • 开放枚举:在支持值超出指定符号范围 (如 C++ 和 Go) 开放枚举类型 的语言中,未知枚举值仅存储为其底层的整数表示形式。
  • 闭合枚举类型:在具有 闭合枚举类型 (如 Java) 的语言中,枚举中的一个类型将用于表示一个无法识别的值,并且可以使用特殊的访问器访问底层的整数。

在这两种情况下,如果消息被序列化,那么不可识别的值仍然会与消息一起被序列化。

其他消息类型

你可以使用其他消息类型作为字段类型:

message SearchResponse {
  repeated Result results = 1;
}

message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3;
}

如果你希望用作字段类型的消息类型已经在另一个.proto文件中定义了,该怎么办?

你可以通过 import 来使用来自其他 .proto 文件的定义。要导入另一个 .proto 的定义,你需要在文件顶部添加一个 import 语句

import "myproject/other_protos.proto";

默认情况下,只能从直接导入的 .proto 文件中使用定义。但是,有时你可能需要将 .proto 文件移动到新的位置。你可以在旧目录放一个占位的 .proto 文件使用 import public 概念将所有导入转发到新位置,而不必直接移动 .proto 文件并修改所有的地方。

import public 依赖项可以被任何导入包含 import public 语句的 proto 的代码传递依赖。
语法:

  • import
  • import public
  1. 相同的是,在 文件A 中两者可以直接引用它们上一级proto文件B的内容。
  2. 不同的是,若文件B内使用了 import 引用文件C,则文件A不能使用文件C的内容;

若文件B内使用了import public引用文件C,则文件A可以使用文件C的内容。

类似于编程语言中的类是否可以继承的含义。

文件: new.proto

所有的定义都被移到了这里

文件: old.proto
这是所有客户端都要导入的原型

import public "new.proto";
import "other.proto";

文件: client.proto
你可以使用 old.proto new.proto ,但是不能使用 other.proto

protocol 编译器使用命令行 -I/--proto_path 参数指定的一组目录中搜索导入的文件。如果没有给该命令行参数,则查看调用编译器的目录。

一般来说,你应该将 --proto_path 参数设置为项目的根目录并为所有导入使用正确的名称。

嵌套类型

方式1:

message SearchResponse {
  message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  repeated Result results = 1;
}

方式2:
要在其父消息类型之外重用此消息类型,通过_Parent_._Type_使用。

message SomeOtherMessage {
  SearchResponse.Result result = 1;
}

方式3:一层又一层嵌入其中

message Outer {                  // Level 0
  message MiddleAA {  // Level 1
    message Inner {   // Level 2
      int64 ival = 1;
      bool  booly = 2;
    }
  }
  message MiddleBB {  // Level 1
    message Inner {   // Level 2
      int32 ival = 1;
      bool  booly = 2;
    }
  }
}
更新消息类型

在不破坏任何现有代码的情况下更新消息类型非常简单:只需记住以下规则:

  • 不要更改任何现有字段的字段编号
  • 如果添加新字段
    • 那么任何使用“旧”消息格式通过代码序列化的消息仍然可以通过新生成的代码进行解析。
      • 你应该记住这些元素的 默认值 ,以便新代码能够正确地与旧代码生成的消息交互。
      • 类似地,新代码创建的消息可以通过旧代码解析: 旧的二进制文件在解析时直接忽略新字段。
  • 字段可以被删除,只要字段编号不再用于你更新的消息类型。
    • 你可能希望改为重命名字段,或者为其添加" OBSOLETE_ “前缀,或者声明字段编号为 reserved ,以便 .proto 的未来用户不可能不小心重复使用这个编号。
  • int32 uint32 int64 uint64 bool 都是兼容的——这意味着你可以在不破坏向前或向后兼容性的情况下将一个字段从这些类型中的一个更改为另一个。
  • 如果一个数字被解析到一个并不适当的类型中,你会得到与在 C++ 中将数字转换为该类型相同的效果
    • 例如,如果一个64位的数字被读作 int32,它将被截断为32位
  • sint32 sint64 相互兼容,但与其他整数类型不兼容
  • string bytes 是兼容的,只要字节是有效的 UTF-8
  • 如果字节包含消息的编码版本,则 嵌入的消息与bytes兼容
  • fixed32 sfixed32 兼容 fixed64 sfixed64 兼容。
  • 对于 string bytes 消息字段 optional 字段与 repeated 字段兼容。
    • 给定重复字段的序列化数据作为输入
      • 如果该字段是 基本类型 字段,期望该字段为可选字段的客户端将接受最后一个输入值
      • 如果该字段是 消息类型 字段,则合并所有输入元素
    • 注意,这对于数字类型,包括 bools enums 通常是不安全的。
      • 重复的数值类型字段可以按 packed 的格式序列化,如果是 optional 字段,则无法正确解析这些字段
  • Enum 在格式方面与 int32 uint32 int64 uint64 兼容(请注意,如果不适合,值将被截断)。
    • 但是要注意,当消息被反序列化时,客户端代码可能会区别对待它们:
      • 例如,未被识别的 proto3 enum 将保留在消息中,但是当消息被反序列化时,这种类型的表示方式依赖于语言。 Int 字段总是保留它们的值。
  • 将单个值更改为新的 oneof 成员是安全的,并且二进制兼容。
    • 如果确保没有代码一次设置多个字段,那么将 多个字段 移动到 新的oneof字段 中可能是安全的。
    • 将任何字段移动到现有的字段中都是不安全的。
未知字段

未知字段是格式良好的协议缓冲区序列化数据,表示解析器不识别的字段。
旧二进制 解析由新二进制发送的带 有新字段 的数据时,这些 新字段 将成为旧二进制中的 未知字段

3.5版本 中,我们重新引入了未知字段的保存来匹配 proto2 行为。在3.5及以后的版本中, 解析期间保留未知字段,并将其包含在序列化输出中

Any

Any 消息类型允许你将消息作为嵌入类型使用,而不需要其 .proto 定义。 Any 包含一个任意序列化的字节消息,以及一个解析为该消息的类型作为消息的全局唯一标识符的URL

要使用 Any 类型,需要导入 google/protobuf/any.proto

给定消息类型的默认类型 URL type.googleapis.com/_packagename_._messagename_

import "google/protobuf/any.proto";

message ErrorStatus {
  string message = 1;
  repeated google.protobuf.Any details = 2;
}

不同的语言实现将支持运行库助手以类型安全的方式打包和解包 Any值:

  • 在java中,Any类型会有特殊的pack()和unpack()访问器,

  • 在C++中,Any类型会有特殊的PackFrom()和UnpackTo()方法。

oneof

如果你有一条包含多个字段的消息,并且 最多 同时设置其中一个字段,那么你可以通过使用 oneof 来实现并节省内存, 优化

oneof 字段类似于常规字段,只不过 oneof 中的所有字段共享内存,而且最多可以同时设置一个字段。

设置其中的任何成员都会自动清除所有其他成员。

根据所选择的语言,可以使用特殊 case() WhichOneof() 方法检查 oneof 中的哪个值被设置

在生成的代码中,其中一个字段具有与常规字段相同的 getter setter

你还可以获得一个特殊的方法来检查其中一个设置了哪个值

message SampleMessage {
  oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
  }
}

然后将其中一个字段添加到该字段的定义中。

你可以添加任何类型的字段,除了 map字段 repeated 字段

oneof有很多特性,具体的个人建议去看 文档

oneof 特性
  • 设置一个字段将自动清除该字段的所有其他成员。
    • 因此,如果你设置了多个 oneof字段,那么只有最后设置的字段仍然具有值。
  • 如果解析器在连接中遇到同一个成员的多个成员,则只有最后看到的成员用于解析消息。
  • oneof 不支持repeated。
  • 反射 api 适用于 oneof 字段。
  • 如果将 oneof 字段设置为默认值(例如将 int32 oneof 字段设置为0) ,则将设置该字段的“ case”,并在连接上序列化该值。
  • 如果使用 C++ ,确保你的代码不会导致内存崩溃。
    • 因为通过调用 set_name()方法已经删除了 sub_message
SampleMessage message;
SubMessage* sub_message = message.mutable_sub_message();
message.set_name("name");      // 删除name
sub_message->set_...            // 这里的崩溃
  • 在C++中,如果你使用 Swap() 两个 oneof 消息,每个消息,两个消息将拥有对方的值
SampleMessage msg1;
msg1.set_name("name");
SampleMessage msg2;
msg2.mutable_sub_message();
msg1.swap(&msg2);
CHECK(msg1.has_sub_message());
CHECK(msg2.has_name());
向后兼容性问题

添加或删除一个字段时要小心。

  • 如果检查 one of 的值返回 None / NOT_SET ,这可能意味着 one of 没有被设置,或者它已经被设置为 one of 的不同版本中的一个字段。
  • 这没有办法区分,因为没有办法知道未知字段是否是 oneof 的成员。

标签重用问题:

  • 将字段 移入 移出 oneof :在序列化和解析消息之后,你可能会丢失一些信息(某些字段将被清除)。但是,你可以安全地将单个字段移动到新的 oneof 字段中,并且如果已知只设置了一个字段,则可以移动多个字段。
  • 删除一个 oneof 字段再添加回来 :这可能会在消息被序列化和解析后清除当前设置的 oneof 字段。
  • 拆分或合并 oneof :这与移动常规字段有类似的问题。
Maps

如果你想创建一个关联映射作为你数据定义的一部分。

map<key_type, value_type> map_field = N;

  • 其中 key_type 可以是任何整型或字符串类型( 除了浮点类型和字节以外的任何标量类型 )
  • value_type 可以是除另一个映射以外的任何类型。
map<string, Project> projects = 3;
  • 映射字段不能重复。
  • 映射值的有线格式排序和映射迭代排序是未定义的,因此不能依赖于映射项的特定排序。
  • 当为 .proto 生成文本格式时,映射按键排序。数字键按数字排序。
  • 当从连接解析或合并时,如果有重复的映射键,则使用最后看到的键。当从文本格式解析映射时,如果有重复的键,解析可能会失败
  • 如果为映射字段提供了键但没有值,则该字段序列化时的行为与语言相关。在 C++ Java Kotlin Python 中,类型的默认值是序列化的,而在其他语言中,没有任何值是序列化的。

JSON 映射

proto3 支持 JSON 的规范编码,使得系统之间更容易共享数据。下表按类型逐一描述了编码。

如果 json 编码 的数据中缺少某个值,或者该值为 null,那么在解析为 protocol buffer 时,该值将被解释为适当的默认值。如果一个字段在 protocol buffer 中具有默认值,为了节省空间,默认情况下 json 编码的数据中将省略该字段。具体实现可以提供在 JSON编码 中可选的默认值。

在这里插入图片描述
在这里插入图片描述
一个proto3协议 JSON 实现可能提供以下选项:

  • 提供默认值的字段 在proto3 JSON 输出中 ,值为默认值的字段被省略。可以提供一个选项,用默认值覆盖此行为和输出字段。
  • 忽略位置字段 在缺省情况下 ,Proto3 JSON 解析器应该拒绝未知字段,但在解析过程中可能会提供一个忽略未知字段的选项。
  • 使用 proto 字段名而不是小驼峰名称 默认情况下 ,proto3 JSON 打印机应该将字段名转换为 lowerCamelCase ,并使用它作为 JSON 名称
    • 可以提供一个选项,用原型字段名作为 JSON 名。需要 协议3 JSON 解析器同时接受转换后的 lowerCamelCase `名称和原始字段名称。
  • 以整数而不是字符串形式展示枚举值 在 JSON 输出中 ,默认情况下使用枚举值的名称。可以提供一个选项来代替使用枚举值的数值。

剩下的可以看: 官方文档

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

GoLong的学习之路,进阶,微服务之序列化协议,Protocol Buffers V3 的相关文章

随机推荐

  • 【UE5】初识MetaHuman 创建虚拟角色

    步骤 在UE5工程中启用 Quixel Bridge 插件 打开 Quixel Bridge 点击 MetaHumans MetaHuman Presets UE5 点击 START MHC 在弹出的网页中选择一个虚幻引擎版本 然后点击 启
  • 【UE5】监控摄像头效果(下)

    目录 效果 步骤 一 多摄像机视角切换 二 摄像头自动旋转巡视 三 摄像头跟踪拍摄 效果 步骤 一 多摄像机视角切换 1 打开玩家控制器 MyPlayerController 添加一个变量 命名为 BP SecurityCameraArra
  • 【UE5.2】通过Water插件使物体漂浮在水面上

    效果 步骤 1 新建一个工程 创建一个Basic关卡 添加初学者内容包到内容浏览器 2 在插件中启用 Water 插件 然后重启工程 3 重启后提示 碰撞描述文件设置不包括水体碰撞描述文件的条目 水碰撞必须使用该描述文件才能正常工作 将条目
  • 软件测试/测试开发丨人工智能算法的基本原理,如何解决实际的问题

    人工智能 AI 算法的基本原理涉及模仿人类智能行为的计算机程序和模型 这些算法通常通过学习和适应从数据中提取规律来解决实际问题 以下是一些常见的人工智能算法以及它们的基本原理 监督学习算法 图像识别 问题 识别图像中的数字 算法 使用卷积神
  • 【UE】在蓝图中修改材质实例的参数的两种方式

    目录 方式一 通过 在材质上设置标量 向量参数值 节点实现 方式二 通过 设置标量 向量参数值 节点实现 方式一 通过 在材质上设置标量 向量参数值 节点实现 1 在材质中设置了两个参数 2 创建材质实例 3 创建一个蓝图 对静态网格体赋予
  • 【UE】制作物体逐渐溶解消失并且可以复原的效果

    效果 步骤 1 新建一个工程 创建一个Basic关卡 添加第三人称游戏和初学者内容包资源到内容浏览器 2 找到并打开初学者内容包中椅子的材质 M Chair 将混合模式改为 已遮罩 在材质图表中添加如下节点 此时我们就可以通过参数 Fade
  • 【UE 材质】角色触碰空气墙效果

    效果 步骤 1 新建一个工程 创建一个Basic关卡 添加一个第三人称游戏资源到内容浏览器 2 新建一个材质参数集 这里命名为 MPC Vector 打开 MPC Vector 添加一个向量参数 3 新建一个材质 这里命名为 M Wall
  • 【UE5.1 MetaHuman】使用mixamo_converter把Mixamo的动画重定向给MetaHuman使用

    目录 前言 效果 步骤 一 下载mixamo converter软件 二 Mixamo动画重定向 三 导入UE 四 动画重定向 五 使用重定向后的动画 前言 上一篇 UE5 初识MetaHuman 创建虚拟角色 中我们已经制作了一个Meta
  • 【UE】制作熔岩星球材质

    效果 步骤 1 新建一个工程 创建一个Basic关卡 添加第三人称游戏和初学者内容包资源到内容浏览器 2 新建一个材质 这里命名为 M Sun 打开 M Sun 添加两个Texture节点 纹理分别为 T Rock Basalt N 和 T
  • 【UE5】监控摄像头效果(上)

    目录 效果 步骤 一 视角切换 二 摄像头画面后期处理 三 在场景中显示摄像头画面 效果 步骤 一 视角切换 1 新建一个Basic关卡 添加第三人称游戏资源到项目浏览器 2 新建一个Actor蓝图 这里命名为 BP SecurityCam
  • pico示波器使用

    文章目录 Pico示波器保存波形 Pico示波器录制数据 Pico示波器解析CAN报文 Pico示波器保存波形 Pico示波器可以通过以下步骤保存波形 在示波器上选择要保存的波形 连接示波器到计算机上 可以使用USB或者Ethernet连接
  • 头歌——HBase 开发:使用Java操作HBase

    第1关 创建表 题目 任务描述 本关任务 使用 Java 代码在 HBase 中创建表 相关知识 为了完成本关任务 你需要掌握 1 如何使用 Java 连接 HBase 数据库 2 如何使用 Java 代码在 HBase 中创建表 如何使用
  • 【UE5】瞬移+马赛克过渡效果

    效果 步骤 1 新建一个工程 创建一个Basic关卡 2 添加第三人称游戏资源到内容浏览器 3 新建一个材质 这里命名为 M Pixel 打开 M Pixel 设置材质域为 后期处理 在材质图表中添加如下节点 此时效果如下 已经有马赛克的效
  • 【3DsMax】制作简单的骨骼动画

    效果 步骤 首先准备4个板子模型展开放置好 添加一个4段的骨骼 选中其中的一块板子添加蒙皮命令 在蒙皮的参数面板中 设置每块板子对应哪块骨骼 设置好后你可以发现此时就已经可以通过骨骼来控制模型了 接下来就可以制作动画 点击左下角 时间配置
  • 【UE】制作地月全息投影

    效果 步骤 1 在必应国际版上搜索 purlin noise 下载如下所示图片 再搜索 Earth Map 下载如下所示图片 再搜索 Moon 360 下载如下所示图片 这三张图片的资源链接如下 链接 https pan baidu com
  • python在车载电子测试方面的应用笔记【1】

    文章目录 在DataFrame中某列插入数据 并根据另一列查找是否存在某个字符串完全一样 在另一列插入对应数据的功能 删除DataFrame某列数据长度大于6的数据 使用 PyInstaller 打包成一个独立的 exe 文件 通过检索空格
  • 通过kubeadm方式安装k8s

    虚拟机最少是 2 core master内存最小3G node内存最小2G 要求的Docker版本是18 03 如果不是安装的docker ce 版本是过旧的 可以选择删除后重新安装 也可以重新创建一个虚拟机执行以下命令 简单方法 使用ma
  • Docker build 无法解析域名

    报错 Docker build 无法解析域名 报错 ERROR 2 12 RUN curl o etc yum repos d CentOS Base repo https mirrors aliyun com repo Centos 7
  • 安装 运行 gemmini 和chipyard

    安装gemmini 和chipyard过程 安装版本 chipyard 版本是1 8 1 gemmini版本0 7 0 tip 如果在base里安装conda lock觉得缓慢 可以新建新的环境时就指定安装conda lock conda
  • GoLong的学习之路,进阶,微服务之序列化协议,Protocol Buffers V3

    这章是接上一章 使用 RPC包 序列化中没有详细去讲 因为这一块需要看的和学习的地方很多 并且这一块是RPC中可以说是最重要的一块 也是性能的重要影响因子 今天这篇主要会讲其使用方式 文章目录 Protocol Buffers V3 背景以