【ROS2】Beginner: Client libraries - 服务Service示例 C++ & Python

部署运行你感兴趣的模型镜像

一、编写简单的C++服务端与客户端

目标:使用C++创建并运行服务端(service)和客户端(client)节点。

教程级别:初学者
预计时间:20分钟

目录

  • 背景
  • 前提条件
  • 任务
    1. 创建功能包
    2. 编写服务端节点
    3. 编写客户端节点
    4. 构建并运行
  • 总结
  • 下一步

背景

当节点通过服务(service)进行通信时,发送数据请求的节点称为客户端节点,响应请求的节点称为服务端节点。请求和响应的结构由.srv文件定义。

本教程使用的示例是一个简单的整数加法系统:一个节点请求两个整数的和,另一个节点返回计算结果。

前提条件

在之前的教程中,你已学会如何创建工作空间和功能包。

任务

1. 创建功能包

  1. 打开新终端,配置ROS 2环境(确保ros2命令可正常使用)。
  2. 进入之前教程中创建的ros2_ws目录。
  3. 功能包需在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.xmlCMakeLists.txt中添加必要的依赖项。
  • example_interfaces是包含本教程所需.srv文件的功能包,该文件定义了请求和响应的结构:
    int64 a
    int64 b
    ---
    int64 sum
    
    其中,虚线以上的两行是请求参数,虚线以下是响应结果。
1.1 更新package.xml

由于创建功能包时使用了--dependencies选项,无需手动向package.xmlCMakeLists.txt添加依赖。但仍需补充功能包描述、维护者信息和许可证信息:

<description>C++ client server tutorial</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache-2.0</license>

2. 编写服务端节点

  1. 进入ros2_ws/src/cpp_srvcli/src目录,创建名为add_two_ints_server.cpp的新文件。
  2. 粘贴以下代码到文件中:
    #include <cinttypes>
    #include <memory>
    
    #include "example_interfaces/srv/add_two_ints.hpp"
    #include "rclcpp/rclcpp.hpp"
    
    using AddTwoInts = example_interfaces::srv::AddTwoInts;
    rclcpp::Node::SharedPtr g_node = nullptr;
    
    void handle_service(
      const std::shared_ptr<rmw_request_id_t> request_header,
      const std::shared_ptr<AddTwoInts::Request> request,
      const std::shared_ptr<AddTwoInts::Response> response)
    {
      (void)request_header;
      RCLCPP_INFO(
        g_node->get_logger(), "request: %" PRId64 " + %" PRId64,
        request->a, request->b);
      response->sum = request->a + request->b;
    }
    
    int main(int argc, char **argv)
    {
      rclcpp::init(argc, argv);
      g_node = rclcpp::Node::make_shared("minimal_service");
      auto server = g_node->create_service<AddTwoInts>("add_two_ints", handle_service);
      rclcpp::spin(g_node);
      rclcpp::shutdown();
      g_node = nullptr;
      return 0;
    }
    
2.1 代码解析
  • 开头的#include语句引入了功能包依赖的头文件:

    • example_interfaces/srv/add_two_ints.hpp:包含加法服务的请求/响应结构定义。
    • rclcpp/rclcpp.hpp:ROS 2 C++客户端库核心头文件。
  • handle_service函数:
    接收请求中的两个整数,计算它们的和并赋值给响应,同时通过日志在控制台输出请求状态。

    void handle_service(
      const std::shared_ptr<rmw_request_id_t> request_header,
      const std::shared_ptr<AddTwoInts::Request> request,
      const std::shared_ptr<AddTwoInts::Response> response)
    {
      (void)request_header;  // 未使用的参数,避免编译警告
      RCLCPP_INFO(
        g_node->get_logger(), "request: %" PRId64 " + %" PRId64,
        request->a, request->b);  // 打印请求的两个整数
      response->sum = request->a + request->b;  // 计算和并赋值给响应
    }
    
  • main函数逐行功能:

    1. 初始化ROS 2 C++客户端库:
      rclcpp::init(argc, argv);
      
    2. 创建名为minimal_service的节点:
      g_node = rclcpp::Node::make_shared("minimal_service");
      
    3. 为节点创建名为add_two_ints的服务,并绑定handle_service函数处理请求:
      auto server = g_node->create_service<AddTwoInts>("add_two_ints", handle_service);
      
    4. 循环运行节点,使服务保持可用状态:
      rclcpp::spin(g_node);
      
2.2 添加可执行文件配置

CMakeLists.txt中,在依赖项配置下方添加以下代码块,生成名为server的可执行文件:

add_executable(server src/add_two_ints_server.cpp)
target_link_libraries(server PUBLIC
  rclcpp::rclcpp
  ${example_interfaces_TARGETS}
)

为了让ros2 run能找到该可执行文件,在CMakeLists.txt末尾、ament_package()之前添加以下安装配置:

install(TARGETS
  server
  DESTINATION lib/${PROJECT_NAME}
)

此时可构建功能包并运行服务端,但建议先编写客户端节点,以便观察完整的服务通信流程。

3. 编写客户端节点

  1. 仍在ros2_ws/src/cpp_srvcli/src目录下,创建名为add_two_ints_client.cpp的新文件。
  2. 粘贴以下代码到文件中:
    #include <chrono>
    #include <cinttypes>
    #include <memory>
    
    #include "example_interfaces/srv/add_two_ints.hpp"
    #include "rclcpp/rclcpp.hpp"
    
    using AddTwoInts = example_interfaces::srv::AddTwoInts;
    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;
      }
    
      // 创建名为"minimal_client"的节点
      auto node = rclcpp::Node::make_shared("minimal_client");
      // 创建客户端,指定服务类型为AddTwoInts,服务名为"add_two_ints"
      auto client = node->create_client<AddTwoInts>("add_two_ints");
    
      // 等待服务端启动(最多等待1秒/次,直到服务可用)
      while (!client->wait_for_service(1s)) {
        if (!rclcpp::ok()) {  // 若节点被中断(如按Ctrl+C),退出程序
          RCLCPP_ERROR(node->get_logger(), "Interrupted while waiting for the service. Exiting.");
          return 0;
        }
        RCLCPP_INFO(node->get_logger(), "service not available, waiting again...");
      }
    
      // 创建服务请求对象,赋值命令行传入的两个整数
      auto request = std::make_shared<AddTwoInts::Request>();
      request->a = atoll(argv[1]);
      request->b = atoll(argv[2]);
    
      // 发送异步请求,并定义响应回调函数
      auto result_future = client->async_send_request(request);
      // 等待响应结果(超时时间为5秒)
      if (rclcpp::spin_until_future_complete(node, result_future, 5s) ==
        rclcpp::FutureReturnCode::SUCCESS)
      {
        // 响应成功,打印结果
        auto result = result_future.get();
        RCLCPP_INFO(
          node->get_logger(), "Result of add_two_ints: %" PRId64 " + %" PRId64 " = %" PRId64,
          request->a, request->b, result->sum);
      } else {
        // 响应超时或失败
        RCLCPP_ERROR(node->get_logger(), "Failed to call service add_two_ints");
      }
    
      // 关闭节点并清理资源
      rclcpp::shutdown();
      return 0;
    }
    
3.1 代码解析
  • 命令行参数检查:确保运行客户端时传入两个整数(作为加法的操作数),若参数不足则提示用法并退出:

    if (argc != 3) {
      RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "usage: add_two_ints_client X Y");
      return 1;
    }
    
  • 服务等待逻辑:客户端会循环等待服务端启动,每次等待1秒,直到服务可用或节点被中断:

    while (!client->wait_for_service(1s)) {
      if (!rclcpp::ok()) {
        RCLCPP_ERROR(node->get_logger(), "Interrupted while waiting for the service. Exiting.");
        return 0;
      }
      RCLCPP_INFO(node->get_logger(), "service not available, waiting again...");
    }
    
  • 请求发送与响应处理:

    1. 创建请求对象,将命令行传入的字符串参数转换为整数并赋值:
      auto request = std::make_shared<AddTwoInts::Request>();
      request->a = atoll(argv[1]);
      request->b = atoll(argv[2]);
      
    2. 发送异步请求,获取未来(future)对象以等待响应:
      auto result_future = client->async_send_request(request);
      
    3. 等待响应结果(超时时间5秒),成功则打印结果,失败则提示错误:
      if (rclcpp::spin_until_future_complete(node, result_future, 5s) ==
        rclcpp::FutureReturnCode::SUCCESS)
      {
        auto result = result_future.get();
        RCLCPP_INFO(
          node->get_logger(), "Result of add_two_ints: %" PRId64 " + %" PRId64 " = %" PRId64,
          request->a, request->b, result->sum);
      } else {
        RCLCPP_ERROR(node->get_logger(), "Failed to call service add_two_ints");
      }
      
3.2 添加客户端可执行文件配置

CMakeLists.txt中,在服务端可执行文件配置下方添加以下代码,生成名为client的可执行文件:

add_executable(client src/add_two_ints_client.cpp)
target_link_libraries(client PUBLIC
  rclcpp::rclcpp
  ${example_interfaces_TARGETS}
)

更新安装配置,将client可执行文件一并安装(在install(TARGETS块中添加client):

install(TARGETS
  server
  client
  DESTINATION lib/${PROJECT_NAME}
)

保存CMakeLists.txt,此时服务端和客户端的代码与配置已完成。

4. 构建并运行

  1. 检查依赖(仅Linux系统):
    在工作空间根目录ros2_ws下运行rosdep,确保无缺失依赖:

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

    macOS和Windows系统可跳过此步骤(rosdep仅支持Linux)。

  2. 构建功能包
    ros2_ws目录下执行构建命令,仅构建cpp_srvcli功能包以节省时间:

    # Linux/macOS
    colcon build --packages-select cpp_srvcli
    
    # Windows(需使用VS 2019的x64 Native Tools Command Prompt)
    colcon build --merge-install --packages-select cpp_srvcli
    

    (Windows需--merge-install参数,避免长路径问题)

  3. 运行服务端

    • 打开新终端,进入ros2_ws目录,配置环境:
      # Linux
      source install/setup.bash
      
      # macOS
      . install/setup.bash
      
      # Windows
      call install/setup.bat
      
    • 启动服务端节点:
      ros2 run cpp_srvcli server
      

    终端会保持运行状态,等待客户端请求。

  4. 运行客户端

    • 再打开一个新终端,同样进入ros2_ws并配置环境。
    • 运行客户端节点,传入两个整数(如35):
      ros2 run cpp_srvcli client 3 5
      
    • 客户端终端会输出结果:
      [INFO] [minimal_client]: Result of add_two_ints: 3 + 5 = 8
      
    • 服务端终端会同步输出请求信息:
      [INFO] [minimal_service]: request: 3 + 5
      
  5. 停止节点
    在每个终端中按Ctrl+C停止节点运行。

总结

你已创建了通过服务通信的两个节点:

  • 服务端节点接收客户端的整数加法请求,计算并返回结果。
  • 客户端节点发送带参数的请求,等待并打印服务端的响应。

在运行前,你通过package.xmlCMakeLists.txt配置了依赖项与可执行文件,确保ROS 2能正确构建和找到节点。

下一步

接下来,你可以学习如何创建更复杂的ROS 2功能,例如结合发布者/订阅者与服务的节点,或自定义.srv/.msg文件以满足特定通信需求。

二、编写简单的Python服务端与客户端

目标:使用Python创建并运行服务端(service)和客户端(client)节点。

教程级别:初学者
预计时间:20分钟

目录

  • 背景
  • 前提条件
  • 任务
    1. 创建功能包
    2. 编写服务端节点
    3. 编写客户端节点
    4. 构建并运行
  • 总结
  • 下一步

背景

当节点通过服务(service)通信时,发送数据请求的节点称为客户端节点,响应请求的节点称为服务端节点。请求与响应的结构由.srv文件定义。

本教程使用的示例是一个简单的整数加法系统:一个节点请求两个整数的和,另一个节点返回计算结果。

前提条件

在之前的教程中,你已学会如何创建工作空间和功能包。

任务

1. 创建功能包

  1. 打开新终端,配置ROS 2环境(确保ros2命令可正常使用)。
  2. 进入之前教程中创建的ros2_ws目录。
  3. 功能包需在src目录下创建(而非工作空间根目录),进入ros2_ws/src并执行以下命令创建新功能包:
    ros2 pkg create --build-type ament_python --license Apache-2.0 py_srvcli --dependencies rclpy example_interfaces
    

终端会返回确认信息,提示py_srvcli功能包及其所需的所有文件和文件夹已创建。

  • --dependencies参数会自动在package.xml中添加必要的依赖项。
  • example_interfaces是包含本教程所需.srv文件的功能包,该文件定义了请求和响应的结构:
    int64 a
    int64 b
    ---
    int64 sum
    
    其中,虚线以上的两行是请求参数,虚线以下是响应结果。
1.1 更新package.xml

由于创建功能包时使用了--dependencies选项,无需手动向package.xml添加依赖。但仍需补充功能包描述、维护者信息和许可证信息:

<description>Python client server tutorial</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache-2.0</license>
1.2 更新setup.py

setup.py文件中,为maintainermaintainer_emaildescriptionlicense字段补充与package.xml一致的信息:

maintainer='Your Name',
maintainer_email='you@email.com',
description='Python client server tutorial',
license='Apache-2.0',

2. 编写服务端节点

  1. 进入ros2_ws/src/py_srvcli/py_srvcli目录(与功能包同名的Python包目录)。
  2. 创建名为service_member_function.py的新文件,粘贴以下代码:
    from example_interfaces.srv import AddTwoInts
    
    import rclpy
    from rclpy.executors import ExternalShutdownException
    from rclpy.node import Node
    
    
    class MinimalService(Node):
    
        def __init__(self):
            super().__init__('minimal_service')
            self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)
    
        def add_two_ints_callback(self, request, response):
            response.sum = request.a + request.b
            self.get_logger().info('Incoming request\na: %d b: %d' % (request.a, request.b))
            return response
    
    
    def main():
        try:
            with rclpy.init():
                minimal_service = MinimalService()
                rclpy.spin(minimal_service)
        except (KeyboardInterrupt, ExternalShutdownException):
            pass
    
    
    if __name__ == '__main__':
        main()
    
2.1 代码解析
  • 导入依赖

    • from example_interfaces.srv import AddTwoInts:导入example_interfaces功能包中的AddTwoInts服务类型,包含请求和响应的结构定义。
    • import rclpy及后续语句:导入ROS 2 Python客户端库的核心接口,用于创建节点和处理回调。
  • MinimalService类构造函数
    初始化节点并命名为minimal_service,同时创建服务:

    • 服务类型:AddTwoInts(与.srv文件对应)。
    • 服务名称:add_two_ints(需与客户端请求的服务名称一致)。
    • 回调函数:self.add_two_ints_callback(用于处理客户端请求)。
    def __init__(self):
        super().__init__('minimal_service')
        self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)
    
  • 服务回调函数
    接收客户端请求的两个整数request.arequest.b,计算它们的和并赋值给response.sum,同时通过日志打印请求信息,最后返回响应。

    def add_two_ints_callback(self, request, response):
        response.sum = request.a + request.b
        self.get_logger().info('Incoming request\na: %d b: %d' % (request.a, request.b))
        return response
    
  • main函数
    初始化ROS 2 Python客户端库,实例化MinimalService类创建服务端节点,通过rclpy.spin循环运行节点以处理回调(直到节点被中断)。

2.2 添加入口点

为了让ros2 run命令能找到并运行服务端节点,需在ros2_ws/src/py_srvcli/setup.py文件的'console_scripts':括号内添加以下入口点:

'service = py_srvcli.service_member_function:main',

3. 编写客户端节点

  1. 仍在ros2_ws/src/py_srvcli/py_srvcli目录下,创建名为client_member_function.py的新文件。
  2. 粘贴以下代码:
    from example_interfaces.srv import AddTwoInts
    
    import rclpy
    from rclpy.executors import ExternalShutdownException
    from rclpy.node import Node
    
    
    class MinimalClientAsync(Node):
    
        def __init__(self):
            super().__init__('minimal_client_async')
            self.cli = self.create_client(AddTwoInts, 'add_two_ints')
            while not self.cli.wait_for_service(timeout_sec=1.0):
                self.get_logger().info('service not available, waiting again...')
            self.req = AddTwoInts.Request()
    
        def send_request(self):
            self.req.a = 41
            self.req.b = 1
            return self.cli.call_async(self.req)
    
    
    def main(args=None):
        try:
            with rclpy.init(args=args):
                minimal_client = MinimalClientAsync()
                future = minimal_client.send_request()
                rclpy.spin_until_future_complete(minimal_client, future)
                response = future.result()
                minimal_client.get_logger().info(
                    'Result of add_two_ints: for %d + %d = %d' %
                    (minimal_client.req.a, minimal_client.req.b, response.sum))
        except (KeyboardInterrupt, ExternalShutdownException):
            pass
    
    
    if __name__ == '__main__':
        main()
    
3.1 代码解析
  • 导入依赖:与服务端代码一致,导入AddTwoInts服务类型和ROS 2 Python核心库。

  • MinimalClientAsync类构造函数

    1. 初始化节点并命名为minimal_client_async
    2. 创建客户端:服务类型为AddTwoInts,服务名称为add_two_ints(需与服务端完全一致,否则无法通信)。
    3. 等待服务端启动:通过while循环每秒检查一次服务是否可用,若不可用则打印日志提示。
    4. 创建请求对象:初始化AddTwoInts.Request()用于存储请求参数。
    def __init__(self):
        super().__init__('minimal_client_async')
        self.cli = self.create_client(AddTwoInts, 'add_two_ints')
        while not self.cli.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('service not available, waiting again...')
        self.req = AddTwoInts.Request()
    
  • send_request方法
    为请求对象赋值(a=41b=1),通过call_async发送异步请求,并返回用于获取响应结果的future对象。

    def send_request(self):
        self.req.a = 41
        self.req.b = 1
        return self.cli.call_async(self.req)
    
  • main函数

    1. 初始化ROS 2环境,实例化客户端节点。
    2. 发送请求并获取future对象。
    3. 通过rclpy.spin_until_future_complete等待响应结果(直到请求完成或超时)。
    4. 打印响应结果(两个整数的和)。

警告:不要在ROS 2回调函数中使用rclpy.spin_until_future_complete,详情可参考同步死锁相关文档

3.2 添加入口点

setup.py文件的entry_points字段中,在服务端入口点下方添加客户端入口点。最终entry_points字段应如下所示:

entry_points={
    'console_scripts': [
        'service = py_srvcli.service_member_function:main',
        'client = py_srvcli.client_member_function:main',
    ],
},

4. 构建并运行

  1. 检查依赖(仅Linux系统):
    在工作空间根目录ros2_ws下运行rosdep,确保无缺失依赖:

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

    macOS和Windows系统可跳过此步骤(rosdep仅支持Linux)。

  2. 构建功能包
    ros2_ws目录下执行构建命令,仅构建py_srvcli功能包以节省时间:

    colcon build --packages-select py_srvcli
    
  3. 运行服务端

    • 打开新终端,进入ros2_ws目录,配置环境:
      # Linux
      source install/setup.bash
      
      # macOS
      . install/setup.bash
      
      # Windows
      call install/setup.bat
      
    • 启动服务端节点:
      ros2 run py_srvcli service
      

    终端会保持运行状态,等待客户端请求。

  4. 运行客户端

    • 再打开一个新终端,同样进入ros2_ws并配置环境。
    • 运行客户端节点:
      ros2 run py_srvcli client
      
    • 客户端终端会输出结果:
      [INFO] [minimal_client_async]: Result of add_two_ints: for 41 + 1 = 42
      
    • 服务端终端会同步输出请求信息:
      [INFO] [minimal_service]: Incoming request
      a: 41 b: 1
      
  5. 停止节点
    在服务端终端中按Ctrl+C停止节点运行。

总结

你已创建了通过服务通信的两个Python节点:

  • 服务端节点接收客户端的整数加法请求,计算并返回结果,同时打印请求日志。
  • 客户端节点发送预设的整数请求(41和1),等待并打印服务端返回的求和结果。

通过配置package.xmlsetup.py,你完成了依赖项和入口点的设置,确保ROS 2能正确构建和运行节点,最终实现了服务端与客户端的完整通信流程。

下一步

在之前的教程中,你已使用接口(.msg/.srv)实现了话题和服务的数据传递。接下来,你可以学习如何创建自定义接口,以满足更灵活的通信需求。

您可能感兴趣的与本文相关的镜像

Python3.11

Python3.11

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ray.so

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值