【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、监控、隔离、安全。
  • 最佳场景:工业机器人、自动驾驶、多机协作、车联网、大规模分布式系统等对实时性、可靠性、网络可控性要求极高的场景。
相关推荐
TechubNews2 小时前
從25Q4及全年財報數字看燦谷(Cango Inc)戰略轉向AI
网络·人工智能·web3·区块链
什么时候才能变强2 小时前
WebSocket 接口测试常见坑与解决方案
网络·websocket·网络协议
the sun342 小时前
计算机网络:数据链路层协议(2)
网络·网络协议·计算机网络
EasyDSS3 小时前
EasyDSS视频流媒体WebRTC技术解析:智慧校园直播、点播与会议一体化融合实践
运维·网络·人工智能·架构·音视频·m3u8·点播技术
袁小皮皮不皮3 小时前
【HCIA】第二章 ipv4协议以及子网划分与集合
linux·运维·服务器·网络·网络协议·tcp/ip·信息与通信
Ken_11153 小时前
Linux放开端口
linux·服务器·网络
艾莉丝努力练剑3 小时前
System V IPC内核实现精析
linux·运维·服务器·网络·c++·人工智能·学习
NaclarbCSDN3 小时前
[特殊字符] HTTP 超详细详解 | 从入门到看懂浏览器请求
网络·网络协议·http
云边云科技_云网融合3 小时前
百度首页中宇联云计算SD-AIoT:万物互联时代,从 “能连上” 到 “用得放心” 的技术革命
网络·数据库·人工智能