【ROS2高级】实现一个服务器/客户端(C++)
创始人
2025-05-29 01:31:16

一、说明

        当节点使用服务进行通信时,发送数据请求的节点称为客户端节点,响应请求的节点称为服务节点。请求和响应的结构由 .srv 文件确定。这里使用的官网例子是一个简单的整数加法系统;一个节点请求两个整数的总和,另一个节点响应结果。然而产生更大疑问...

二、建立包

        打开一个新终端并获取您的 ROS 2 安装源,这样 ros2 命令就会起作用。

        导航到在上一个教程中创建的 cs_ws 目录。

        回想一下,应该在 src 目录而不是工作区的根目录中创建包。导航到 ros2_ws/src 并创建一个新包:

ros2 pkg create --build-type ament_cmake cpp_srvcli --dependencies rclcpp example_interfaces

        您的终端将返回一条消息,验证您的包 cpp_srvcli 及其所有必要文件和文件夹的创建。

        --dependencies 参数会自动将必要的依赖行添加到 package.xml 和 CMakeLists.txt。 example_interfaces 是包含 .srv 文件的包。

        您需要构建请求和响应:

    int64 aint64 b---int64 sum

        前两行是请求的参数,虚线下面是响应。

2.1 修改 package.xml

        因为您在包创建期间使用了 --dependencies 选项,所以您不必手动将依赖项添加到 package.xml 或 CMakeLists.txt。

        不过,一如既往,请确保将描述、维护者电子邮件和姓名以及许可信息添加到 package.xml 中。

C++ client server tutorial
Your Name
Apache License 2.0

2.2  写服务节点

        cs_ws/src/cpp_srvcli/src 目录中,创建一个名为 add_two_ints_server.cpp 的新文件并将以下代码粘贴到其中:

#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"#include void add(const std::shared_ptr request,std::shared_ptr      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 node = rclcpp::Node::make_shared("add_two_ints_server");rclcpp::Service::SharedPtr service =node->create_service("add_two_ints", &add);RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add two ints.");rclcpp::spin(node);rclcpp::shutdown();
}

2.3 运行代码

        前两个 #include 语句是您的包依赖项。

        add 函数将请求中的两个整数相加并将总和提供给响应,同时使用日志将其状态通知给控制台。

void add(const std::shared_ptr request,std::shared_ptr      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 函数逐行完成以下操作:

  • main函数执行完成以下操作:

    rclcpp::init(argc, argv);
    
  • 创建一个名为 add_two_ints_server 的节点:

    std::shared_ptr node = rclcpp::Node::make_shared("add_two_ints_server");
    
  • 为该节点创建一个名为 add_two_ints 的服务,并使用 &add 方法在网络上自动发布它:

    rclcpp::Service::SharedPtr service =
    node->create_service("add_two_ints", &add);
    
  • 准备就绪时打印日志消息:

    RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add two ints.");
    
  • 旋转节点,使服务可用。

    rclcpp::spin(node);
    

2.4 在CMakeLists.txt加入executable项

        add_executable 宏生成一个可执行文件,您可以使用 ros2 run 运行它。将以下代码块添加到 CMakeLists.txt 以创建名为服务器的可执行文件:

add_executable(server src/add_two_ints_server.cpp)
ament_target_dependencies(server
rclcpp example_interfaces)

        所以 ros2 run 可以找到可执行文件,将以下行添加到文件末尾,就在 ament_package() 之前:

install(TARGETSserverDESTINATION lib/${PROJECT_NAME})

        您现在可以构建您的包,获取本地安装文件并运行它,但让我们先创建客户端节点,以便您可以看到整个系统在工作。

三、写客户端代码

        cs_ws/src/cpp_srvcli/src 目录中,创建一个名为 add_two_ints_client.cpp 的新文件并将以下代码粘贴到其中:

#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"#include 
#include 
#include 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 node = rclcpp::Node::make_shared("add_two_ints_client");rclcpp::Client::SharedPtr client =node->create_client("add_two_ints");auto request = std::make_shared();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 node = rclcpp::Node::make_shared("add_two_ints_client");
rclcpp::Client::SharedPtr client =node->create_client("add_two_ints");

        接下来,创建请求。它的结构由前面提到的 .srv 文件定义。

auto request = std::make_shared();
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.");return 0;

        然后客户端发送它的请求,节点旋转直到它收到它的响应,或者失败。

3.2 追加executable项

        返回 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(serverrclcpp example_interfaces)add_executable(client src/add_two_ints_client.cpp)
ament_target_dependencies(clientrclcpp example_interfaces)install(TARGETSserverclientDESTINATION lib/${PROJECT_NAME})ament_package()

四、编译和执行

        最好在工作区 (cs_ws) 的根目录中运行 rosdep,以便在构建之前检查是否缺少依赖项:

    rosdep install -i --from-path src --rosdistro humble -y

        反回您的工作区 cs_ws 的根目录,并构建您的新包:

    colcon build --packages-select cpp_srvcli

        打开一个新终端,导航到 cs_ws,然后获取设置文件:

    . install/setup.bash

       1 现在运行服务节点:

    ros2 run cpp_srvcli server

        终端应返回以下消息,然后等待:

             [INFO] [rclcpp]: Ready to add two ints.

        2 打开另一个终端,再次从 cs_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 以停止节点旋转。

五、我们的疑问

        官方的这里例子给的很突兀。不难令人产生困惑:

  • 为什么要#include "example_interfaces/srv/add_two_ints.hpp"。?
  • 我自己编写一个其它的server和client需要哪些必要知识?

        1)跟踪#include "example_interfaces/srv/add_two_ints.hpp",发现事情很不简单,如果试图自己创作一个srv,需要吃透如下代码。

// generated from rosidl_generator_cpp/resource/idl__builder.hpp.em
// with input from example_interfaces:srv/AddTwoInts.idl
// generated code does not contain a copyright notice#ifndef EXAMPLE_INTERFACES__SRV__DETAIL__ADD_TWO_INTS__BUILDER_HPP_
#define EXAMPLE_INTERFACES__SRV__DETAIL__ADD_TWO_INTS__BUILDER_HPP_#include 
#include #include "example_interfaces/srv/detail/add_two_ints__struct.hpp"
#include "rosidl_runtime_cpp/message_initialization.hpp"namespace example_interfaces
{namespace srv
{namespace builder
{class Init_AddTwoInts_Request_b
{
public:explicit Init_AddTwoInts_Request_b(::example_interfaces::srv::AddTwoInts_Request & msg): msg_(msg){}::example_interfaces::srv::AddTwoInts_Request b(::example_interfaces::srv::AddTwoInts_Request::_b_type arg){msg_.b = std::move(arg);return std::move(msg_);}private:::example_interfaces::srv::AddTwoInts_Request msg_;
};class Init_AddTwoInts_Request_a
{
public:Init_AddTwoInts_Request_a(): msg_(::rosidl_runtime_cpp::MessageInitialization::SKIP){}Init_AddTwoInts_Request_b a(::example_interfaces::srv::AddTwoInts_Request::_a_type arg){msg_.a = std::move(arg);return Init_AddTwoInts_Request_b(msg_);}private:::example_interfaces::srv::AddTwoInts_Request msg_;
};}  // namespace builder}  // namespace srvtemplate
auto build();template<>
inline
auto build<::example_interfaces::srv::AddTwoInts_Request>()
{return example_interfaces::srv::builder::Init_AddTwoInts_Request_a();
}}  // namespace example_interfacesnamespace example_interfaces
{namespace srv
{namespace builder
{class Init_AddTwoInts_Response_sum
{
public:Init_AddTwoInts_Response_sum(): msg_(::rosidl_runtime_cpp::MessageInitialization::SKIP){}::example_interfaces::srv::AddTwoInts_Response sum(::example_interfaces::srv::AddTwoInts_Response::_sum_type arg){msg_.sum = std::move(arg);return std::move(msg_);}private:::example_interfaces::srv::AddTwoInts_Response msg_;
};}  // namespace builder}  // namespace srvtemplate
auto build();template<>
inline
auto build<::example_interfaces::srv::AddTwoInts_Response>()
{return example_interfaces::srv::builder::Init_AddTwoInts_Response_sum();
}}  // namespace example_interfaces#endif  // EXAMPLE_INTERFACES__SRV__DETAIL__ADD_TWO_INTS__BUILDER_HPP_

        2)自己实现一个srv,不依赖example才能说是掌握了server和client编程。因此,我们打算开始一个新的自我实现的svr案例。

        3)有个github示例需要掌握,这里记下地址:examples/main.cpp at rolling · ros2/examples · GitHub

        查看 ros2/examples 存储库中的 minimal_service 和 minimal_client 包。

相关内容

热门资讯

科技推荐.掌心麻将圈.有没有挂... 科技推荐.掌心麻将圈.有没有挂.[透视曝光猫腻]亲,掌心麻将圈这个游戏其实有挂的,确实是有挂的,需要...
科普实测“微乐陕西挖坑可不可以... 您好:微乐陕西挖坑这款游戏可以开挂,确实是有挂的,需要软件加微信【5951795】,很多玩家在微乐陕...
玩家实测“微乐邯郸麻将到底有挂... 您好:微乐邯郸麻将这款游戏可以开挂,确实是有挂的,需要软件加微信【69174242】,很多玩家在微乐...
发现一款“来来淮北麻将辅助助手... 您好:来来淮北麻将这款游戏可以开挂,确实是有挂的,需要了解加客服微信【3398215】很多玩家在这款...
(科技解密)“新蜜瓜拼三张.有... (科技解密)“新蜜瓜拼三张.有没有挂@太坑了果然有挂亲,新蜜瓜拼三张这个游戏其实有挂的,确实是有挂的...