【Ros2学习】服务-客户端模式
文章目录
- 前言
- 一、通信流程图
- 二、工作区结构准备
- 1:创建工作区目录结构
- 2:创建功能包
- 3:工作空间目录结构
- 三、服务端实现
- 1:src/calculator_server.hpp实现
- 2:src/calculator_server.cpp实现
- 四、手动客户端实现
- 1:src/manual_client.hpp实现
- 2:src/manual_client.cpp实现
- 五、自动客户端实现
- 1:src/calculator_client.hpp实现
- 2:src/calculator_client.cpp实现
- 六、修改配置文件
- 1:修改CMakeLists.txt
- 2:修改package.xml
- 七、工程构建运行
- 1:构建包
- 2:运行服务端
- 3:运行手动客户端
- 4:运行自动客户端
前言
学习记录Ros2中服务-客户端模式操作步骤
一、通信流程图
以下图是大致的流程图:

二、工作区结构准备
1:创建工作区目录结构
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
2:创建功能包
ros2 pkg create calculator_service --build-type ament_cmake --dependencies rclcpp example_interfaces
3:工作空间目录结构

三、服务端实现
1:src/calculator_server.hpp实现
#ifndef CALCULATOR_SERVICE__CALCULATOR_SERVER_HPP_
#define CALCULATOR_SERVICE__CALCULATOR_SERVER_HPP_#include <rclcpp/rclcpp.hpp>
#include "example_interfaces/srv/add_two_ints.hpp"#include <memory>namespace calculator_service
{/*** @brief 计算器服务端节点类* * 提供加法计算服务,处理客户端发送的两个整数相加请求* 模拟实际的服务处理过程,包括处理时间和日志记录*/
class CalculatorServer : public rclcpp::Node
{
public:/*** @brief 构造函数* * 初始化服务端节点,创建加法计算服务*/CalculatorServer();/*** @brief 析构函数*/virtual ~CalculatorServer() = default;private:/*** @brief 处理加法计算请求* * @param request 服务请求,包含两个整数 a 和 b* @param response 服务响应,包含计算结果 sum*/void handle_add_request(const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> response);// ROS2服务rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr service_;// 请求计数器int request_count_;
};} // namespace calculator_service#endif // CALCULATOR_SERVICE__CALCULATOR_SERVER_HPP_
2:src/calculator_server.cpp实现
#include "calculator_service/calculator_server.hpp"
#include <chrono>
#include <thread>using namespace std::chrono_literals;namespace calculator_service
{CalculatorServer::CalculatorServer()
: Node("calculator_server"),request_count_(0)
{// 创建服务,处理AddTwoInts请求service_ = this->create_service<example_interfaces::srv::AddTwoInts>("add_two_ints",std::bind(&CalculatorServer::handle_add_request, this,std::placeholders::_1, std::placeholders::_2));RCLCPP_INFO(this->get_logger(), "计算器服务端已启动,等待请求...");RCLCPP_INFO(this->get_logger(), "服务名称: /add_two_ints");
}void CalculatorServer::handle_add_request(const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> response)
{RCLCPP_INFO(this->get_logger(), "收到计算请求: %ld + %ld", request->a, request->b);// 模拟处理时间(实际服务中可能有复杂的计算或IO操作)RCLCPP_INFO(this->get_logger(), "正在计算中...");std::this_thread::sleep_for(1s);// 执行加法计算response->sum = request->a + request->b;RCLCPP_INFO(this->get_logger(), "计算完成: %ld + %ld = %ld",request->a, request->b, response->sum);// 记录服务统计request_count_++;RCLCPP_INFO(this->get_logger(), "已处理请求数量: %d", request_count_);
}} // namespace calculator_serviceint main(int argc, char** argv)
{rclcpp::init(argc, argv);auto server_node = std::make_shared<calculator_service::CalculatorServer>();RCLCPP_INFO(server_node->get_logger(), "计算器服务端初始化完成");// 保持节点运行,等待服务请求rclcpp::spin(server_node);rclcpp::shutdown();return 0;
}
四、手动客户端实现
1:src/manual_client.hpp实现
#ifndef CALCULATOR_SERVICE__MANUAL_CLIENT_HPP_
#define CALCULATOR_SERVICE__MANUAL_CLIENT_HPP_#include <rclcpp/rclcpp.hpp>
#include "example_interfaces/srv/add_two_ints.hpp"#include <memory>
#include <string>namespace calculator_service
{/*** @brief 手动测试客户端节点类* * 提供交互式命令行界面,允许用户手动输入数字进行测试* 支持同步服务调用和用户友好的交互体验*/
class ManualClient : public rclcpp::Node
{
public:/*** @brief 构造函数* * 初始化手动客户端,启动交互式模式*/ManualClient();/*** @brief 析构函数*/virtual ~ManualClient() = default;private:/*** @brief 等待服务端可用* * 在控制台显示等待信息,直到服务端上线*/void wait_for_service();/*** @brief 启动交互式模式* * 提供命令行界面,接收用户输入并发送计算请求*/void start_interactive_mode();/*** @brief 读取用户输入的数字* * @param number 输出的数字值* @return true 读取成功* @return false 用户请求退出*/bool read_number(int64_t& number);/*** @brief 发送计算请求* * @param a 第一个数字* @param b 第二个数字*/void send_request(int64_t a, int64_t b);// ROS2服务客户端rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client_;
};} // namespace calculator_service#endif // CALCULATOR_SERVICE__MANUAL_CLIENT_HPP_
2:src/manual_client.cpp实现
#include "calculator_service/manual_client.hpp"
#include <iostream>
#include <memory>
#include <chrono>using namespace std::chrono_literals;namespace calculator_service
{ManualClient::ManualClient() : Node("manual_client")
{client_ = this->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");// 等待服务wait_for_service();// 开始交互式输入start_interactive_mode();
}void ManualClient::wait_for_service()
{std::cout << "等待计算器服务端..." << std::endl;// 使用正确的chrono时间参数while (!client_->wait_for_service(1s)) {if (!rclcpp::ok()) {std::cout << "用户中断等待" << std::endl;return;}std::cout << "服务不可用,继续等待..." << std::endl;}std::cout << "服务连接成功!" << std::endl;
}void ManualClient::start_interactive_mode()
{std::cout << "\n=== ROS2 计算器客户端 ===" << std::endl;std::cout << "输入两个整数进行计算 (输入 q 退出)" << std::endl;while (rclcpp::ok()) {int64_t a, b;std::cout << "\n请输入第一个数字: ";if (!read_number(a)) break;std::cout << "请输入第二个数字: ";if (!read_number(b)) break;// 发送请求send_request(a, b);}std::cout << "客户端退出" << std::endl;
}bool ManualClient::read_number(int64_t& number)
{std::string input;std::cin >> input;if (input == "q" || input == "quit") {return false;}try {number = std::stoll(input);return true;} catch (const std::exception& e) {std::cout << "输入无效,请输入整数或 'q' 退出: ";return read_number(number);}
}void ManualClient::send_request(int64_t a, int64_t b)
{auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();request->a = a;request->b = b;std::cout << "发送请求: " << a << " + " << b << std::endl;// 同步调用(等待响应)auto future = client_->async_send_request(request);// 等待响应 - 使用正确的超时时间std::cout << "等待服务响应..." << std::endl;if (rclcpp::spin_until_future_complete(this->get_node_base_interface(), future, 5s) ==rclcpp::FutureReturnCode::SUCCESS){auto response = future.get();std::cout << "✅ 计算结果: " << response->sum << std::endl;} else {std::cout << "❌ 服务调用失败或超时" << std::endl;}
}} // namespace calculator_serviceint main(int argc, char** argv)
{rclcpp::init(argc, argv);auto manual_client = std::make_shared<calculator_service::ManualClient>();rclcpp::shutdown();return 0;
}
五、自动客户端实现
1:src/calculator_client.hpp实现
#ifndef CALCULATOR_SERVICE__CALCULATOR_CLIENT_HPP_
#define CALCULATOR_SERVICE__CALCULATOR_CLIENT_HPP_#include <rclcpp/rclcpp.hpp>
#include "example_interfaces/srv/add_two_ints.hpp"#include <memory>
#include <random>namespace calculator_service
{/*** @brief 计算器客户端节点类* * 自动向服务端发送随机数计算请求,定期发送请求并处理响应* 支持参数配置和请求统计*/
class CalculatorClient : public rclcpp::Node
{
public:/*** @brief 构造函数* * 初始化客户端节点,创建服务客户端和定时器*/CalculatorClient();/*** @brief 析构函数*/virtual ~CalculatorClient() = default;private:/*** @brief 等待服务端可用* * 循环等待直到服务端上线,支持中断检测*/void wait_for_service();/*** @brief 发送计算请求* * 生成随机数并发送加法计算请求到服务端*/void send_request();/*** @brief 同步发送请求(在单独线程中执行)* * @param request 服务请求*/void send_request_sync(std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request);/*** @brief 处理服务响应* * @param future 服务调用的future对象*/void handle_response(rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedFuture future);// ROS2服务客户端rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client_;// 定时器,用于定期发送请求rclcpp::TimerBase::SharedPtr timer_;// 随机数生成器std::random_device rd_;std::mt19937 gen_;// 配置参数int min_value_;int max_value_;// 统计信息int request_count_;int successful_requests_;int failed_requests_;
};} // namespace calculator_service#endif // CALCULATOR_SERVICE__CALCULATOR_CLIENT_HPP_
2:src/calculator_client.cpp实现
#include "calculator_service/calculator_client.hpp"
#include <chrono>
#include <thread>
#include <future>using namespace std::chrono_literals;namespace calculator_service
{CalculatorClient::CalculatorClient()
: Node("calculator_client"), gen_(rd_()),request_count_(0),successful_requests_(0),failed_requests_(0)
{// 创建服务客户端client_ = this->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");// 声明参数this->declare_parameter<int>("min_value", 1);this->declare_parameter<int>("max_value", 100);this->declare_parameter<double>("request_interval", 2.0);// 获取参数min_value_ = this->get_parameter("min_value").as_int();max_value_ = this->get_parameter("max_value").as_int();double interval = this->get_parameter("request_interval").as_double();RCLCPP_INFO(this->get_logger(), "计算器客户端已启动");RCLCPP_INFO(this->get_logger(), "参数范围: %d ~ %d, 请求间隔: %.1f秒", min_value_, max_value_, interval);// 等待服务端可用wait_for_service();// 创建定时器,定期发送请求auto timer_interval = std::chrono::duration<double>(interval);timer_ = this->create_wall_timer(timer_interval, std::bind(&CalculatorClient::send_request, this));
}void CalculatorClient::wait_for_service()
{// 使用正确的chrono时间参数while (!client_->wait_for_service(1s)) {if (!rclcpp::ok()) {RCLCPP_ERROR(this->get_logger(), "等待服务时被中断");return;}RCLCPP_INFO(this->get_logger(), "等待服务端上线...");}RCLCPP_INFO(this->get_logger(), "服务端已连接!");
}void CalculatorClient::send_request()
{request_count_++;// 生成随机数std::uniform_int_distribution<> dis(min_value_, max_value_);auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();request->a = dis(gen_);request->b = dis(gen_);RCLCPP_INFO(this->get_logger(), "发送第 %d 个请求: %ld + %ld", request_count_, request->a, request->b);// 在单独的线程中处理同步调用,避免阻塞主线程std::thread([this, request]() {this->send_request_sync(request);}).detach();
}void CalculatorClient::send_request_sync(std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request)
{try {// 同步等待响应auto future = client_->async_send_request(request);// 等待响应完成,添加超时时间if (rclcpp::spin_until_future_complete(this->get_node_base_interface(), future, 5s) ==rclcpp::FutureReturnCode::SUCCESS){auto result = future.get();RCLCPP_INFO(this->get_logger(), "✅ 计算结果: %ld", result->sum);successful_requests_++;} else {RCLCPP_ERROR(this->get_logger(), "❌ 服务调用超时或失败");failed_requests_++;}} catch (const std::exception& e) {// RCLCPP_ERROR(this->get_logger(), "❌ 服务调用异常: %s", e.what());failed_requests_++;}// 显示统计信息RCLCPP_INFO(this->get_logger(), "统计: 成功=%d, 失败=%d, 总计=%d", successful_requests_, failed_requests_, request_count_);
}void CalculatorClient::handle_response(rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedFuture future)
{// 这个函数在第一个方案中使用,在同步方案中不需要}} // namespace calculator_serviceint main(int argc, char** argv)
{rclcpp::init(argc, argv);auto client_node = std::make_shared<calculator_service::CalculatorClient>();RCLCPP_INFO(client_node->get_logger(), "计算器客户端初始化完成,开始发送请求...");rclcpp::spin(client_node);rclcpp::shutdown();return 0;
}
六、修改配置文件
1:修改CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(calculator_service)if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")add_compile_options(-Wall -Wextra -Wpedantic)
endif()# 查找依赖
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(example_interfaces REQUIRED)# 包含目录
include_directories(include)# 创建可执行文件
add_executable(calculator_server src/calculator_server.cpp)
ament_target_dependencies(calculator_server rclcpp example_interfaces)
target_include_directories(calculator_server PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>$<INSTALL_INTERFACE:include>)add_executable(calculator_client src/calculator_client.cpp)
ament_target_dependencies(calculator_client rclcpp example_interfaces)
target_include_directories(calculator_client PUBLIC$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>$<INSTALL_INTERFACE:include>)add_executable(manual_client src/manual_client.cpp)
ament_target_dependencies(manual_client rclcpp example_interfaces)
target_include_directories(manual_client PUBLIC$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>$<INSTALL_INTERFACE:include>)# 安装目标
install(TARGETScalculator_servercalculator_clientmanual_clientDESTINATION lib/${PROJECT_NAME}
)# 安装头文件
install(DIRECTORY include/DESTINATION include/
)if(BUILD_TESTING)find_package(ament_lint_auto REQUIRED)ament_lint_auto_find_test_dependencies()
endif()ament_package()
2:修改package.xml
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypelayout="1.0"?>
<package format="3"><name>calculator_service</name><version>0.0.0</version><description>ROS2服务-客户端模式示例:计算器服务</description><maintainer email="you@example.com">Your Name</maintainer><license>Apache-2.0</license><depend>rclcpp</depend><depend>example_interfaces</depend><buildtool_depend>ament_cmake</buildtool_depend><test_depend>ament_lint_auto</test_depend><test_depend>ament_lint_common</test_depend><export><build_type>ament_cmake</build_type></export>
</package>
七、工程构建运行
1:构建包
cd ~/ros2_ws
source /opt/ros/foxy/setup.bash
colcon build --packages-select calculator_service
source install/setup.bash
2:运行服务端
# 终端1:启动服务端
ros2 run calculator_service calculator_server

3:运行手动客户端
# 终端3:启动手动客户端(交互式输入)
ros2 run calculator_service manual_client

4:运行自动客户端
# 终端2:启动自动客户端(每2秒发送随机请求)
ros2 run calculator_service calculator_client
