〖ROS2 源码解析〗rclcpp/include/rclcpp/generic_client.hpp

深入解析 ROS 2 的通用客户端:GenericClient 类的逐行解析

概述

在 ROS 2 中,GenericClient 类提供了一个通用的客户端接口,允许用户在服务端类型未知的情况下进行服务请求。这种灵活性使得 GenericClient 成为处理多种服务类型的理想选择。本文将深入解析 GenericClient 类的实现,逐行解释其关键部分,并展示其在实际应用中的使用场景。

源码位置

// Copyright 2023 Sony Group Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef RCLCPP__GENERIC_CLIENT_HPP_
#define RCLCPP__GENERIC_CLIENT_HPP_

#include <map>
#include <memory>
#include <future>
#include <string>
#include <vector>
#include <utility>

#include "rcl/client.h"

#include "rclcpp/client.hpp"
#include "rclcpp/visibility_control.hpp"
#include "rcpputils/shared_library.hpp"

#include "rosidl_typesupport_introspection_cpp/message_introspection.hpp"

namespace rclcpp
{
class GenericClient : public ClientBase
{
public:
  using Request = void *;   // Serialized data pointer of request message
  using Response = void *;  // Serialized data pointer of response message

  using SharedResponse = std::shared_ptr<void>;

  using Promise = std::promise<SharedResponse>;
  using SharedPromise = std::shared_ptr<Promise>;

  using Future = std::future<SharedResponse>;
  using SharedFuture = std::shared_future<SharedResponse>;

  RCLCPP_SMART_PTR_DEFINITIONS(GenericClient)

  /// A convenient GenericClient::Future and request id pair.
  /**
   * Public members:
   * - future: a std::future<void *>.
   * - request_id: the request id associated with the future.
   *
   * All the other methods are equivalent to the ones std::future provides.
   */
  struct FutureAndRequestId
    : detail::FutureAndRequestId<Future>
  {
    using detail::FutureAndRequestId<Future>::FutureAndRequestId;

    /// See std::future::share().
    SharedFuture share() noexcept {return this->future.share();}

    /// Move constructor.
    FutureAndRequestId(FutureAndRequestId && other) noexcept = default;
    /// Deleted copy constructor, each instance is a unique owner of the future.
    FutureAndRequestId(const FutureAndRequestId & other) = delete;
    /// Move assignment.
    FutureAndRequestId & operator=(FutureAndRequestId && other) noexcept = default;
    /// Deleted copy assignment, each instance is a unique owner of the future.
    FutureAndRequestId & operator=(const FutureAndRequestId & other) = delete;
    /// Destructor.
    ~FutureAndRequestId() = default;
  };

  GenericClient(
    rclcpp::node_interfaces::NodeBaseInterface * node_base,
    rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph,
    const std::string & service_name,
    const std::string & service_type,
    rcl_client_options_t & client_options);

  RCLCPP_PUBLIC
  SharedResponse
  create_response() override;

  RCLCPP_PUBLIC
  std::shared_ptr<rmw_request_id_t>
  create_request_header() override;

  RCLCPP_PUBLIC
  void
  handle_response(
    std::shared_ptr<rmw_request_id_t> request_header,
    std::shared_ptr<void> response) override;

  /// Send a request to the service server.
  /**
   * This method returns a `FutureAndRequestId` instance
   * that can be passed to Executor::spin_until_future_complete() to
   * wait until it has been completed.
   *
   * If the future never completes,
   * e.g. the call to Executor::spin_until_future_complete() times out,
   * GenericClient::remove_pending_request() must be called to clean the client internal state.
   * Not doing so will make the `Client` instance to use more memory each time a response is not
   * received from the service server.
   *
   * ```cpp
   * auto future = client->async_send_request(my_request);
   * if (
   *   rclcpp::FutureReturnCode::TIMEOUT ==
   *   executor->spin_until_future_complete(future, timeout))
   * {
   *   client->remove_pending_request(future);
   *   // handle timeout
   * } else {
   *   handle_response(future.get());
   * }
   * ```
   *
   * \param[in] request request to be send.
   * \return a FutureAndRequestId instance.
   */
  RCLCPP_PUBLIC
  FutureAndRequestId
  async_send_request(const Request request);

  /// Clean all pending requests older than a time_point.
  /**
   * \param[in] time_point Requests that were sent before this point are going to be removed.
   * \param[inout] pruned_requests Removed requests id will be pushed to the vector
   *  if a pointer is provided.
   * \return number of pending requests that were removed.
   */
  template<typename AllocatorT = std::allocator<int64_t>>
  size_t
  prune_requests_older_than(
    std::chrono::time_point<std::chrono::system_clock> time_point,
    std::vector<int64_t, AllocatorT> * pruned_requests = nullptr)
  {
    return detail::prune_requests_older_than_impl(
      pending_requests_,
      pending_requests_mutex_,
      time_point,
      pruned_requests);
  }

  RCLCPP_PUBLIC
  size_t
  prune_pending_requests();

  RCLCPP_PUBLIC
  bool
  remove_pending_request(
    int64_t request_id);

  /// Take the next response for this client.
  /**
   * \sa ClientBase::take_type_erased_response().
   *
   * \param[out] response_out The reference to a Service Response into
   *   which the middleware will copy the response being taken.
   * \param[out] request_header_out The request header to be filled by the
   *   middleware when taking, and which can be used to associate the response
   *   to a specific request.
   * \returns true if the response was taken, otherwise false.
   * \throws rclcpp::exceptions::RCLError based exceptions if the underlying
   *   rcl function fail.
   */
  RCLCPP_PUBLIC
  bool
  take_response(Response response_out, rmw_request_id_t & request_header_out)
  {
    return this->take_type_erased_response(response_out, request_header_out);
  }

protected:
  using CallbackInfoVariant = std::variant<
    std::promise<SharedResponse>>;  // Use variant for extension

  int64_t
  async_send_request_impl(
    const Request request,
    CallbackInfoVariant value);

  std::optional<CallbackInfoVariant>
  get_and_erase_pending_request(
    int64_t request_number);

  RCLCPP_DISABLE_COPY(GenericClient)

  std::map<int64_t, std::pair<
      std::chrono::time_point<std::chrono::system_clock>,
      CallbackInfoVariant>> pending_requests_;
  std::mutex pending_requests_mutex_;

private:
  std::shared_ptr<rcpputils::SharedLibrary> ts_lib_;
  const rosidl_typesupport_introspection_cpp::MessageMembers * response_members_;
};
}  // namespace rclcpp

#endif  // RCLCPP__GENERIC_CLIENT_HPP_```

## 逐行解析

### 文件头部

```cpp
// Copyright 2023 Sony Group Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
  • 解释:这部分代码是版权声明和许可证信息,表明该代码由 Sony Group Corporation 开发,并受 Apache License 2.0 约束。

头文件保护与包含的头文件

#ifndef RCLCPP__GENERIC_CLIENT_HPP_
#define RCLCPP__GENERIC_CLIENT_HPP_

#include <map>
#include <memory>
#include <future>
#include <string>
#include <vector>
#include <utility>

#include "rcl/client.h"
#include "rclcpp/client.hpp"
#include "rclcpp/visibility_control.hpp"
#include "rcpputils/shared_library.hpp"
#include "rosidl_typesupport_introspection_cpp/message_introspection.hpp"
  • 解释:头文件保护机制确保该文件不会被多次包含。随后包含的头文件提供了创建客户端所需的基础设施,如 rclcpp/client.hpp 定义了客户端基础类,rcpputils/shared_library.hpp 提供了共享库的支持。

类定义

namespace rclcpp
{
class GenericClient : public ClientBase
{
public:
  using Request = void *;   // 请求消息的序列化数据指针
  using Response = void *;  // 响应消息的序列化数据指针
  • 解释GenericClient 类继承自 ClientBase,用于表示通用客户端。RequestResponse 类型分别是指向序列化数据的指针,适用于任意类型的请求和响应消息。

智能指针定义

  RCLCPP_SMART_PTR_DEFINITIONS(GenericClient)
  • 解释:该宏定义了 GenericClient 类的智能指针类型(如 SharedPtrConstSharedPtr),便于内存管理和资源共享。

FutureAndRequestId 结构体

  struct FutureAndRequestId
    : detail::FutureAndRequestId<Future>
  {
    using detail::FutureAndRequestId<Future>::FutureAndRequestId;

    SharedFuture share() noexcept {return this->future.share();}

    FutureAndRequestId(FutureAndRequestId && other) noexcept = default;
    FutureAndRequestId(const FutureAndRequestId & other) = delete;
    FutureAndRequestId & operator=(FutureAndRequestId && other) noexcept = default;
    FutureAndRequestId & operator=(const FutureAndRequestId & other) = delete;
    ~FutureAndRequestId() = default;
  };
  • 解释FutureAndRequestId 结构体扩展了 std::future,用于管理异步请求的返回值及其关联的请求 ID。该结构体通过 share() 方法可以将 std::future 转换为 std::shared_future,支持多次访问。

构造函数与基本方法

  GenericClient(
    rclcpp::node_interfaces::NodeBaseInterface * node_base,
    rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph,
    const std::string & service_name,
    const std::string & service_type,
    rcl_client_options_t & client_options);

  SharedResponse
  create_response() override;

  std::shared_ptr<rmw_request_id_t>
  create_request_header() override;

  void
  handle_response(
    std::shared_ptr<rmw_request_id_t> request_header,
    std::shared_ptr<void> response) override;
  • 解释
    • 构造函数 GenericClient 用于初始化通用客户端,接受节点基础接口、节点图接口、服务名称、服务类型和客户端选项作为参数。
    • create_response() 方法创建一个响应对象,用于处理服务响应。
    • create_request_header() 方法创建一个请求头,用于跟踪服务请求。
    • handle_response() 方法处理从服务端接收到的响应。

异步请求与清理方法

  FutureAndRequestId
  async_send_request(const Request request);

  size_t
  prune_requests_older_than(
    std::chrono::time_point<std::chrono::system_clock> time_point,
    std::vector<int64_t, AllocatorT> * pruned_requests = nullptr);

  size_t
  prune_pending_requests();

  bool
  remove_pending_request(
    int64_t request_id);
  • 解释
    • async_send_request() 方法发送异步请求,并返回一个 FutureAndRequestId 实例,便于后续处理请求的响应。
    • prune_requests_older_than()prune_pending_requests() 方法用于清理超时或未完成的请求,防止内存泄漏。
    • remove_pending_request() 方法用于手动移除挂起的请求。

内部实现与保护成员

protected:
  using CallbackInfoVariant = std::variant<
    std::promise<SharedResponse>>;

  int64_t
  async_send_request_impl(
    const Request request,
    CallbackInfoVariant value);

  std::optional<CallbackInfoVariant>
  get_and_erase_pending_request(
    int64_t request_number);

  RCLCPP_DISABLE_COPY(GenericClient)

  std::map<int64_t, std::pair<
      std::chrono::time_point<std::chrono::system_clock>,
      CallbackInfoVariant>> pending_requests_;
  std::mutex pending_requests_mutex_;

private:
  std::shared_ptr<rcpputils::SharedLibrary> ts_lib_;
  const rosidl_typesupport_introspection_cpp::MessageMembers * response_members_;
};
  • 解释
    • CallbackInfoVariant 是一个 std::variant,用于扩展和管理回调信息。
    • async_send_request_impl() 是异步请求的内部实现,用于发送请求并记录其回调信息。
    • get_and_erase_pending_request() 方法用于获取并移除指定的挂起请求。
    • pending_requests_ 是一个映射,用于管理挂起请求的 ID 及其相关信息。
    • ts_lib_response_members_ 是私有成员,分别用于管理类型支持库和响应消息的成员信息。

命名空间与头文件保护结束

}  // namespace rclcpp

#endif  // RCLCPP__GENERIC_CLIENT_HPP_
  • 解释:这部分代码关闭了 rclcpp 命名空间,并结束了头文件的防重复包含保护。

总结

GenericClient 类为 ROS 2 提供了一个通用的客户端接口,允许用户在不明确指定服务类型的情况下发送请求和处理响应。通过解析 GenericClient 类的实现,我们可以更好地理解如何在 ROS 2 中构建灵活的客户端应用程序,以及如何有效地管理异步请求和响应。如果您在使用 GenericClient 时遇到问题,欢迎在评论区讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值