【ROS2】ROS 2 中 Unique Network Flow Endpoints(唯一网络流端点)的简介与使用

【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),核心功能:

  1. 创建两个发布者:
    • publisher_1:发布到 topic_1,启用 "可选的唯一网络流端点",500ms 发布一次英文消息;
    • publisher_2:发布到 topic_2,使用默认配置(禁用唯一网络流端点),1000ms 发布一次瑞典语消息;
  2. 获取并打印两个发布者的网络流端点信息(JSON 格式);
  3. 异常处理:如果底层 RMW(ROS 中间件)不支持 get_network_flow_endpoints 接口,捕获并打印异常。

2.2、核心前置概念

在解析代码前,先理解两个关键概念(ROS 2 网络层核心):

  1. RMW(ROS Middleware):ROS 2 的中间件抽象层,底层对接 DDS(如 FastDDS、CycloneDDS),负责网络通信;
  2. Unique Network Flow Endpoints(唯一网络流端点):
    • 作用:为发布者 / 订阅者分配唯一的网络端口 / 流标识,避免不同发布者 / 订阅者共享网络端点导致的流混淆;
    • 策略:RMW_UNIQUE_NETWORK_FLOW_ENDPOINTS_OPTIONALLY_REQUIRED 表示 "可选启用"------ 如果中间件支持则启用,不支持则降级为默认策略;
    • 场景:多发布者 / 订阅者、高并发通信、网络隔离需求的场景(如工业机器人、多机通信)。

2.3、逐模块代码解析

  1. 头文件引入(基础依赖)
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 是本次示例的核心,必须引入才能配置发布者的网络流端点策略。

  1. 时间字面量命名空间
cpp 复制代码
using namespace std::chrono_literals;

启用 C++14 时间字面量,简化定时器周期写法(如 500ms 替代 std::chrono::milliseconds(500))。

  1. 发布者类定义(核心逻辑)
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 计数器
};
  1. 主函数(程序入口)
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、核心细节与关键考点

  1. 唯一网络流端点的配置逻辑
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(禁用)。

  1. 网络流端点的获取与打印

    • publisher_->get_network_flow_endpoints():返回 std::vectorrclcpp::NetworkFlowEndpoint,包含发布者的网络信息(如 IP 地址、端口、传输协议、流标识等);
    • NetworkFlowEndpoint 重载了 << 运算符,可直接输出为字符串(格式由底层 RMW 决定);
    • 打印为 JSON 格式是为了可读性,方便解析 / 调试。
  2. 异常处理的必要性

    • get_network_flow_endpoints() 是 ROS 2 Humble 及以上版本新增的接口,部分老旧 RMW 或自定义中间件可能不支持;
    • 必须捕获 RCLError 异常,否则程序会崩溃。
  3. 双发布者的差异化配置

维度 publisher_1(topic_1) publisher_2(topic_2)
网络流端点策略 可选启用唯一端点 默认禁用
发布周期 500ms 1000ms
消息内容 英文(Hello, world!) 瑞典语(Hej, världen!)
配置选项 自定义 PublisherOptions 默认选项

2.5、运行逻辑梳理(完整流程)

  1. 程序启动 → main 函数初始化 ROS 2,创建节点;
  2. 节点构造函数执行:
    • 创建 publisher_1(带唯一网络流端点)+ 500ms 定时器;
    • 创建 publisher_2(默认配置)+ 1000ms 定时器;
    • 尝试获取两个发布者的网络流端点,打印为 JSON 格式(不支持则捕获异常并打印);
  3. 节点进入自旋,定时器开始工作:
    • 每 500ms:publisher_1 发布英文消息到 topic_1,计数器 count_1_ 自增;
    • 每 1000ms:publisher_2 发布瑞典语消息到 topic_2,计数器 count_2_ 自增;
  4. 按下 Ctrl+C → 节点退出自旋,ROS 2 上下文关闭,程序结束。

2.6、应用场景与价值

这个示例的核心价值不是 "双发布者",而是ROS 2 网络层的精细化配置:

  1. 工业级通信:在多机器人、高并发通信场景下,唯一网络流端点可避免不同发布者的网络流混淆,提升通信稳定性;
  2. 网络隔离 / 监控:通过 get_network_flow_endpoints() 可获取发布者的网络端口、IP 等信息,用于网络监控、防火墙配置;
  3. 中间件适配:演示如何通过 PublisherOptions 配置中间件特性,适配不同 RMW 的差异化功能。

2.7、小结

  1. 核心功能:实现两个差异化配置的 ROS 2 发布者,演示 "唯一网络流端点" 的配置、网络流信息的获取与打印;
  2. 关键技术:PublisherOptions 的 require_unique_network_flow_endpoints 参数、get_network_flow_endpoints() 接口、异常处理;
  3. 核心价值: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、适用场景(核心价值)

  1. 工业机器人 / 多机协作(高实时、高可靠)
  • 场景:产线 AGV、机械臂集群、多机器人协同作业。
  • 痛点:控制指令、传感器数据、状态反馈共用网络,易被大流量数据(如点云、图像)抢占带宽,导致控制延迟、抖动。
  • 价值:
    • 为控制指令流分配高优先级、低延迟 QoS。
    • 为传感器数据流分配高带宽、可容忍丢包的 QoS。
    • 网络设备可按流标识做流量整形、优先级调度,保障控制实时性。
  1. 自动驾驶 / 车联网(5G / 车规级网络)
  • 场景:自动驾驶车辆、V2X 通信、车路协同。
  • 痛点:车辆内部(CAN / 以太网)、车 - 云、车 - 车通信需严格区分安全关键流(制动、转向)与非关键流(娱乐、地图下载)。
  • 价值:
    • 结合 5G 5QI(QoS 标识符),为安全关键流分配高优先级、极低延迟(如 10ms 内)、零丢包的网络资源。
    • 网络可对不同流做独立计费、监控、故障隔离。
  1. 高并发 / 大规模分布式系统
  • 场景:多节点、多进程、多机器人组成的分布式集群(如仓储物流、智慧城市)。
  • 痛点:大量发布者 / 订阅者共存,网络流混杂,难以定位通信瓶颈、排查丢包 / 延迟问题。
  • 价值:
    • 通过 get_network_flow_endpoints() 获取每个发布者的端口、IP、协议、GID,实现流级别的网络监控与故障定位。
    • 防火墙可基于流标识做精细化访问控制,提升系统安全性。
  1. 网络隔离与安全(企业 / 军工场景)
  • 场景:涉密机器人、军工无人系统、企业内网隔离部署。
  • 痛点:不同安全级别的数据流需物理 / 逻辑隔离,防止越权访问、数据泄露。
  • 价值:
    • 唯一流标识可作为安全隔离的最小单元,实现不同功能模块的网络隔离。
    • 结合 SROS2,实现流级别的加密、认证、访问控制。
  1. 性能调优与网络规划
  • 场景:机器人系统性能优化、网络架构设计。
  • 痛点:无法量化不同流的带宽占用、延迟、丢包率,难以针对性优化。
  • 价值:
    • 基于流标识做流量统计、带宽分析,识别瓶颈流。
    • 为网络规划(交换机选型、带宽分配、QoS 配置)提供精准数据支撑。

3.4、不适用场景

  • 单节点、单发布者 / 订阅者:无流区分需求,开启会增加端口占用与配置复杂度。
  • 本地进程内通信(localhost):不经过物理网络,QoS 配置无效,无需开启。
  • 资源极度受限的嵌入式设备:额外的端口 / 流管理会占用 CPU、内存资源。

3.5、小结

  • 推出:2022 年 5 月随 ROS 2 Humble 正式发布ROS。
  • 核心:为发布者 / 订阅者分配唯一网络流标识,实现流级 QoS、监控、隔离、安全。
  • 最佳场景:工业机器人、自动驾驶、多机协作、车联网、大规模分布式系统等对实时性、可靠性、网络可控性要求极高的场景。
相关推荐
SHENGWEI胜为3 小时前
交换机端口显示正常,终端却获取不到 IP 地址?从 DHCP 流程定位故障
服务器·网络·测试工具·缓存·通信工程
IMPYLH5 小时前
Linux 系统备份与镜像
linux·服务器·网络
淘气包海鸟6 小时前
水声网络框架
网络·ns3·水
国科安芯7 小时前
星间激光通信终端CANFD控制网络的时延确定性与辐射可靠性
网络·嵌入式硬件·信息与通信·canfd·低轨卫星·商业航天·抗辐射
中屹指纹浏览器svip7 小时前
网络抖动会标记账号异常?分清两种波动
网络
承渊政道8 小时前
Linux网络学习【传输层协议UDP】
linux·网络·网络协议·学习·ubuntu·udp
小此方8 小时前
Linux网络(三十三):告别O(n)噩梦:从select/poll的挣扎到epoll红黑树+回调的降维打击,一文打通IO多路复用
linux·网络·驱动开发
2301_777998348 小时前
Linux 网络:数据链路层
linux·服务器·网络
国科安芯8 小时前
空间锂离子电池组管理系统CANFD通信网络的辐射可靠性设计
网络·信息与通信·canfd·电池·低轨卫星·抗辐射
2401_868534789 小时前
网规物流企业网络问题
网络