【ROS2】ROS 2 中 Unique Network Flow Endpoints(唯一网络流端点)的简介与使用
1、官方示例代码
cpp
#include <chrono>
#include <functional>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include "rclcpp/rclcpp.hpp"
#include "rclcpp/publisher_options.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std::chrono_literals;
class MinimalPublisherWithUniqueNetworkFlowEndpoints : public rclcpp::Node
{
public:
MinimalPublisherWithUniqueNetworkFlowEndpoints()
: Node("minimal_publisher_with_unique_network_flow_endpoints"), count_1_(0), count_2_(0)
{
// Create publisher with unique network flow endpoints
// Enable unique network flow endpoints via options
auto options_1 = rclcpp::PublisherOptions();
options_1.require_unique_network_flow_endpoints =
RMW_UNIQUE_NETWORK_FLOW_ENDPOINTS_OPTIONALLY_REQUIRED;
publisher_1_ = this->create_publisher<std_msgs::msg::String>("topic_1", 10, options_1);
timer_1_ = this->create_wall_timer(
500ms, std::bind(&MinimalPublisherWithUniqueNetworkFlowEndpoints::timer_1_callback, this));
// Create publisher without unique network flow endpoints
// Unique network flow endpoints are disabled in default options
publisher_2_ = this->create_publisher<std_msgs::msg::String>("topic_2", 10);
timer_2_ = this->create_wall_timer(
1000ms, std::bind(&MinimalPublisherWithUniqueNetworkFlowEndpoints::timer_2_callback, this));
// Catch an exception if implementation does not support get_network_flow_endpoints.
try {
// Get network flow endpoints
auto network_flow_endpoints_1 = publisher_1_->get_network_flow_endpoints();
auto network_flow_endpoints_2 = publisher_2_->get_network_flow_endpoints();
// Print network flow endpoints
print_network_flow_endpoints(network_flow_endpoints_1);
print_network_flow_endpoints(network_flow_endpoints_2);
} catch (const rclcpp::exceptions::RCLError & e) {
RCLCPP_INFO(
this->get_logger(), "%s", e.what());
}
}
private:
void timer_1_callback()
{
auto message = std_msgs::msg::String();
message.data = "Hello, world! " + std::to_string(count_1_++);
RCLCPP_INFO(
this->get_logger(), "Publishing: '%s'", message.data.c_str());
publisher_1_->publish(message);
}
void timer_2_callback()
{
auto message = std_msgs::msg::String();
message.data = "Hej, världen! " + std::to_string(count_2_++);
RCLCPP_INFO(
this->get_logger(), "Publishing: '%s'", message.data.c_str());
publisher_2_->publish(message);
}
/// Print network flow endpoints in JSON-like format
void print_network_flow_endpoints(
const std::vector<rclcpp::NetworkFlowEndpoint> & network_flow_endpoints) const
{
std::ostringstream stream;
stream << "{\"networkFlowEndpoints\": [";
bool comma_skip = true;
for (auto network_flow_endpoint : network_flow_endpoints) {
if (comma_skip) {
comma_skip = false;
} else {
stream << ",";
}
stream << network_flow_endpoint;
}
stream << "]}";
RCLCPP_INFO(
this->get_logger(), "%s",
stream.str().c_str());
}
rclcpp::TimerBase::SharedPtr timer_1_;
rclcpp::TimerBase::SharedPtr timer_2_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_1_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_2_;
size_t count_1_;
size_t count_2_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalPublisherWithUniqueNetworkFlowEndpoints>());
rclcpp::shutdown();
return 0;
}
2、代码解析
以上代码是 ROS 2 中带 "唯一网络流端点(Unique Network Flow Endpoints)" 配置的双发布者示例,核心目的是演示如何为 ROS 2 发布者配置不同的网络流端点策略,并获取 / 打印发布者的网络流信息。文本将从整体功能、核心概念、逐模块解析、运行逻辑四个维度,对每个细节和背后的原理进行讲解。
2.1、整体功能总结
这个程序实现了一个 ROS 2 节点(minimal_publisher_with_unique_network_flow_endpoints),核心功能:
- 创建两个发布者:
- publisher_1:发布到 topic_1,启用 "可选的唯一网络流端点",500ms 发布一次英文消息;
- publisher_2:发布到 topic_2,使用默认配置(禁用唯一网络流端点),1000ms 发布一次瑞典语消息;
- 获取并打印两个发布者的网络流端点信息(JSON 格式);
- 异常处理:如果底层 RMW(ROS 中间件)不支持 get_network_flow_endpoints 接口,捕获并打印异常。
2.2、核心前置概念
在解析代码前,先理解两个关键概念(ROS 2 网络层核心):
- RMW(ROS Middleware):ROS 2 的中间件抽象层,底层对接 DDS(如 FastDDS、CycloneDDS),负责网络通信;
- Unique Network Flow Endpoints(唯一网络流端点):
- 作用:为发布者 / 订阅者分配唯一的网络端口 / 流标识,避免不同发布者 / 订阅者共享网络端点导致的流混淆;
- 策略:RMW_UNIQUE_NETWORK_FLOW_ENDPOINTS_OPTIONALLY_REQUIRED 表示 "可选启用"------ 如果中间件支持则启用,不支持则降级为默认策略;
- 场景:多发布者 / 订阅者、高并发通信、网络隔离需求的场景(如工业机器人、多机通信)。
2.3、逐模块代码解析
- 头文件引入(基础依赖)
cpp
#include <chrono> // 时间字面量(500ms/1000ms)
#include <functional> // 函数绑定(std::bind)
#include <memory> // 智能指针(SharedPtr)
#include <sstream> // 字符串流(拼接 JSON 格式)
#include <string> // 字符串处理
#include <vector> // 存储网络流端点列表
#include "rclcpp/rclcpp.hpp" // ROS 2 核心 API
#include "rclcpp/publisher_options.hpp" // 发布者配置选项(核心:网络流端点配置)
#include "std_msgs/msg/string.hpp" // ROS 2 标准字符串消息
重点:rclcpp/publisher_options.hpp 是本次示例的核心,必须引入才能配置发布者的网络流端点策略。
- 时间字面量命名空间
cpp
using namespace std::chrono_literals;
启用 C++14 时间字面量,简化定时器周期写法(如 500ms 替代 std::chrono::milliseconds(500))。
- 发布者类定义(核心逻辑)
cpp
class MinimalPublisherWithUniqueNetworkFlowEndpoints : public rclcpp::Node
{
public:
// 构造函数:初始化节点、发布者、定时器、计数器
MinimalPublisherWithUniqueNetworkFlowEndpoints()
: Node("minimal_publisher_with_unique_network_flow_endpoints"), // 节点名
count_1_(0), count_2_(0) // 两个发布者的计数器初始化
{
// ========== 第一部分:创建带"唯一网络流端点"的发布者(publisher_1) ==========
// 1. 创建发布者配置选项
auto options_1 = rclcpp::PublisherOptions();
// 2. 配置"可选的唯一网络流端点"策略
options_1.require_unique_network_flow_endpoints =
RMW_UNIQUE_NETWORK_FLOW_ENDPOINTS_OPTIONALLY_REQUIRED;
// 3. 创建发布者:topic_1,队列大小10,使用自定义配置 options_1
publisher_1_ = this->create_publisher<std_msgs::msg::String>("topic_1", 10, options_1);
// 4. 创建定时器:500ms 触发一次 timer_1_callback
timer_1_ = this->create_wall_timer(
500ms, std::bind(&MinimalPublisherWithUniqueNetworkFlowEndpoints::timer_1_callback, this));
// ========== 第二部分:创建默认配置的发布者(publisher_2) ==========
// 1. 创建发布者:topic_2,队列大小10,使用默认配置(禁用唯一网络流端点)
publisher_2_ = this->create_publisher<std_msgs::msg::String>("topic_2", 10);
// 2. 创建定时器:1000ms 触发一次 timer_2_callback
timer_2_ = this->create_wall_timer(
1000ms, std::bind(&MinimalPublisherWithUniqueNetworkFlowEndpoints::timer_2_callback, this));
// ========== 第三部分:获取并打印网络流端点信息(异常处理) ==========
try {
// 1. 获取两个发布者的网络流端点列表
auto network_flow_endpoints_1 = publisher_1_->get_network_flow_endpoints();
auto network_flow_endpoints_2 = publisher_2_->get_network_flow_endpoints();
// 2. 打印网络流端点(JSON 格式)
print_network_flow_endpoints(network_flow_endpoints_1);
print_network_flow_endpoints(network_flow_endpoints_2);
} catch (const rclcpp::exceptions::RCLError & e) {
// 捕获异常:如果底层 RMW 不支持 get_network_flow_endpoints 接口
RCLCPP_INFO(this->get_logger(), "%s", e.what());
}
}
private:
// ========== 定时器 1 回调函数(publisher_1 发布逻辑) ==========
void timer_1_callback()
{
// 1. 创建 ROS 字符串消息
auto message = std_msgs::msg::String();
// 2. 拼接消息内容:英文 + 计数器
message.data = "Hello, world! " + std::to_string(count_1_++);
// 3. 打印日志
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
// 4. 发布消息到 topic_1
publisher_1_->publish(message);
}
// ========== 定时器 2 回调函数(publisher_2 发布逻辑) ==========
void timer_2_callback()
{
// 1. 创建 ROS 字符串消息
auto message = std_msgs::msg::String();
// 2. 拼接消息内容:瑞典语(Hej, världen! = 你好,世界!) + 计数器
message.data = "Hej, världen! " + std::to_string(count_2_++);
// 3. 打印日志
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
// 4. 发布消息到 topic_2
publisher_2_->publish(message);
}
// ========== 辅助函数:打印网络流端点(JSON 格式) ==========
void print_network_flow_endpoints(
const std::vector<rclcpp::NetworkFlowEndpoint> & network_flow_endpoints) const
{
std::ostringstream stream; // 字符串流,用于拼接 JSON
stream << "{\"networkFlowEndpoints\": ["; // JSON 开头
bool comma_skip = true; // 控制逗号(避免第一个元素前加逗号)
// 遍历所有网络流端点
for (auto network_flow_endpoint : network_flow_endpoints) {
if (comma_skip) {
comma_skip = false; // 第一个元素,跳过逗号
} else {
stream << ","; // 非第一个元素,加逗号分隔
}
stream << network_flow_endpoint; // 输出单个端点信息(NetworkFlowEndpoint 重载了 <<)
}
stream << "]}"; // JSON 结尾
// 打印 JSON 格式的网络流端点信息
RCLCPP_INFO(this->get_logger(), "%s", stream.str().c_str());
}
// ========== 成员变量 ==========
rclcpp::TimerBase::SharedPtr timer_1_; // 定时器 1(500ms)
rclcpp::TimerBase::SharedPtr timer_2_; // 定时器 2(1000ms)
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_1_; // 发布者 1(带唯一网络流端点)
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_2_; // 发布者 2(默认配置)
size_t count_1_; // 发布者 1 计数器
size_t count_2_; // 发布者 2 计数器
};
- 主函数(程序入口)
cpp
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv); // 初始化 ROS 2 上下文
// 创建节点智能指针并进入自旋(阻塞等待回调/消息)
rclcpp::spin(std::make_shared<MinimalPublisherWithUniqueNetworkFlowEndpoints>());
rclcpp::shutdown(); // 关闭 ROS 2 上下文
return 0;
}
ROS 2 程序的标准入口逻辑,和普通发布者示例一致,无特殊逻辑。
2.4、核心细节与关键考点
- 唯一网络流端点的配置逻辑
cpp
options_1.require_unique_network_flow_endpoints =
RMW_UNIQUE_NETWORK_FLOW_ENDPOINTS_OPTIONALLY_REQUIRED;
枚举值含义:OPTIONALLY_REQUIRED → "可选启用"------ 如果底层 RMW(如 FastDDS)支持,则启用唯一网络流端点;不支持则忽略,不报错。
对比:默认配置的 publisher_2 未设置该参数,等价于 RMW_UNIQUE_NETWORK_FLOW_ENDPOINTS_DISABLED(禁用)。
-
网络流端点的获取与打印
- publisher_->get_network_flow_endpoints():返回 std::vectorrclcpp::NetworkFlowEndpoint,包含发布者的网络信息(如 IP 地址、端口、传输协议、流标识等);
- NetworkFlowEndpoint 重载了 << 运算符,可直接输出为字符串(格式由底层 RMW 决定);
- 打印为 JSON 格式是为了可读性,方便解析 / 调试。
-
异常处理的必要性
- get_network_flow_endpoints() 是 ROS 2 Humble 及以上版本新增的接口,部分老旧 RMW 或自定义中间件可能不支持;
- 必须捕获 RCLError 异常,否则程序会崩溃。
-
双发布者的差异化配置
| 维度 | publisher_1(topic_1) | publisher_2(topic_2) |
|---|---|---|
| 网络流端点策略 | 可选启用唯一端点 | 默认禁用 |
| 发布周期 | 500ms | 1000ms |
| 消息内容 | 英文(Hello, world!) | 瑞典语(Hej, världen!) |
| 配置选项 | 自定义 PublisherOptions | 默认选项 |
2.5、运行逻辑梳理(完整流程)
- 程序启动 → main 函数初始化 ROS 2,创建节点;
- 节点构造函数执行:
- 创建 publisher_1(带唯一网络流端点)+ 500ms 定时器;
- 创建 publisher_2(默认配置)+ 1000ms 定时器;
- 尝试获取两个发布者的网络流端点,打印为 JSON 格式(不支持则捕获异常并打印);
- 节点进入自旋,定时器开始工作:
- 每 500ms:publisher_1 发布英文消息到 topic_1,计数器 count_1_ 自增;
- 每 1000ms:publisher_2 发布瑞典语消息到 topic_2,计数器 count_2_ 自增;
- 按下 Ctrl+C → 节点退出自旋,ROS 2 上下文关闭,程序结束。
2.6、应用场景与价值
这个示例的核心价值不是 "双发布者",而是ROS 2 网络层的精细化配置:
- 工业级通信:在多机器人、高并发通信场景下,唯一网络流端点可避免不同发布者的网络流混淆,提升通信稳定性;
- 网络隔离 / 监控:通过 get_network_flow_endpoints() 可获取发布者的网络端口、IP 等信息,用于网络监控、防火墙配置;
- 中间件适配:演示如何通过 PublisherOptions 配置中间件特性,适配不同 RMW 的差异化功能。
2.7、小结
- 核心功能:实现两个差异化配置的 ROS 2 发布者,演示 "唯一网络流端点" 的配置、网络流信息的获取与打印;
- 关键技术:PublisherOptions 的 require_unique_network_flow_endpoints 参数、get_network_flow_endpoints() 接口、异常处理;
- 核心价值:ROS 2 网络层精细化配置的典型示例,适用于工业级、高可靠性的通信场景。
3、技术背景与应用场景
3.1、推出时间与版本
Unique Network Flow Endpoints(唯一网络流端点) 是 ROS 2 为解决网络流标识与 QoS 差异化而设计的特性。
- 设计提案:2021 年 5 月在 ROS 2 Design 文档中正式提出。
- 正式发布:随 ROS 2 Humble Hawksbill(2022 年 5 月) 稳定版发布,成为 LTS 版本的标准特性ROS。
- 核心 API:
- rclcpp::PublisherOptions::require_unique_network_flow_endpoints
- rclcpp::Publisher::get_network_flow_endpoints()
- rclcpp::NetworkFlowEndpoint 结构体
- RMW 支持:FastDDS、CycloneDDS 等主流中间件在 Humble 及后续版本中逐步实现支持ROS。
3.2、核心概念与作用
- 默认问题:ROS 2 默认下,同节点、同话题的多个发布者 / 订阅者可能共享相同的网络流标识(如端口、GID),导致网络设备无法区分不同流,无法对不同流做差异化 QoS(带宽、优先级、延迟、丢包率)。
- 特性作用:
- 为每个发布者 / 订阅者分配唯一的网络流标识(端口、流 ID、GID 等)。
- 让网络设备(交换机、路由器、5G 核心网)能识别不同流,实现流级别的 QoS 控制、流量监控、防火墙策略、带宽隔离。
- 配置枚举:
cpp
// 禁用(默认)
RMW_UNIQUE_NETWORK_FLOW_ENDPOINTS_DISABLED
// 可选启用:RMW 支持则启用,不支持则降级
RMW_UNIQUE_NETWORK_FLOW_ENDPOINTS_OPTIONALLY_REQUIRED
// 强制启用:RMW 不支持则报错
RMW_UNIQUE_NETWORK_FLOW_ENDPOINTS_STRICTLY_REQUIRED
3.3、适用场景(核心价值)
- 工业机器人 / 多机协作(高实时、高可靠)
- 场景:产线 AGV、机械臂集群、多机器人协同作业。
- 痛点:控制指令、传感器数据、状态反馈共用网络,易被大流量数据(如点云、图像)抢占带宽,导致控制延迟、抖动。
- 价值:
- 为控制指令流分配高优先级、低延迟 QoS。
- 为传感器数据流分配高带宽、可容忍丢包的 QoS。
- 网络设备可按流标识做流量整形、优先级调度,保障控制实时性。
- 自动驾驶 / 车联网(5G / 车规级网络)
- 场景:自动驾驶车辆、V2X 通信、车路协同。
- 痛点:车辆内部(CAN / 以太网)、车 - 云、车 - 车通信需严格区分安全关键流(制动、转向)与非关键流(娱乐、地图下载)。
- 价值:
- 结合 5G 5QI(QoS 标识符),为安全关键流分配高优先级、极低延迟(如 10ms 内)、零丢包的网络资源。
- 网络可对不同流做独立计费、监控、故障隔离。
- 高并发 / 大规模分布式系统
- 场景:多节点、多进程、多机器人组成的分布式集群(如仓储物流、智慧城市)。
- 痛点:大量发布者 / 订阅者共存,网络流混杂,难以定位通信瓶颈、排查丢包 / 延迟问题。
- 价值:
- 通过 get_network_flow_endpoints() 获取每个发布者的端口、IP、协议、GID,实现流级别的网络监控与故障定位。
- 防火墙可基于流标识做精细化访问控制,提升系统安全性。
- 网络隔离与安全(企业 / 军工场景)
- 场景:涉密机器人、军工无人系统、企业内网隔离部署。
- 痛点:不同安全级别的数据流需物理 / 逻辑隔离,防止越权访问、数据泄露。
- 价值:
- 唯一流标识可作为安全隔离的最小单元,实现不同功能模块的网络隔离。
- 结合 SROS2,实现流级别的加密、认证、访问控制。
- 性能调优与网络规划
- 场景:机器人系统性能优化、网络架构设计。
- 痛点:无法量化不同流的带宽占用、延迟、丢包率,难以针对性优化。
- 价值:
- 基于流标识做流量统计、带宽分析,识别瓶颈流。
- 为网络规划(交换机选型、带宽分配、QoS 配置)提供精准数据支撑。
3.4、不适用场景
- 单节点、单发布者 / 订阅者:无流区分需求,开启会增加端口占用与配置复杂度。
- 本地进程内通信(localhost):不经过物理网络,QoS 配置无效,无需开启。
- 资源极度受限的嵌入式设备:额外的端口 / 流管理会占用 CPU、内存资源。
3.5、小结
- 推出:2022 年 5 月随 ROS 2 Humble 正式发布ROS。
- 核心:为发布者 / 订阅者分配唯一网络流标识,实现流级 QoS、监控、隔离、安全。
- 最佳场景:工业机器人、自动驾驶、多机协作、车联网、大规模分布式系统等对实时性、可靠性、网络可控性要求极高的场景。