深入解析 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
,用于表示通用客户端。Request
和Response
类型分别是指向序列化数据的指针,适用于任意类型的请求和响应消息。
智能指针定义
RCLCPP_SMART_PTR_DEFINITIONS(GenericClient)
- 解释:该宏定义了
GenericClient
类的智能指针类型(如SharedPtr
和ConstSharedPtr
),便于内存管理和资源共享。
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
时遇到问题,欢迎在评论区讨论。