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);

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Windows:AList+RaiDrive挂载阿里云盘至本地磁盘

零、前言 电脑存储的文件多了&#xff0c;出现存储空间不够用的情况。又没前买新的硬盘或者笔记本电脑没有额外的插槽提供给新的硬盘。遇到这种情况&#xff0c;我想到可以使用网盘&#xff0c;但单纯的网盘又要上传下载&#xff0c;极其麻烦。看到WPS云盘可以直接挂载本地&…

Unity游戏(Assault空对地打击)开发(3) 摄像机的控制

详细步骤 打开My Assets或者Package Manager。 选择Unity Registry。 搜索Cinemachine&#xff0c;找到 Cinemachine包&#xff0c;点击 Install按钮进行安装。 关闭窗口&#xff0c;新建一个FreeLook Camera&#xff0c;如下。 接着新建一个对象Pos&#xff0c;拖到Player下面…

保姆级教程Docker部署Zookeeper官方镜像

目录 1、安装Docker及可视化工具 2、创建挂载目录 3、运行Zookeeper容器 4、Compose运行Zookeeper容器 5、查看Zookeeper运行状态 6、验证Zookeeper是否正常运行 1、安装Docker及可视化工具 Docker及可视化工具的安装可参考&#xff1a;Ubuntu上安装 Docker及可视化管理…

力扣73矩阵置零

给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 输入&#xff1a;matrix [[1,1,1],[1,0,1],[1,1,1]] 输出&#xff1a;[[1,0,1],[0,0,0],[1,0,1]] 输入&#xff1a;matrix [[0,1,2,0],[3,4,5,2],[…

【ArcGIS_Python】使用arcpy脚本将shape数据转换为三维白膜数据

说明&#xff1a; 该专栏之前的文章中python脚本使用的是ArcMap10.6自带的arcpy&#xff08;好几年前的文章&#xff09;&#xff0c;从本篇开始使用的是ArcGIS Pro 3.3版本自带的arcpy&#xff0c;需要注意不同版本对应的arcpy函数是存在差异的 数据准备&#xff1a;准备一个…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.19 线性代数核武器:BLAS/LAPACK深度集成

2.19 线性代数核武器&#xff1a;BLAS/LAPACK深度集成 目录 #mermaid-svg-yVixkwXWUEZuu02L {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-yVixkwXWUEZuu02L .error-icon{fill:#552222;}#mermaid-svg-yVixkwXWUEZ…

记8(高级API实现手写数字识别

目录 1、Keras&#xff1a;2、Sequential模型&#xff1a;2.1、建立Sequential模型&#xff1a;modeltf.keras.Sequential()2.2、添加层&#xff1a;model.add(tf.keras.layers.层)2.3、查看摘要&#xff1a;model.summary()2.4、配置训练方法&#xff1a;model.compile(loss,o…

RK3568中使用QT opencv(显示基础图像)

文章目录 一、查看对应的开发环境是否有opencv的库二、QT使用opencv一、查看对应的开发环境是否有opencv的库 在开发板中的/usr/lib目录下查看是否有opencv的库: 这里使用的是正点原子的ubuntu虚拟机,在他的虚拟机里面已经安装好了opencv的库。 二、QT使用opencv 在QT pr…

el-table表格点击单元格实现编辑

使用 el-table 和 el-table-column 创建表格。在单元格的默认插槽中&#xff0c;使用 div 显示文本内容&#xff0c;单击时触发编辑功能。使用 el-input 组件在单元格中显示编辑框。data() 方法中定义了 tableData&#xff0c;tabClickIndex: null,tabClickLabel: ,用于判断是否…

idea隐藏无关文件

idea隐藏无关文件 如果你想隐藏某些特定类型的文件&#xff08;例如 .log 文件或 .tmp 文件&#xff09;&#xff0c;可以通过以下步骤设置&#xff1a; 打开设置 在菜单栏中选择 File > Settings&#xff08;Windows/Linux&#xff09;或 IntelliJ IDEA > Preference…

备考蓝桥杯嵌入式4:使用LCD显示我们捕捉的PWM波

上一篇博客我们提到了定时器产生PWM波&#xff0c;现在&#xff0c;我们尝试的想要捕获我们的PWM波&#xff0c;测量它的频率&#xff0c;我们应该怎么做呢&#xff1f;答案还是回到我们的定时器上。 我们知道&#xff0c;定时器是一个高级的秒表&#xff08;参考笔者的比喻&a…

ChatGPT-4o和ChatGPT-4o mini的差异点

在人工智能领域&#xff0c;OpenAI再次引领创新潮流&#xff0c;近日正式发布了其最新模型——ChatGPT-4o及其经济实惠的小型版本ChatGPT-4o Mini。这两款模型虽同属于ChatGPT系列&#xff0c;但在性能、应用场景及成本上展现出显著的差异。本文将通过图文并茂的方式&#xff0…

Codeforces Round 1002 (Div. 2)(部分题解)

补题链接 A. Milya and Two Arrays 思路&#xff1a;题意还是比较好理解&#xff0c;分析的话我加了一点猜的成分&#xff0c;对a&#xff0c;b数组的种类和相加小于4就不行&#xff0c;蒋老师的乘完后小于等于2也合理。 AC代码&#xff1a; #include <bits/stdc.h> u…

机器学习中的关键概念:通过SKlearn的MNIST实验深入理解

欢迎来到我的主页&#xff1a;【Echo-Nie】 本篇文章收录于专栏【机器学习】 1 sklearn相关介绍 Scikit-learn 是一个广泛使用的开源机器学习库&#xff0c;提供了简单而高效的数据挖掘和数据分析工具。它建立在 NumPy、SciPy 和 matplotlib 等科学计算库之上&#xff0c;支持…

【Linux系统】信号:信号保存 / 信号处理、内核态 / 用户态、操作系统运行原理(中断)

理解Linux系统内进程信号的整个流程可分为&#xff1a; 信号产生 信号保存 信号处理 上篇文章重点讲解了 信号的产生&#xff0c;本文会讲解信号的保存和信号处理相关的概念和操作&#xff1a; 两种信号默认处理 1、信号处理之忽略 ::signal(2, SIG_IGN); // ignore: 忽略#…

OpenAI新商标申请曝光:AI硬件、机器人、量子计算全线布局?

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

python学opencv|读取图像(五十六)使用cv2.GaussianBlur()函数实现图像像素高斯滤波处理

【1】引言 前序学习了均值滤波和中值滤波&#xff0c;对图像的滤波处理有了基础认知&#xff0c;相关文章链接为&#xff1a; python学opencv|读取图像&#xff08;五十四&#xff09;使用cv2.blur()函数实现图像像素均值处理-CSDN博客 python学opencv|读取图像&#xff08;…

【C语言深入探索】:指针高级应用与极致技巧(二)

目录 一、指针与数组 1.1. 数组指针 1.2. 指向多维数组的指针 1.2.1. 指向多维数组元素的指针 1.2.2. 指向多维数组行的指针 1.3. 动态分配多维数组 1.4. 小结 二、指针与字符串 2.1. 字符串表示 2.2. 字符串处理函数 2.3. 代码示例 2.4. 注意事项 三、指针与文件…

吴恩达深度学习——有效运作神经网络

内容来自https://www.bilibili.com/video/BV1FT4y1E74V&#xff0c;仅为本人学习所用。 文章目录 训练集、验证集、测试集偏差、方差正则化正则化参数为什么正则化可以减少过拟合Dropout正则化Inverted Dropout其他的正则化方法数据增广Early stopping 归一化梯度消失与梯度爆…

蓝桥杯刷题 DAY4:小根堆 区间合并+二分

import os import sys import heapq# 请在此输入您的代码if __name__"__main__":x,n map(int,input().split())l[]a[0]*nb[0]*nc[0]*nq[]for i in range(n):l.append(list( map( int ,input().split()) ))l.sort(keylambda pair:-pair[1])total0j0for i in range(x,0…