编写简单的服务和客户端(C++)
目标:使用 C++ 创建并运行服务和客户端节点。
教程级别:初学者
时间:20 分钟
背景
当 nodes 使用 services 进行通信时,发送数据请求的节点称为客户端节点,响应请求的节点称为服务节点。
请求和响应的结构由 .srv
文件确定。
此处使用的示例是一个简单的整数加法系统;一个节点请求两个整数之和,另一个节点响应结果。
先决条件
任务
1 创建包
打开一个新终端并:doc:source 您的 ROS 2 安装,以便 ros2
命令可以正常工作。
导航到在:ref:上一个教程 中创建的 ros2_ws
目录。
回想一下,应该在 src
目录中创建包,而不是在工作区的根目录中创建包。
导航到“ros2_ws/src”并创建一个新包:
ros2 pkg create --build-type ament_cmake --license Apache-2.0 cpp_srvcli --dependencies rclcpp example_interfaces
您的终端将返回一条消息,验证您的包“cpp_srvcli”及其所有必要的文件和文件夹的创建。
“–dependencies”参数将自动将必要的依赖行添加到“package.xml”和“CMakeLists.txt”。 “example_interfaces”是包含“.srv 文件<https://github.com/ros2/example_interfaces/blob/rolling/srv/AddTwoInts.srv>”的包,您需要构造您的请求和响应:
int64 a
int64 b
---
int64 sum
前两行是请求的参数,破折号下面是响应。
1.1 更新 package.xml
由于您在创建包时使用了 --dependencies
选项,因此您不必手动将依赖项添加到 package.xml
或 CMakeLists.txt
。
不过,与往常一样,请确保将描述、维护者电子邮件和姓名以及许可证信息添加到 package.xml
。
<description>C++ client server tutorial</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache-2.0</license>
2 编写服务节点
在 ros2_ws/src/cpp_srvcli/src
目录中,创建一个名为 add_two_ints_server.cpp
的新文件,并将以下代码粘贴到其中:
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"
#include <memory>
void add(const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> response)
{
response->sum = request->a + request->b;
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming request\na: %ld" " b: %ld",
request->a, request->b);
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "sending back response: [%ld]", (long int)response->sum);
}
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_server");
rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr service =
node->create_service<example_interfaces::srv::AddTwoInts>("add_two_ints", &add);
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add two ints.");
rclcpp::spin(node);
rclcpp::shutdown();
}
2.1 检查代码
前两个 #include
语句是您的包依赖项。
add
函数将请求中的两个整数相加,并将总和提供给响应,同时使用日志将其状态通知控制台。
void add(const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> response)
{
response->sum = request->a + request->b;
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming request\na: %ld" " b: %ld",
request->a, request->b);
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "sending back response: [%ld]", (long int)response->sum);
}
main
函数逐行完成以下操作:
初始化 ROS 2 C++ 客户端库:
rclcpp::init(argc, argv);
创建一个名为“add_two_ints_server”的节点:
std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_server");
为该节点创建一个名为“add_two_ints”的服务,并使用“&add”方法自动通过网络进行宣传:
rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr service = node->create_service<example_interfaces::srv::AddTwoInts>("add_two_ints", &add);
准备就绪时打印日志消息:
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add two ints.");
旋转节点,使服务可用。
rclcpp::spin(node);
2.2 添加可执行文件
add_executable
宏生成一个可以使用 ros2 run
运行的可执行文件。
将以下代码块添加到 CMakeLists.txt
以创建名为 server
的可执行文件:
add_executable(server src/add_two_ints_server.cpp)
ament_target_dependencies(server rclcpp example_interfaces)
因此“ros2 run”可以找到可执行文件,在文件末尾的“ament_package()”之前添加以下几行:
install(TARGETS
server
DESTINATION lib/${PROJECT_NAME})
您现在可以构建您的包,获取本地安装文件并运行它,但让我们先创建客户端节点,以便您可以看到整个系统的运行情况。
3 编写客户端节点
在 ros2_ws/src/cpp_srvcli/src
目录中,创建一个名为 add_two_ints_client.cpp
的新文件,并将以下代码粘贴到其中:
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"
#include <chrono>
#include <cstdlib>
#include <memory>
using namespace std::chrono_literals;
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
if (argc != 3) {
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "usage: add_two_ints_client X Y");
return 1;
}
std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_client");
rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client =
node->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");
auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();
request->a = atoll(argv[1]);
request->b = atoll(argv[2]);
while (!client->wait_for_service(1s)) {
if (!rclcpp::ok()) {
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Interrupted while waiting for the service. Exiting.");
return 0;
}
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "service not available, waiting again...");
}
auto result = client->async_send_request(request);
// Wait for the result.
if (rclcpp::spin_until_future_complete(node, result) ==
rclcpp::FutureReturnCode::SUCCESS)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Sum: %ld", result.get()->sum);
} else {
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Failed to call service add_two_ints");
}
rclcpp::shutdown();
return 0;
}
3.1 检查代码
与服务节点类似,以下代码行创建节点,然后为该节点创建客户端:
std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_client");
rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client =
node->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");
接下来,创建请求。 其结构由前面提到的“.srv”文件定义。
auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();
request->a = atoll(argv[1]);
request->b = atoll(argv[2]);
while
循环给客户端 1 秒钟的时间在网络中搜索服务节点。
如果找不到,它将继续等待。
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "service not available, waiting again...");
如果客户端被取消(例如,您在终端中输入“Ctrl+C”),它将返回一条错误日志消息,指出它已被中断。
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Interrupted while waiting for the service. Exiting.");
然后客户端发送其请求,节点旋转直到收到其响应或发生故障。
3.2 添加可执行文件
返回“CMakeLists.txt”以添加新节点的可执行文件和目标。 从自动生成的文件中删除一些不必要的样板后,您的“CMakeLists.txt”应如下所示:
cmake_minimum_required(VERSION 3.5)
project(cpp_srvcli)
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(example_interfaces REQUIRED)
add_executable(server src/add_two_ints_server.cpp)
ament_target_dependencies(server rclcpp example_interfaces)
add_executable(client src/add_two_ints_client.cpp)
ament_target_dependencies(client rclcpp example_interfaces)
install(TARGETS
server
client
DESTINATION lib/${PROJECT_NAME})
ament_package()
4 构建并运行
在构建之前,最好在工作区(“ros2_ws”)的根目录中运行“rosdep”来检查是否缺少依赖项:
rosdep install -i --from-path src --rosdistro rolling -y
rosdep only runs on Linux, so you can skip ahead to next step.
rosdep only runs on Linux, so you can skip ahead to next step.
导航回到工作区的根目录“ros2_ws”,并构建新的包:
colcon build --packages-select cpp_srvcli
colcon build --packages-select cpp_srvcli
colcon build --merge-install --packages-select cpp_srvcli
打开一个新终端,导航到“ros2_ws”,并获取安装文件:
source install/setup.bash
. install/setup.bash
call install/setup.bat
现在运行服务节点:
ros2 run cpp_srvcli server
终端应该返回以下消息,然后等待:
[INFO] [rclcpp]: Ready to add two ints.
打开另一个终端,再次从“ros2_ws”内部获取安装文件。 启动客户端节点,后跟任意两个用空格分隔的整数:
ros2 run cpp_srvcli client 2 3
例如,如果您选择“2”和“3”,客户端将收到如下响应:
[INFO] [rclcpp]: Sum: 5
返回到您的服务节点正在运行的终端。 您将看到它在收到请求和收到的数据时发布了日志消息,以及它发回的响应:
[INFO] [rclcpp]: Incoming request
a: 2 b: 3
[INFO] [rclcpp]: sending back response: [5]
在服务器终端中输入``Ctrl+C`` 以停止节点旋转。
摘要
您创建了两个节点来通过服务请求和响应数据。
您将它们的依赖项和可执行文件添加到包配置文件中,以便您可以构建和运行它们,并查看服务/客户端系统的工作情况。
后续步骤
在过去的几个教程中,您一直在利用接口在主题和服务之间传递数据。
接下来,您将学习如何:doc:创建自定义接口。
相关内容
有几种方法可以用 C++ 编写服务和客户端;查看 ros2/examples repo 中的
minimal_service
和minimal_client
包。