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 |
bound | string/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相同的数据是同一个“实例”,它会:
- 缓存最后一次收到的
id = 1
的数据(类似数据库的UPDATE
)。也就是说如果State
结构体有 @keyid
,那么 DDS 会按id
分别存储不同的实例。如果 DDS 订阅者(Subscriber)已经收到id = 1
的数据,再次收到id = 1
的新数据时,DDS 只会 更新id = 1
的数据,不会新增新的条目。 - 自动删除旧数据(可以配置数据历史策略)。DDS 允许你配置“数据历史策略”(History QoS),决定保留多少条历史记录。如果配置KEEP_LAST(1) DDS 只会保存每个id的最新数据,旧数据会自动被删除。如果配置KEEP_ALL DDS 会保留所有历史数据,不删除。
- 允许
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类型,那所有的类型都设计成可变的类型,系统的可扩展性不就可以得到保证吗,为什么还需要支持前面两个类型?答案总结在下面的这张不同类型的优劣势中,不同类型可扩展性实现的关键技术在数据序列化中介绍。
可扩展性 | 优势 | 劣势 |
---|---|---|
FINAL | 1、首先是安全,类似于Java里面把一个类声明为final禁止其他类型继承扩展;2、固定结构下数据序列化/反序列化效率高 | 无可扩展性 |
MUTABLE | 具备很好的可扩展性 | 结构可变带来底层序列化/反序列化需要携带更多的额外信息,导致效率变低 |
APPENDABLE | 1、具备一定的可扩展性;2、接近于固定结构序列化/反序列化效率高 | 可扩展性有限 |
基础类型
idl和C++用的基本类型差不多:
类型 | 描述 | 示例 |
---|---|---|
boolean | 布尔值(true 或 false ) | boolean is_active; |
char | 单个字符(ASCII) | char letter; |
octet | 8-bit 无符号整数 | octet small_value; |
int8 | 8-bit 有符号整数 | int8 small_number; |
uint8 | 8-bit 无符号整数 | uint8 small_number; |
int16 | 16-bit 有符号整数 | int16 medium_number; |
uint16 | 16-bit 无符号整数 | uint16 medium_number; |
int32 | 32-bit 有符号整数 | int32 large_number; |
uint32 | 32-bit 无符号整数 | uint32 large_number; |
int64 | 64-bit 有符号整数 | int64 very_large_number; |
uint64 | 64-bit 无符号整数 | uint64 very_large_number; |
float | 32-bit 单精度浮点数 | float temperature; |
double | 64-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 自动创建 |
创建 Publisher 和 Subscriber | ✅ FastDDS 自动创建 |
管理 Request 和 Reply 的序列化 | ✅ FastDDS 自动管理 |
匹配 Client 和 Server 的 Domain 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); |
相关文章:
Fastdds学习分享_xtpes_发布订阅模式及rpc模式
在之前的博客中我们介绍了dds的大致功能,与组成结构。本篇博文主要介绍的是xtypes.分为理论和实际运用两部分.理论主要用于梳理hzy大佬的知识,对于某些一带而过的部分作出更为详细的阐释,并在之后通过实际案例便于理解。案例分为普通发布订阅…...
什么叫DeepSeek-V3,以及与GPT-4o的区别
1. DeepSeek 的故事 1.1 DeepSeek 是什么? DeepSeek 是一家专注于人工智能技术研发的公司,致力于打造高性能、低成本的 AI 模型。它的目标是让 AI 技术更加普惠,让更多人能够用上强大的 AI 工具。 1.2 DeepSeek-V3 的问世 DeepSeek-V3 是…...
【C#】Process、ProcessStartInfo启动外部exe
在C#中使用 Process 和 ProcessStartInfo 类启动外部 .exe 文件,可以按照以下步骤进行: 创建 ProcessStartInfo 实例:配置进程启动信息,包括可执行文件的路径、传递给该程序的参数等。 设置启动选项:根据需要配置 Pro…...
android 音视频系列引导
音视频这块的知识点自己工作中有用到,一直没有好好做一个总结,原因有客观和主观的。 客观是工作太忙,没有成段时间做总结。 主观自己懒。 趁着这次主动离职拿了n1的钱,休息一下,对自己的人生做一下总结,…...
Mac电脑上最新的好用邮件软件比较
在Mac电脑上,选择一款好用的邮件软件需要根据个人需求、功能偏好以及与系统生态的兼容性来决定。以下是基于我搜索到的资料,对当前市场上一些优秀的邮件客户端进行比较和推荐: 1. Apple Mail Apple Mail是Mac系统自带的邮件客户端ÿ…...
C#,入门教程(11)——枚举(Enum)的基础知识和高级应用
上一篇: C#,入门教程(10)——常量、变量与命名规则的基础知识https://blog.csdn.net/beijinghorn/article/details/123913570 不会枚举,就不会编程! 枚举 一个有组织的常量系列 比如:一个星期每一天的名字…...
Spring 实现注入的方式
一、XML配置文件注入 概念:这是一种传统的依赖注入方式,通过在XML文件中配置bean的相关信息来实现依赖注入。在Spring框架中,需要在applicationContext.xml或spring-config.xml等配置文件中定义bean,并通过<property>标签或…...
【论文复现】粘菌算法在最优经济排放调度中的发展与应用
目录 1.摘要2.黏菌算法SMA原理3.改进策略4.结果展示5.参考文献6.代码获取 1.摘要 本文提出了一种改进粘菌算法(ISMA),并将其应用于考虑阀点效应的单目标和双目标经济与排放调度(EED)问题。为提升传统粘菌算法…...
【LLM-agent】(task6)构建教程编写智能体
note 构建教程编写智能体 文章目录 note一、功能需求二、相关代码(1)定义生成教程的目录 Action 类(2)定义生成教程内容的 Action 类(3)定义教程编写智能体(4)交互式操作调用教程编…...
04树 + 堆 + 优先队列 + 图(D1_树(D10_决策树))
目录 一、引言 二、算法原理 三、算法实现 四、知识小结 一、引言 决策树算法是一种常用的机器学习算法,可用于分类和回归问题。它基于特征之间的条件判断来构 建一棵树,树的每个节点代表一个特征,每个叶节点代表一个类别或回归值。决策…...
JavaScript模块化
什么是JavaScript的模块化? JavaScript的模块化是指将代码分割成独立的、可重用的模块,每个模块具有自己的功能和作用,可以单独进行开发、测试和维护。不同的目的是提升代码的可维护性、可复用性和可扩展性,同时避免不同模块间的…...
排序算法--插入排序
插入排序是一种简单且稳定的排序算法,适合小规模数据或部分有序数据。 // 插入排序函数 void insertionSort(int arr[], int n) {for (int i 1; i < n; i) { // 从第二个元素开始int key arr[i]; // 当前需要插入的元素int j i - 1;// 将比 key 大的元素向后移…...
【C语言篇】“三子棋”
一、游戏介绍 三子棋,英文名为 Tic - Tac - Toe,是一款简单而经典的棋类游戏。游戏在一个 33 的棋盘上进行,两名玩家轮流在棋盘的空位上放置自己的棋子(通常用 * 和 # 表示),率先在横、竖或斜方向上连成三个…...
【大模型理论篇】DeepSeek-R1:引入冷启动的强化学习
1. 背景 首先给出DeepSeek-V3、DeepSeek-R1-Zero、DeepSeek-R1的关系图【1】。 虽然DeepSeek-R1-Zero推理能力很强,但它也面临一些问题。例如,DeepSeek-R1-Zero存在可读性差和语言混杂等问题。为了使推理过程更具可读性,进而推出了DeepSee…...
Linux基础 ——tmux vim 以及基本的shell语法
Linux 基础 ACWING y总的Linux基础课,看讲义作作笔记。 tmux tmux 可以干嘛? tmux可以分屏多开窗口,可以进行多个任务,断线,不会自动杀掉正在进行的进程。 tmux – session(会话,多个) – window(多个…...
使用 Kotlin 将 Vertx 和 Springboot 整合
本篇文章目的是将 Springboot 和 Vertx 进行简单整合。整合目的仅仅是为了整活,因为两个不同的东西整合在一起提升的性能并没有只使用 Vertx 性能高,因此追求高性能的话这是在我来说不推荐。而且他们不仅没有提高很多性能甚至增加了学习成本 一、整合流…...
【单层神经网络】softmax回归的从零开始实现(图像分类)
softmax回归 该回归分析为后续的多层感知机做铺垫 基本概念 softmax回归用于离散模型预测(分类问题,含标签) softmax运算本质上是对网络的多个输出进行了归一化,使结果有一个统一的判断标准,不必纠结为什么要这么算…...
课题推荐——基于自适应滤波技术的多传感器融合在无人机组合导航中的应用研究
无人机在现代航空、农业和监测等领域的应用日益广泛。为了提高导航精度,通常采用多传感器融合技术,将来自GPS、惯性测量单元(IMU)、磁力计等不同传感器的数据整合。然而,传感器的量测偏差、环境干扰以及非线性特性使得…...
【基于SprintBoot+Mybatis+Mysql】电脑商城项目之用户登录
🧸安清h:个人主页 🎥个人专栏:【Spring篇】【计算机网络】【Mybatis篇】 🚦作者简介:一个有趣爱睡觉的intp,期待和更多人分享自己所学知识的真诚大学生。 目录 🎯1.登录-持久层 &…...
Mono里运行C#脚本40—mono_magic_trampoline函数的参数设置
前面介绍过跳板代码,它是用来切换托管代码与非托管的代码,以及运行时与C#代码的交互。实现从运行时切换到C#代码来运行,再从C#代码返回运行时。 要想理解整个切换的细节,那么就需要理解mono_magic_trampoline函数, 而要理解此函数,就必须了解这个函数的参数来源。 要理…...
Verilog基础(三):过程
过程(Procedures) - Always块 – 组合逻辑 (Always blocks – Combinational) 由于数字电路是由电线相连的逻辑门组成的,所以任何电路都可以表示为模块和赋值语句的某种组合. 然而,有时这不是描述电路最方便的方法. 两种always block是十分有用的: 组合逻辑: always @(…...
实际操作 检测缺陷刀片
号he 找到目标图像的缺陷位置,首先思路为对图像进行预处理,灰度-二值化-针对图像进行轮廓分析 //定义结构元素 Mat se getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1)); morphologyEx(thre, tc, MORPH_OPEN, se, Point(-1, -1), 1); …...
DeepSeek 阐述 2025年前端发展趋势
预测2025年前端的发展趋势。首先,我需要考虑当前的前端 技术发展情况,以及近几年的变化趋势。比如,框架方面,React、Vue、Angular这些主流框架的更新方向和社区活跃度。可能用户想知道未来哪些技术会更流行,或者需要学…...
Elasticsearch基本使用详解
文章目录 Elasticsearch基本使用详解一、引言二、环境搭建1、安装 Elasticsearch2、安装 Kibana(可选) 三、索引操作1、创建索引2、查看索引3、删除索引 四、数据操作1、插入数据2、查询数据(1)简单查询(2)…...
【Rust自学】17.3. 实现面向对象的设计模式
喜欢的话别忘了点赞、收藏加关注哦,对接下来的教程有兴趣的可以关注专栏。谢谢喵!(・ω・) 17.3.1. 状态模式 状态模式(state pattern) 是一种面向对象设计模式,指的是一个值拥有的内部状态由数个状态对象(…...
司库建设-融资需求分析与计划制定
当企业现金流紧张时,企业需要考虑外部融资,从财务营运角度来考虑和分析如何确定输入和输出,进行整体解决方案设计。 融资需求分析与计划制定 功能点: 现金流预测工具:集成历史数据和业务计划,自动生成未来1…...
2. 【.NET Aspire 从入门到实战】--理论入门与环境搭建--.NET Aspire 概览
在当今快速发展的软件开发领域,构建高效、可靠且易于维护的云原生应用程序已成为开发者和企业的核心需求。.NET Aspire 作为一款专为云原生应用设计的开发框架,旨在简化分布式系统的构建和管理,提供了一整套工具、模板和集成包,帮…...
【Elasticsearch】allow_no_indices
- **allow_no_indices 参数的作用**: 该参数用于控制当请求的目标索引(通过通配符、别名或 _all 指定)不存在或已关闭时,Elasticsearch 的行为。 - **默认行为**: 如果未显式设置该参数,默认值为 …...
26.useScript
在 Web 应用开发中,动态加载外部脚本是一个常见需求,特别是在需要集成第三方库或服务时。然而,在 React 应用中管理脚本加载可能会变得复杂。useScript 钩子提供了一种优雅的方式来处理外部脚本的加载、错误处理和清理,使得在 Rea…...
跨域问题和解决方案
跨域问题及解决方案 同源策略及跨域问题 同源策略是一套浏览器安全机制,当一个源的文档和脚本,与另一个源的资源进行通信时,同源策略就会对这个通信做出不同程度的限制。 简单来说,同源策略对 同源资源 放行,对 异源…...
springboot中路径默认配置与重定向/转发所存在的域对象
Spring Boot 是一种简化 Spring 应用开发的框架,它提供了多种默认配置和方便的开发特性。在 Web 开发中,路径配置和请求的重定向/转发是常见操作。本文将详细介绍 Spring Boot 中的路径默认配置,并解释重定向和转发过程中存在的域对象。 一、…...
【OS】AUTOSAR架构下的Interrupt详解(下篇)
目录 3.代码分析 3.1中断配置代码 3.2 OS如何找到中断处理函数 3.3 Os_InitialEnableInterruptSources实现 3.4 Os_EnableInterruptSource 3.5 DisableAllInterrupts 3.5.1Os_IntSuspendCat1 3.5.2 Os_InterruptDisableAllEnter 3.5.3 Disable二类中断 3.5.4 Disable一…...
基于遗传算法的256QAM星座图的最优概率整形matlab仿真,对比优化前后整形星座图和误码率
目录 1.算法仿真效果 2.算法涉及理论知识概要 3.MATLAB核心程序 4.完整算法代码文件获得 1.算法仿真效果 matlab2022a仿真结果如下(完整代码运行后无水印): GA优化曲线: 优化前后星座图对比 优化前后误码率对比 仿真操作步骤…...
Android学习21 -- launcher
1 前言 之前在工作中,第一次听到launcher有点蒙圈,不知道是啥,当时还赶鸭子上架去和客户PK launcher的事。后来才知道其实就是安卓的桌面。本来还以为很复杂,毕竟之前接触过windows的桌面,那叫一个复杂。。。 后面查了…...
小程序越来越智能化,作为设计师要如何进行创新设计
一、用户体验至上 (一)简洁高效的界面设计 小程序的特点之一是轻便快捷,用户期望能够在最短的时间内找到所需功能并完成操作。因此,设计师应致力于打造简洁高效的界面。避免过多的装饰元素和复杂的布局,采用清晰的导航…...
【实践案例】基于大语言模型的海龟汤游戏
文章目录 项目背景提示词构建海龟汤主持人真相判断专家 具体实现流程文心一言大语言模型“海龟汤”插件参考 项目背景 “海龟汤”作为一种聚会类桌游,又称情境推理游戏,是一种猜测情境还原事件真相的智力游戏。其玩法是由出题者提出一个难以理解的事件&…...
基于多智能体强化学习的医疗AI中RAG系统程序架构优化研究
一、引言 1.1 研究背景与意义 在数智化医疗飞速发展的当下,医疗人工智能(AI)已成为提升医疗服务质量、优化医疗流程以及推动医学研究进步的关键力量。医疗 AI 借助机器学习、深度学习等先进技术,能够处理和分析海量的医疗数据,从而辅助医生进行疾病诊断、制定治疗方案以…...
【Unity2D 2022:UI】创建滚动视图
一、创建Scroll View游戏对象 在Canvas画布下新建Scroll View游戏对象 二、为Content游戏对象添加Grid Layout Group(网格布局组)组件 选中Content游戏物体,点击Add Competent添加组件,搜索Grid Layout Group组件 三、调整Grid La…...
C++ Primer 多维数组
欢迎阅读我的 【CPrimer】专栏 专栏简介:本专栏主要面向C初学者,解释C的一些基本概念和基础语言特性,涉及C标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级…...
怀旧经典:1200+款红白机游戏合集,Windows版一键畅玩
沉浸在怀旧的海洋中,体验经典红白机游戏的魅力!我们为您精心准备了超过1200款经典游戏的合集,每一款都是时代的印记,每一场都是回忆的旅程。这个合集不仅包含了丰富的游戏资源,还内置了多个Windows版的NES模拟器&…...
【数据采集】案例02:基于Selenium采集豆瓣电影Top250的详细数据
基于Selenium采集豆瓣电影Top250的详细数据 Selenium官网:https://www.selenium.dev/blog/ 豆瓣电影Top250官网:https://movie.douban.com/top250 写在前面 实验目标:基于Selenium框架采集豆瓣电影Top250的详细数据。 电脑系统:Windows 使用软件:PyCharm、Navicat 技术需求…...
Spring 面试题【每日20道】【其二】
1、Spring MVC 具体的工作原理? 中等 Spring MVC 是 Spring 框架的一部分,专门用于构建基于Java的Web应用程序。它采用模型-视图-控制器(MVC)架构模式,有助于分离应用程序的不同方面,如输入逻辑、业务逻辑…...
算法设计-0-1背包动态规划(C++)
一、问题阐述 0-1 背包问题的目标是在给定背包容量 W 的情况下,从 n 个物品中选择一些物品放入背包,使得背包中物品的总价值最大。每个物品只能选择一次(即要么放入背包,要么不放入)。 二、代码 #include <iostr…...
【Java知识】使用Java实现地址逆向解析到区划信息
文章目录 1. 实现 FST1.1 定义 FST 节点1.2 定义 FST 2. 实现地址逆向查询2.1 定义区划信息2.2 构建 FST 3. 运行结果4. 代码说明5. 进一步优化6. 总结 实现一个 FST(Finite State Transducer,有限状态转换器) 并用于 地址逆向查询区划信息…...
单机伪分布Hadoop详细配置
目录 1. 引言2. 配置单机Hadoop2.1 下载并解压JDK1.8、Hadoop3.3.62.2 配置环境变量2.3 验证JDK、Hadoop配置 3. 伪分布Hadoop3.1 配置ssh免密码登录3.2 配置伪分布Hadoop3.2.1 修改hadoop-env.sh3.2.2 修改core-site.xml3.2.3 修改hdfs-site.xml3.2.4 修改yarn-site.xml3.2.5 …...
[250204] Mistral Small 3:小巧、快速、强大 | asdf 0.16.0 发布:Golang 重写带来性能飞跃
目录 Mistral AI 发布开源模型 Mistral Small 3:小巧、快速、强大asdf 0.16.0 版本发布:Golang 重写带来性能飞跃! Mistral AI 发布开源模型 Mistral Small 3:小巧、快速、强大 法国人工智能初创公司 Mistral AI 发布了最新的开源…...
解读“大语言模型(LLM)安全性测评基准”
1. 引入 OWASP,全称为Open Web Application Security Project,即开放式Web应用程序安全项目,是一个致力于提高软件安全性的非营利国际组织。 由于庞大的规模和复杂的结构,大语言模型也存在多种安全风险,如prompt误导…...
可视化相机pose colmap形式的相机内参外参
目录 内参外参转换 可视化相机pose colmap形式的相机内参外参 内参外参转换 def visualize_cameras(cameras, images):fig plt.figure()ax fig.add_subplot(111, projection3d)for image_id, image_data in images.items():qvec image_data[qvec]tvec image_data[tvec]#…...
从0开始使用面对对象C语言搭建一个基于OLED的图形显示框架(基础图形库实现)
目录 基础图形库的抽象 抽象图形 抽象点 设计我们的抽象 实现我们的抽象 测试 抽象线 设计我们的抽象 实现我们的抽象 绘制垂直的和水平的线 使用Bresenham算法完成任意斜率的绘制 绘制三角形和矩形 矩形 三角形 实现 绘制圆,圆弧和椭圆 继续我们的…...
python学opencv|读取图像(五十三)原理探索:使用cv.matchTemplate()函数实现最佳图像匹配
【1】引言 前序学习进程中,已经探索了使用cv.matchTemplate()函数实现最佳图像匹配的技巧,并且成功对两个目标进行了匹配。 相关文章链接为:python学opencv|读取图像(五十二)使用cv.matchTemplate()函数实现最佳图像…...