Fastdds学习分享_xtpes_发布订阅模式及rpc模式

        在之前的博客中我们介绍了dds的大致功能,与组成结构。本篇博文主要介绍的是xtypes.分为理论和实际运用两部分.理论主要用于梳理hzy大佬的知识,对于某些一带而过的部分作出更为详细的阐释,并在之后通过实际案例便于理解。案例分为普通发布订阅模式与rpc模式。原博客地址:https://zhuanlan.zhihu.com/p/700132625

目录

xtypes是什么?

自定义类型相关的发送/接收接口

数据筛选

类型规范是不同DDS产品互联互通的基础

        静态模式

优势

劣势

类型描述

2.1. 类型描述

Nested

key

为什么 @key 重要?

id

optional

Extensibility

基础类型

interface

容器

使用流程

1.发布订阅模式

2.命令详细说明

3.RPC模式

这里先写一个demo

server

client

域ID

QoS

Transport

Timeout(RPC 调用超时)

Topic

Threading

FastDDS RPC 可配置参数总结


xtypes是什么?

        xtypes是 DDS(Data Distribution Service) 的一个扩展,提供了一种动态和静态数据类型管理机制.以数据为中心是DDS与其他消息中间件的一个重要的区别。它类似于ros的.msg文件但是更为强大。xtypes使得DDS表现的像能够理解业务数据一样。在hzy大佬的博客中总结了以下几点特性:

  • 自定义类型相关的发送/接收接口

  • 即提交给DDS和从DDS中获取的是主题关联的自定义数据结构对象。

  • 优势
    • 序列化/反序列化的工作从应用下沉到中间件,由中间件考虑端序/对齐/不同语言类型的转换;
    • 类型检查,在编译期即可检查出部分问题;
  • 劣势
    • 使用复杂,即便是简单的收发也需要IDL编译器编译支持代码;
  • 数据筛选

  • DDS提供类似于数据库的实时数据存储与查询的功能,包括:

    • 将主题数据按照key值组织,比如订阅端可以仅读取特定key值的数据;
    • 内容过滤,即订阅端可以配置只关心某个成员范围之间的值,DDS将自动过滤不属于这个范围的主题数据;
  • 类型规范是不同DDS产品互联互通的基础

    • 产品遵循相同的规范使得能够支持的数据类型互认;
    • 数据样本序列化方式规范使得A厂家的DDS产品序列化的数据可以由B厂家的DDS产品反序列化还原成相同类型的样本数据;

我们来理解一下是什么意思,首先自定义类型的发送/接收接口是什么意思?fastdds支持两种模式,静态模式需要对应的idl文件通过fastddsgen生成.hpp与xxxtypes.hpp文件。同时xtpes也支持动态类型来发送和接受数据此时无需idl文件.

        静态模式

        我们来简单看一下普通的静态模式的idl文件是如何编写的:

module state_and_error {// 错误码请求@extensibility(MUTABLE)struct ErrorCode {string code;                   // 错误码 (如 "1001", "1002")};// 错误码解析响应@extensibility(MUTABLE)struct ErrorCodeReply {string description;            // 错误码的解析描述string suggestion;             // 修复建议};// 错误处理接口interface ErrorHandle {ErrorCodeReply analyze_error(in ErrorCode error);};
}

        上面的module就类似于C++里面的namespace,里面还有个state碍于篇幅我就没放进来,看个原理就可以了。他这里面的消息单元就是用类似结构体的方式来进行编写的。extensibility这些后面会讲到,它用于支持数据扩展性。包括后面的interface,这些都会在后面的篇幅中讲到。这里看完了静态模式,我们来观看一下动态模式是如何编写的:

#include <fastdds/xtypes/dynamic_types/DynamicTypeBuilder.hpp>
#include <fastdds/xtypes/dynamic_types/DynamicData.hpp>// 1. 创建动态数据类型
DynamicTypeBuilder* builder = DynamicTypeBuilderFactory::get_instance()->create_struct_builder();
builder->add_member(0, "id", DynamicTypeBuilderFactory::get_instance()->create_int32_type());
builder->add_member(1, "name", DynamicTypeBuilderFactory::get_instance()->create_string_type());
DynamicType_ptr myType = builder->build();// 2. 创建 `DynamicData` 数据对象
DynamicData* myData = DynamicDataFactory::get_instance()->create_data(myType);
myData->set_int32_value(0, 42);
myData->set_string_value(1, "Example");// 3. 发送数据
dds_writer->write(myData);

        这个就相当于一个写在idl文件一个写在了程序里但是他们序列化都需要fastcdr支持。以下是静态和动态的一个对比表:

对比项静态 xtypes(IDL 编译)动态 xtypes(运行时创建)
定义方式通过 .idl 文件定义运行时动态定义
是否需要编译 IDL✅ 需要❌ 不需要
数据结构变化❌ 不能在运行时修改✅ 运行时可修改
类型检查✅ 编译期检查❌ 运行时检查
适合的应用场景实时性高、结构固定结构不固定、跨 DDS 版本兼容
序列化方式DDS CDR(默认高效)可用 JSON、CBOR、DDS CDR
性能更快(直接访问编译好的类型)稍慢(需要运行时解析类型)
ROS 2适配性✅ 是 ROS 2 默认方式❌ 目前 ROS 2 不支持动态 xtypes

        我这边建议使用静态模式,因为对于rpc模式来说,动态模式并不支持interface,并且他在传递性能上较动态模式更弱。但是如果你的数据结构是 “动态的” ,在运行时种类随时可能变化时动态模式也是较好的选择。但是有mutable其实也可以用静态的。

优势

  • 优势
    • 序列化/反序列化的工作从应用下沉到中间件,由中间件考虑端序/对齐/不同语言类型的转换;
    • 类型检查,在编译期即可检查出部分问题;

        这一部分是什么意思呢?在前面我们说了他的序列化是由fastcdr中间件完成的,对于我们程序编写就不用考虑序列化问题,但这也存在一个问题。比如说如果没有自定义序列化插件,将 Protobuf之类的序列化方式转换为 DDS 兼容的数据格式,那么他就不支持其他序列化协议。这种耦合有其好处也有其坏处。像ros1这种没有将序列化下沉到中间件而是用应用层来处理的,就可以通过sfinea机制来让他兼容protobuf.有好有坏吧。类型检查这些也不必多说,常规操作。

 劣势

  • 劣势
    • 使用复杂,即便是简单的收发也需要IDL编译器编译支持代码;

        这个怎么说呢,就是常用的静态 xtypes 使用复杂,即使只是简单的消息传输,也需要 IDL 编译。而且他编译器还挺搞的,dds版本很多有些编译器支持这种dds但是不支持其他dds。有些时候有些数据结构他最新的,自己版本的编译器又不支持。升级上去,可能自己的代码有些编译就会报错。建议用稳定的就行了别折腾了。

        下面的两种,在下文中会有提及,这里就不展开讲了。

类型描述

        

2.1. 类型描述

        类型描述定义开发语言无关的各种类型的语言以及结构,具体包含的类型参见上图,协议中规定DDS主题能够关联的数据类型只包括:结构体struct以及联合体union,其他类型则作为这两种聚合类型的成员。

        除了常规的类型/成员定义外,类型系统中还为类型或者成员添加了一些标签来提供额外的信息,常见的几个标签参见下表。

标签作用对象说明
Extensibility类型用于表明该类型的可扩展性,详见2.2.
Nested类型是否直接关联到DDS主题
key成员表明成员是否为键值
optional成员表明成员是否为可选
id成员指定成员的唯一ID
boundstring/sequence/map成员表明变长结构的长度上界,主要用于空间管理

        在我前面的例子中我们可以看到我只写了拓展性,因为这些其实都不是必填的,他们都是一些可选条件。如果我们要加上限制的话,我们可以这样写,看实际需要来写吧。

        

module state_and_error {@extensibility(APPENDABLE)struct State {@key int32 status;  // `status` 作为唯一标识@id(1) double current_x;@id(2) double current_y;@id(3) double current_theta;@optional double linear_velocity;  // 这个成员是可选的@optional double angular_velocity;  // 这个成员也是可选的@bound(255) string feedback_message;  // 限制字符串最大长度为 255};
};

下面来详细讲一下,这些标签。

Nested

        他是一个类型标签(Annotation),它用于指示该类型是否可以直接用作 DDS 主题(Topic),或者它是否只能作为其他数据类型的成员如果一个struct或union被标记为 @Nested,它不能直接作为 DDS 主题(Topic)发布或订阅,只能作为其他 struct 的成员来使用了。如果不加@Nested,默认情况下struct可以直接作为 DDS 主题使用。以下是代码案例:

struct Position {double x;double y;double z;
};@Nested
struct State {int32 status;Position pos; // `@Nested` 使 `State` 只能作为 `struct` 的成员
};

key

        在 DDS 里,DDS 通过@key识别数据实例(Instance),@key 相同的数据会被认为是同一个对象,可以更新,不是新的消息。如果你不加@key,DDS 认为你的数据是无状态的消息流(类似于 UDP 广播),而如果你加了@key,DDS 就会把数据当作唯一标识的实例(类似数据库的主键)这句话怎么理解呢?当没有加@key的时候:

struct SensorData {int32 id;double temperature;
};

        DDS 认为所有SensorData消息是“独立的消息流”,不会追踪id是否重复。每个消息就像 UDP 广播,没有“实例管理”机制,接收方无法分辨两个数据是否属于同一个传感器。

加@key:

struct SensorData {@key int32 id;  // 传感器的唯一标识double temperature;
};

DDS 现在认为id相同的数据是同一个“实例”,它会:

  1. 缓存最后一次收到的 id = 1 的数据(类似数据库的 UPDATE)。也就是说如果 State 结构体有 @key id,那么 DDS 会按 id 分别存储不同的实例。如果 DDS 订阅者(Subscriber)已经收到 id = 1 的数据,再次收到 id = 1 的新数据时,DDS 只会 更新 id = 1 的数据,不会新增新的条目
  2. 自动删除旧数据(可以配置数据历史策略)。DDS 允许你配置“数据历史策略”(History QoS),决定保留多少条历史记录。如果配置KEEP_LAST(1) DDS 只会保存每个id的最新数据,旧数据会自动被删除。如果配置KEEP_ALL  DDS 会保留所有历史数据,不删除。
  3. 允许 QueryCondition 进行实例查询,比如“只订阅 id = 2 的数据”。

这里展示一下怎么配置只保留最新的

DataReaderQos qos;
qos.history().kind = KEEP_LAST_HISTORY_QOS;
qos.history().depth = 1;  // 只保留最新的一条数据
reader->set_qos(qos);
为什么 @key 重要?

        如果你加了@key,DDS 知道哪些数据属于同一个实例,可以做增量更新,而不是简单的消息广播。这句话就是说

  • 如果你加了@key,DDS 就会按照 key(通常是 id)来管理数据。
  • @key 让 DDS 认为 id 相同的数据是同一个对象的“状态更新”,可以进行增量更新(类似数据库的UPDATA)。

        如果你不加@key,每个消息都是“独立的”,无法做基于 ID 的筛选、历史记录管理或 QoS 策略。但是如果@key类型相同,其他类型不同,如果拓展性没有设置mutable那么就会报错。

id

        用于mutable可扩展性模式,确保新旧版本字段顺序不同也能正确解析数据。不会影响实例管理。如果不加@id,DDS 解析数据时只能按字段顺序匹配,无法正确解析字段新增、删除或重排的情况。这句话怎么理解呢?因为拓展性的mutable允许添加新的数据,那么就需要@id确保新旧版本的数据结构,即使字段顺序不同,DDS 仍然可以正确解析,而不会误解数据格式如果不加 @id,DDS 只能按照字段的顺序解析数据,这意味着:如果字段的顺序改变,旧版本可能解析错字段,导致数据错误。如果字段被删除或新增,旧版本可能会崩溃或丢弃数据。这样,即使新版本的数据结构发生了变化,旧版本仍然可以解析它能识别的字段,不会因字段顺序变化而导致错误!

举个例子

//旧数据
@extensibility(MUTABLE)
struct State {int32 status;double x;double y;
};
//新数据
@extensibility(MUTABLE)
struct State {int32 status;double y;  // ⚠️ 位置发生变化!double x;  // ⚠️ 位置发生变化!
};

这样就会出问题,但是如果加了@id呢?

@extensibility(MUTABLE)
struct State {@id(1) int32 status;@id(2) double x;@id(3) double y;
};@extensibility(MUTABLE)
struct State {@id(1) int32 status;@id(3) double y;  // 位置变化了,但 `@id(3)` 让 DDS 知道它是 `y`@id(2) double x;  // 位置变化了,但 `@id(2)` 让 DDS 知道它是 `x`
};

这样就没问题了

optional

        他是在旧版本里面使用的,但是现在有拓展性的mutable,就没那么重要了。但是如果某个字段在新版本中可能为空,但旧版本的解析器不允许null值,optional让新系统的发布者可以选择是否发送该字段,避免影响旧系统。optional允许你在不影响旧版本的情况下逐步添加新功能。也就是说大部分时间是没用的。

Extensibility

        这一部分hzy大佬讲的非常详细,引用他的原文即可。需要了解更多dds知识的可以去上面博客去看看原博客,写的很不错。但是注意大佬写的是DDS规范,规范是一个宽泛的概念,各版本的dds具体实现可能略有不同。

DDS可扩展性分为3种,详见下表,为什么取名叫“类型演进”,因为基于APPENDABLE/MUTABLE可扩展性类型,原有系统无需做任何的代码、配置的修改,即可与新的系统(使用迭代后的新的数据类型)进行数据交互。

可扩展性说明
FINAL不可扩展,类型结构必须完全一致才能相互交换数据,用于保护已有系统。
APPENDABLE可追加,这种类型是默认的类型,新的类型是基于老的类型在后面添加成员得到,这种模式下新老数据结构关联的主题能够相互交换数据。
MUTABLE可随意变换,新的类型可将老的类型重新排序组合以及添加新的成员得到,这种模式下新老数据结构关联的主题能够相互交换数据。

FINAL可扩展性示意图

上图中下面蓝色部分代表已有运行系统,上面的橙色部分代表新建的系统,新建的发布/订阅应用将位置信息从原有的2个坐标修改为3个坐标,此时由于原有系统设置为FINAL的保护状态,新的应用无法集成到老的系统中去。

APPENDABLE可扩展性示意图

上图中下面蓝色部分代表已有运行系统,上面的橙色部分代表新建的系统,新建的发布/订阅应用将位置信息从原有的2个坐标修改为3个坐标,此时由于类型系统设置为APPENDABLE可扩展状态,老的应用不修改任何的配置以及代码,即可把新的发布/订阅应用集成到原有的系统中,老的订阅者(右下)将接收到新的发布者发布的数据,其中多出的z成员将被忽略,而新的订阅者应用(左上)将接收到老的发布端者发布的数据,其中缺少的z成员将赋予默认的值。

MUABLE可扩展性示意图

上图中下面蓝色部分代表已有运行系统,上面的橙色部分代表新建的系统,新建的发布/订阅应用将位置信息将原有的x、y坐标打乱并在中间插入一个新的成员z,此时由于类型系统设置为MUTABLE可扩展状态,老的应用不修改任何的配置以及代码,即可把新的发布/订阅应用集成到原有的系统中,老的订阅者(右下)将接收到新的发布者发布的数据,其中多出的z成员将被忽略,而新的订阅者应用(左上)将接收到老的发布端者发布的数据,其中缺少的z成员将赋予默认的值。

介绍到这里可能会产生一个疑问:既然能够支持MUTABLE类型,那所有的类型都设计成可变的类型,系统的可扩展性不就可以得到保证吗,为什么还需要支持前面两个类型?答案总结在下面的这张不同类型的优劣势中,不同类型可扩展性实现的关键技术在数据序列化中介绍。

可扩展性优势劣势
FINAL1、首先是安全,类似于Java里面把一个类声明为final禁止其他类型继承扩展;2、固定结构下数据序列化/反序列化效率高无可扩展性
MUTABLE具备很好的可扩展性结构可变带来底层序列化/反序列化需要携带更多的额外信息,导致效率变低
APPENDABLE1、具备一定的可扩展性;2、接近于固定结构序列化/反序列化效率高可扩展性有限

基础类型

idl和C++用的基本类型差不多:

类型描述示例
boolean布尔值(truefalseboolean is_active;
char单个字符(ASCII)char letter;
octet8-bit 无符号整数octet small_value;
int88-bit 有符号整数int8 small_number;
uint88-bit 无符号整数uint8 small_number;
int1616-bit 有符号整数int16 medium_number;
uint1616-bit 无符号整数uint16 medium_number;
int3232-bit 有符号整数int32 large_number;
uint3232-bit 无符号整数uint32 large_number;
int6464-bit 有符号整数int64 very_large_number;
uint6464-bit 无符号整数uint64 very_large_number;
float32-bit 单精度浮点数float temperature;
double64-bit 双精度浮点数double precise_value;

interface

        interface用于 DDS RPC(远程过程调用),类似 ROS 的 Service。后面会详细介绍

容器

FastDDS 的 xtypes 支持容器类型(Collection Types),包括:

  • sequence<T>(可变长度序列)
  • array<T, N>(固定大小数组)
  • map<K, V, N>(键值对映射,部分 DDS 实现支持)

    例子如下:

struct SensorReadings {sequence<float, 10> temperatures;  // 最多存储 10 个温度值
};
SensorReadings data;
data.temperatures().resize(5); // 运行时调整大小struct Position {array<float, 3> coordinates;  // 3D 坐标 (x, y, z)
};
Position pos;
pos.coordinates()[0] = 1.0;
pos.coordinates()[1] = 2.0;
pos.coordinates()[2] = 3.0;struct SensorMapping {map<string<10>, float, 5> sensor_data;  // 最多存储 5 个传感器数据
};
SensorMapping mapping;
mapping.sensor_data()["temperature"] = 36.5;
mapping.sensor_data()["humidity"] = 45.0;

使用流程

        

1.发布订阅模式

        我们先写idl文件,然后进入fastddsgen文件夹。

运行命令

./fastddsgen -language C++ path/to/xxx.idl -d path/to/output/

2.命令详细说明

参数作用示例
-language C++指定生成 C++ 代码(默认是 C++)./fastddsgen -language C++ xxx.idl
-d <output_path>指定输出目录./fastddsgen -d /home/user/generated_code xxx.idl
-replace覆盖旧文件,重新生成代码./fastddsgen -replace xxx.idl
-example <OS>生成完整示例(可选 Linux, Windows, Mac)./fastddsgen -example Linux xxx.idl
-help显示帮助信息./fastddsgen -help

        他会生成一系列代码。在写发布者订阅者的时候,需要.hpp文件与xxxPubSubTypes.hpp.首先需要注册类型,这里就是注册给cdr序列化协议的。

TypeSupport type_support(new Destination::Destination_sitePubSubType());
participant->register_type(type_support); 

之后就可以定义数据结构了

Destination::Destination_site data;
data.x(0);
data.y(0);

3.RPC模式

这里先写一个demo

module robot_control {interface RobotService {string get_status();boolean move_to(in double x, in double y, out string response_msg);};
};

然后我们用

fastddsgen -example C++ robot_service.idl -d /home/user/generated_code/

注意一下,有些版本他是用fastrpcgen来编译idl,需要注意一下。

他会生成

robot_controlRobotServiceProxy.hpp   // RPC 客户端(Proxy)
robot_controlRobotServiceServer.hpp  // RPC 服务端(Server)
robot_controlRobotServiceImpl.hpp    // 需要用户实现的服务逻辑
robot_controlRobotService.cxx        // FastDDS RPC 底层实现
robot_controlRobotServicePubSubTypes.hpp  // 数据类型支持

server

#include "robot_controlRobotServiceServer.hpp"class RobotServiceImpl : public robot_control::RobotServiceServer
{
public:// 实现 get_status() 方法void get_status(::eprosima::fastdds::dds::StringType& _return) override{_return = "Robot is running";  // 返回状态信息}// 实现 move_to() 方法,返回是否移动成功bool move_to(double x, double y, ::eprosima::fastdds::dds::StringType& response_msg) override{std::cout << "Moving to: (" << x << ", " << y << ")" << std::endl;if (x >= 0 && y >= 0) // 只允许正坐标{response_msg = "Move successful!";return true; // 移动成功}else{response_msg = "Invalid target position.";return false; // 移动失败}}
};int main()
{RobotServiceImpl robot_service;if (robot_service.run()){std::cout << "RPC Server is running..." << std::endl;while (true) { } // 保持运行}return 0;
}

        在 FastDDS RPC 生成的 C++ 代码中,IDL 里定义的返回值 在生成的 C++ 代码中会 被转换为void,并使用 out 参数_return 传递结果。这是 FastDDS RPC 代码生成的特性,用于避免额外的拷贝,提高性能。

client

#include "robot_controlRobotServiceProxy.hpp"int main()
{robot_control::RobotServiceProxy client;if (client.run()){std::cout << "Connected to RPC Server!" << std::endl;// 远程调用 get_status()eprosima::fastdds::dds::StringType status;client.get_status(status);std::cout << "Robot Status: " << status << std::endl;// 远程调用 move_to(),获取返回值eprosima::fastdds::dds::StringType response_msg;bool result = client.move_to(10.5, 20.8, response_msg);std::cout << "Move Result: " << (result ? "Success" : "Failure") << std::endl;std::cout << "Server Response: " << response_msg << std::endl;client.stop();}return 0;
}

        在RPC模式下你无需创建主题,域参与者,qos之类的。fastddsrpc内部都会帮你搞定,你只要拥有相同的头文件即可。

普通 DDS 需要手动做的事情FastDDS RPC 自动管理
创建 DomainParticipant✅ FastDDS 自动创建
定义 Topic✅ FastDDS 自动创建
创建 PublisherSubscriber✅ FastDDS 自动创建
管理 RequestReply 的序列化✅ FastDDS 自动管理
匹配 ClientServerDomain ID✅ FastDDS 内部处理

        但与自动管理并不代表你不能设置,比如:

域ID

client.set_domain_id(5);  // 修改 Domain ID
server.set_domain_id(5);

QoS

FastDDS 允许你设置 QoS,控制 RPC 的可靠性、历史记录等。例如:

  • RELIABLE_RELIABILITY_QOS(可靠传输,确保请求不丢失)
  • KEEP_LAST_HISTORY_QOS(保留最近的 N 条历史记录)
  • TRANSIENT_LOCAL_DURABILITY_QOS(即使 Server 断开,Client 仍然能获取数据)
eprosima::fastdds::dds::QoSSettings qos;
qos.reliability(eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS);
qos.history(eprosima::fastdds::dds::KEEP_LAST_HISTORY_QOS);
client.set_qos(qos);

Transport

        默认情况下,FastDDS 使用 UDP 进行通信。如果你想强制使用 TCP,可以这样配置:

eprosima::fastdds::dds::TransportConfig transport;
transport.use_tcp(true);
client.set_transport(transport);

Timeout(RPC 调用超时)

        如果Client调用Server超时(Server可能崩溃或网络异常),默认 FastDDS 不会一直等待,可以设置超时时间:

client.set_timeout(std::chrono::milliseconds(5000));  // 5 秒超时

如果 5 秒内 Server没有响应,RPC 调用会失败并返回错误

Topic

client.set_topic_name("MyCustomTopic");

如果你想同时运行多个不同的 RPC 服务,可以用不同的Topic进行隔离

Threading

eprosima::fastdds::dds::ThreadSettings threads;
threads.use_separate_thread(true);  // 每个 RPC 请求使用单独线程
client.set_threading(threads);

        默认情况下,FastDDS 使用单线程模式,你可以改为多线程,提高吞吐量。如果你的 RPC 请求处理速度较慢,建议开启多线程模式,以支持高并发调用。

FastDDS RPC 可配置参数总结

参数作用示例
Domain ID指定 RPC 运行的 DDS 领域client.set_domain_id(5);
QoS设置可靠性、持久性client.set_qos(qos);
Transport指定 TCP/UDP 传输client.set_transport(transport);
Timeout设置调用超时client.set_timeout(std::chrono::milliseconds(5000));
Topic手动指定 Topic 名称client.set_topic_name("MyCustomTopic");
Threading设定是否使用多线程client.set_threading(threads);

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

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

相关文章

OpenGL学习笔记(五):Textures 纹理

文章目录 纹理坐标纹理环绕方式纹理过滤——处理纹理分辨率低的情况多级渐远纹理Mipmap——处理纹理分辨率高的情况加载与创建纹理 &#xff08; <stb_image.h> &#xff09;生成纹理应用纹理纹理单元练习1练习2练习3练习4 通过上一篇着色部分的学习&#xff0c;我们可以…

unity学习26:用Input接口去监测: 鼠标,键盘,虚拟轴,虚拟按键

目录 1 用Input接口去监测&#xff1a;鼠标&#xff0c;键盘&#xff0c;虚拟轴&#xff0c;虚拟按键 2 鼠标 MouseButton 事件 2.1 鼠标的基本操作 2.2 测试代码 2.3 测试情况 3 键盘Key事件 3.1 键盘的枚举方式 3.2 测试代码同上 3.3 测试代码同上 3.4 测试结果 4…

Flink2支持提交StreamGraph到Flink集群

最近研究Flink源码的时候&#xff0c;发现Flink已经支持提交StreamGraph到集群了&#xff0c;替换掉了原来的提交JobGraph。 新增ExecutionPlan接口&#xff0c;将JobGraph和StreamGraph作为实现。 Flink集群Dispatcher也进行了修改&#xff0c;从JobGraph改成了接口Executio…

AJAX笔记进阶篇

黑马程序员视频地址&#xff1a; AJAX-Day04-01.同步代码和异步代码https://www.bilibili.com/video/BV1MN411y7pw?vd_source0a2d366696f87e241adc64419bf12cab&spm_id_from333.788.videopod.episodes&p47https://www.bilibili.com/video/BV1MN411y7pw?vd_source0a2…

利用Muduo库实现简单且健壮的Echo服务器

一、muduo网络库主要提供了两个类&#xff1a; TcpServer&#xff1a;用于编写服务器程序 TcpClient&#xff1a;用于编写客户端程序 二、三个重要的链接库&#xff1a; libmuduo_net、libmuduo_base、libpthread 三、muduo库底层就是epoll线程池&#xff0c;其好处是…

【学术投稿-2025年计算机视觉研究进展与应用国际学术会议 (ACVRA 2025)】从计算机基础到HTML开发:Web开发的第一步

会议官网&#xff1a;www.acvra.org 简介 2025年计算机视觉研究进展与应用&#xff08;ACVRA 2025&#xff09;将于2025年2月28-3月2日在中国广州召开&#xff0c;将汇聚世界各地的顶尖学者、研究人员和行业专家&#xff0c;聚焦计算机视觉领域的最新研究动态与应用成就。本次…

Rust 所有权特性详解

Rust 所有权特性详解 Rust 的所有权系统是其内存安全的核心机制之一。通过所有权规则&#xff0c;Rust 在编译时避免了常见的内存错误&#xff08;如空指针、数据竞争等&#xff09;。本文将从堆内存与栈内存、所有权规则、变量作用域、String 类型、内存分配、所有权移动、Cl…

【Git】一、初识Git Git基本操作详解

文章目录 学习目标Ⅰ. 初始 Git&#x1f4a5;注意事项 Ⅱ. Git 安装Linux-centos安装Git Ⅲ. Git基本操作一、创建git本地仓库 -- git init二、配置 Git -- git config三、认识工作区、暂存区、版本库① 工作区② 暂存区③ 版本库④ 三者的关系 四、添加、提交更改、查看提交日…

【Envi遥感图像处理】009:envi5.6设置中文界面的方法

ENVI软件从5.0版本开始,界面发生了大的变化,并开始支持中文。本文讲述envi5.6设置中文界面的方法。 文章目录 一、中文界面预览二、设置中文界面三、注意事项一、中文界面预览 以下为envi5.6新版的中文界面: 二、设置中文界面 打开英文版的envi5.6软件,首先需要从安装App…

014-STM32单片机实现矩阵薄膜键盘设计

1.功能说明 本设计主要是利用STM32驱动矩阵薄膜键盘&#xff0c;当按下按键后OLED显示屏上会对应显示当前的按键键值&#xff0c;可以将此设计扩展做成电子秤、超市收银机、计算器等需要多个按键操作的单片机应用。 2.硬件接线 模块管脚STM32单片机管脚矩阵键盘行1PA0矩阵键盘…

鸿蒙Harmony-双向数据绑定MVVM以及$$语法糖介绍

鸿蒙Harmony-双向数据绑定MVVM以及$$语法糖介绍 1.1 双向数据绑定概念 在鸿蒙&#xff08;HarmonyOS&#xff09;应用开发中&#xff0c;双向数据改变&#xff08;或双向数据绑定&#xff09;是一种让数据模型和UI组件之间保持同步的机制&#xff0c;当数据发生变化时&#x…

Chromium132 编译指南 - Android 篇(一):编译前准备

1. 引言 欢迎来到《Chromium 132 编译指南 - Android 篇》系列的第一部分。本系列指南将引导您逐步完成在 Android 平台上编译 Chromium 132 版本的全过程。Chromium 作为一款由 Google 主导开发的开源浏览器引擎&#xff0c;为众多现代浏览器提供了核心驱动力。而 Android 作…

通向AGI之路:人工通用智能的技术演进与人类未来

文章目录 引言:当机器开始思考一、AGI的本质定义与技术演进1.1 从专用到通用:智能形态的范式转移1.2 AGI发展路线图二、突破AGI的五大技术路径2.1 神经符号整合(Neuro-Symbolic AI)2.2 世界模型架构(World Models)2.3 具身认知理论(Embodied Cognition)三、AGI安全:价…

使用 DeepSeek-R1 与 AnythingLLM 搭建本地知识库

一、下载地址Download Ollama on macOS 官方网站&#xff1a;Ollama 官方模型库&#xff1a;library 二、模型库搜索 deepseek r1 deepseek-r1:1.5b 私有化部署deepseek&#xff0c;模型库搜索 deepseek r1 运行cmd复制命令&#xff1a;ollama run deepseek-r1:1.5b 私有化…

C++游戏开发实战:从引擎架构到物理碰撞

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 1. 引言 C 是游戏开发中最受欢迎的编程语言之一&#xff0c;因其高性能、低延迟和强大的底层控制能力&#xff0c;被广泛用于游戏…

计算机网络——三种交换技术

目录 电路交换——用于电话网络 电路交换的优点&#xff1a; 电路交换的缺点&#xff1a; 报文交换——用于电报网络 报文交换的优点&#xff1a; 报文交换的缺点&#xff1a; 分组交换——用于现代计算机网络 分组交换的优点&#xff1a; 分组交换的缺点 电路交换——…

35.Word:公积金管理中心文员小谢【37】

目录 Word1.docx ​ Word2.docx Word2.docx ​ 注意本套题还是与上一套存在不同之处 Word1.docx 布局样式的应用设计页眉页脚位置在水平/垂直方向上均相对于外边距居中排列&#xff1a;格式→大小对话框→位置→水平/垂直 按下表所列要求将原文中的手动纯文本编号分别替换…

小程序越来越智能化,作为设计师要如何进行创新设计

一、用户体验至上 &#xff08;一&#xff09;简洁高效的界面设计 小程序的特点之一是轻便快捷&#xff0c;用户期望能够在最短的时间内找到所需功能并完成操作。因此&#xff0c;设计师应致力于打造简洁高效的界面。避免过多的装饰元素和复杂的布局&#xff0c;采用清晰的导航…

全栈开发:使用.NET Core WebAPI构建前后端分离的核心技巧(二)

目录 配置系统集成 分层项目使用 筛选器的使用 中间件的使用 配置系统集成 在.net core WebAPI前后端分离开发中&#xff0c;配置系统的设计和集成是至关重要的一部分&#xff0c;尤其是在管理不同环境下的配置数据时&#xff0c;配置系统需要能够灵活、可扩展&#xff0c…

Linux——进程概念

目录 一、系统调用和库函数概念二、基本概念三、描述进程-PCB3.1 task_struct-PCB的一种3.2 task_ struct内容分类 四、组织进程五、查看进程六、通过系统调用获取进程标示符七、通过系统调用创建进程- fork初始7.1 fork函数创建子进程7.2 fork 之后通常要用 if 进行分流 八、进…