ROS2- 服务-cpp py实现 -09

1.服务

Client-Server
Request-Response

话题Pub/Sub是没有返回的,适用于单向或大量的数据传递。
服务是双向的,客户端发送请求,服务端响应请求。

同一个服务(名称相同)有且只能有一个节点来提供
同一个服务可以被多个客户端调用

ros2 run examples_rclpy_minimal_service service
ros2 service list
ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 5,b: 10}"
可以看到客户端请求两个数字5+10,服务端返回15。

service相关的命令
ros2 service list
ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 5,b: 10}"
ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts
ros2 service type /add_two_ints # 查看服务接口类型
ros2 service find example_interfaces/srv/AddTwoInts # 查找使用某一接口的服务

root@ubuntu-16:~/stephen/chapt3/chapt3_ws# ros2 service list
/add_two_ints
/minimal_service/describe_parameters
/minimal_service/get_parameter_types
/minimal_service/get_parameters
/minimal_service/list_parameters
/minimal_service/set_parameters
/minimal_service/set_parameters_atomically
root@ubuntu-16:~/stephen/chapt3/chapt3_ws# ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 5,b: 10}"
requester: making request: example_interfaces.srv.AddTwoInts_Request(a=5, b=10)

response:
example_interfaces.srv.AddTwoInts_Response(sum=15)

root@ubuntu-16:~/stephen/chapt3/chapt3_ws# ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts
waiting for service to become available...
requester: making request: example_interfaces.srv.AddTwoInts_Request(a=0, b=0)

response:
example_interfaces.srv.AddTwoInts_Response(sum=0)

root@ubuntu-16:~/stephen/chapt3/chapt3_ws# ros2 service type /add_two_ints
example_interfaces/srv/AddTwoInts
root@ubuntu-16:~/stephen/chapt3/chapt3_ws# ros2 service find example_interfaces/srv/AddTwoInts
/add_two_ints

2. 服务的cpp实现

cd chapt3/chapt3_ws/src
ros2 pkg create example_service_rclcpp --build-type ament_cmake --dependencies rclcpp
touch example_service_rclcpp/src/service_server_01.cpp
touch example_service_rclcpp/src/service_client_01.cpp

service_server_01.cpp

#include "rclcpp/rclcpp.hpp"

class ServiceServer01 : public rclcpp::Node {
public:
  ServiceServer01(std::string name) : Node(name) {
    RCLCPP_INFO(this->get_logger(), "节点已启动:%s.", name.c_str());
  }

private:
};

int main(int argc, char** argv) {
  rclcpp::init(argc, argv);
  auto node = std::make_shared<ServiceServer01>("service_server_01");
  rclcpp::spin(node);
  rclcpp::shutdown();
  return 0;
}

service_client_01.cpp

#include "rclcpp/rclcpp.hpp"

class ServiceClient01 : public rclcpp::Node {
public:
  // 构造函数,有一个参数为节点名称
  ServiceClient01(std::string name) : Node(name) {
    RCLCPP_INFO(this->get_logger(), "节点已启动:%s.", name.c_str());
  }
private:
};

int main(int argc, char** argv) {
  rclcpp::init(argc, argv);
  /*创建对应节点的共享指针对象*/
  auto node = std::make_shared<ServiceClient01>("service_client_01");
  /* 运行节点,并检测退出信号*/
  rclcpp::spin(node);
  rclcpp::shutdown();
  return 0;
}

CMakeLists.txt

add_executable(service_client_01 src/service_client_01.cpp)
ament_target_dependencies(service_client_01 rclcpp)

add_executable(service_server_01 src/service_server_01.cpp)
ament_target_dependencies(service_server_01 rclcpp)

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

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

测试

cd chapt3/chapt3_ws/
colcon build --packages-select example_service_rclcpp

# 运行 service_server_01
source install/setup.bash
ros2 run example_service_rclcpp service_server_01

# 打开新终端运行  service_client_01
source install/setup.bash
ros2 run example_service_rclcpp service_client_01

3.服务端

如何利用自带的接口
ros2 interface show example_interfaces/srv/AddTwoInts

导入接口的三个步骤:
在CMakeLists.txt中导入,具体是先find_packages再ament_target_dependencies。
在packages.xml中导入,具体是添加depend标签并将消息接口写入。
在代码中导入,C++中是#include"消息功能包/xxx/xxx.hpp"。

具体:
CMakeLists.txt

find_package(example_interfaces REQUIRED)

add_executable(service_client_01 src/service_client_01.cpp)
ament_target_dependencies(service_client_01 rclcpp example_interfaces)

add_executable(service_server_01 src/service_server_01.cpp)
ament_target_dependencies(service_server_01 rclcpp example_interfaces)

packages.xml

<depend>example_interfaces</depend>

代码:
create_service,参考rclcpp API文档即可
ServiceT,消息接口example_interfaces::srv::AddTwoInts
service_name,服务名称
callback,回调函数,使用成员函数作为回调函数,std::bind进行转换
qos_profile,服务质量配置文件,默认rmw_qos_profile_services_default
group,调用服务的回调组,默认nullptr

#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"


class ServiceServer01 : public rclcpp::Node {
public:
  ServiceServer01(std::string name) : Node(name) {
    RCLCPP_INFO(this->get_logger(), "节点已启动:%s.", name.c_str());

    // 创建服务
    add_ints_server_ =
      this->create_service<example_interfaces::srv::AddTwoInts>(
        "add_two_ints_srv",
        std::bind(&ServiceServer01::handle_add_two_ints, this,
                  std::placeholders::_1, std::placeholders::_2));
  }

private:
  // 声明一个服务
  rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr
    add_ints_server_;

  // 收到请求的处理函数
  void handle_add_two_ints(
    const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
    std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> response) {
    RCLCPP_INFO(this->get_logger(), "收到a: %ld b: %ld", request->a,
                request->b);
    response->sum = request->a + request->b;
  };
};

int main(int argc, char** argv) {
  rclcpp::init(argc, argv);
  auto node = std::make_shared<ServiceServer01>("service_server_01");
  rclcpp::spin(node);
  rclcpp::shutdown();
  return 0;
}

测试

cd chapt3_ws/
colcon build --packages-select example_service_rclcpp
source install/setup.bash
ros2 run example_service_rclcpp service_server_01

可以看到我们声明的服务
ros2 service list
使用命令行进行调用
ros2 service call /add_two_ints_srv example_interfaces/srv/AddTwoInts "{a: 5,b: 10}"

root@ubuntu-16:~/stephen/chapt3/chapt3_ws# ros2 service list
/add_two_ints_srv
/service_server_01/describe_parameters
/service_server_01/get_parameter_types
/service_server_01/get_parameters
/service_server_01/list_parameters
/service_server_01/set_parameters
/service_server_01/set_parameters_atomically
root@ubuntu-16:~/stephen/chapt3/chapt3_ws# ros2 service call /add_two_ints_srv example_interfaces/srv/AddTwoInts "{a: 5,b: 10}"
waiting for service to become available...
requester: making request: example_interfaces.srv.AddTwoInts_Request(a=5, b=10)

response:
example_interfaces.srv.AddTwoInts_Response(sum=15)


root@ubuntu-16:~/stephen/chapt3/chapt3_ws# vi src/example_service_rclcpp/CMakeLists.txt
root@ubuntu-16:~/stephen/chapt3/chapt3_ws# vi src/example_service_rclcpp/package.xml
root@ubuntu-16:~/stephen/chapt3/chapt3_ws# vi src/example_service_rclcpp/src/service_server_01.cpp
root@ubuntu-16:~/stephen/chapt3/chapt3_ws# vi src/example_service_rclcpp/src/service_client_01.cpp
root@ubuntu-16:~/stephen/chapt3/chapt3_ws# vi src/example_service_rclcpp/src/service_server_01.cpp
root@ubuntu-16:~/stephen/chapt3/chapt3_ws# colcon build --packages-select example_service_rclcpp
Starting >>> example_service_rclcpp
Finished <<< example_service_rclcpp [5.50s]

Summary: 1 package finished [5.80s]
root@ubuntu-16:~/stephen/chapt3/chapt3_ws# source install/setup.bash
root@ubuntu-16:~/stephen/chapt3/chapt3_ws# ros2 run example_service_rclcpp service_server_01
[INFO] [1749515671.413449518] [service_server_01]: 节点已启动:service_server_01.
[INFO] [1749515695.230367691] [service_server_01]: 收到a: 5 b: 10

4. 客户端

写代码时看API文档,创建客户端 https://link.zhihu.com/?target=https%3A//docs.ros2.org/latest/api/rclcpp/classrclcpp_1_1Node.html%23aed42f345ae1de3a1979d5a8076127199
create_client:
service_name,服务名称
qos_profile,服务质量配置文件,默认rmw_qos_profile_services_default
group,调用服务的回调组,默认nullptr

函数async_send_request()同时传入两个参数
request,请求的消息,这里用于放a,b两个数。
CallBack,回调函数,异步接收服务器的返回的函数

wait_for_service
timeout

参数就一个,等待的时间,返回值是bool类型的,上线了就是true,不上线就是false。
之所以会用的这个函数的原因是,再发送请求之前保证服务端启动了,避免发送一个请求出去而无人响应的尴尬局面。

回调函数
void result_callback_(rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedFuture result_future)
函数的参数是客户端AddTwoInts类型的SharedFuture对象,
利用C++11的新特性std::shared_future创建的SharedResponse类模板,
类模板 std::shared_future 提供访问异步操作结果的机制,类似 std::future ,除了允许多个线程等候同一共享状态。
使用get函数即可获取结果。

最后还要修改下主函数:
//增加这一行,node->send_request(5, 6);,计算5+6结果
node->send_request(5, 6);

客户端最终代码:

#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"


class ServiceClient01 : public rclcpp::Node {
public:
  // 构造函数,有一个参数为节点名称
  ServiceClient01(std::string name) : Node(name) {
    RCLCPP_INFO(this->get_logger(), "节点已启动:%s.", name.c_str());
    // 创建客户端
    client_ = this->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints_srv");
  }

  void send_request(int a, int b) {
    RCLCPP_INFO(this->get_logger(), "计算%d+%d", a, b);

    // 1.等待服务端上线
    while (!client_->wait_for_service(std::chrono::seconds(1))) {
      //等待时检测rclcpp的状态
      if (!rclcpp::ok()) {
        RCLCPP_ERROR(this->get_logger(), "等待服务的过程中被打断...");
        return;
      }
      RCLCPP_INFO(this->get_logger(), "等待服务端上线中");
    }

    // 2.构造请求的
    auto request =
      std::make_shared<example_interfaces::srv::AddTwoInts_Request>();
    request->a = a;
    request->b = b;

    // 3.发送异步请求,然后等待返回,返回时调用回调函数
    client_->async_send_request(
      request, std::bind(&ServiceClient01::result_callback_, this,
                         std::placeholders::_1));
  };

private:
  // 声明客户端
  rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client_;

  void result_callback_(
    rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedFuture
      result_future) {
    auto response = result_future.get();
    RCLCPP_INFO(this->get_logger(), "计算结果:%ld", response->sum);
  }
};


int main(int argc, char** argv) {
  rclcpp::init(argc, argv);
  /*创建对应节点的共享指针对象*/
  auto node = std::make_shared<ServiceClient01>("service_client_01");
  /* 运行节点,并检测退出信号*/
  //增加这一行,node->send_request(5, 6);,计算5+6结果
  node->send_request(5, 6);

  rclcpp::spin(node);
  rclcpp::shutdown();
  return 0;
}

测试:

root@ubuntu-16:~/stephen/chapt3/chapt3_ws# 
colcon build --packages-select example_service_rclcpp
source install/setup.bash
ros2 run example_service_rclcpp service_client_01

Summary: 1 package finished [4.92s]
[INFO] [1749516734.441409617] [service_client_01]: 节点已启动:service_client_01.
[INFO] [1749516734.442560823] [service_client_01]: 计算5+6
[INFO] [1749516735.442674784] [service_client_01]: 等待服务端上线中
[INFO] [1749516736.442809463] [service_client_01]: 等待服务端上线中
[INFO] [1749516768.349921900] [service_client_01]: 计算结果:11

另一端启动服务:

root@ubuntu-16:~/stephen/chapt3/chapt3_ws# ros2 run example_service_rclcpp service_server_01
[INFO] [1749516767.244634848] [service_server_01]: 节点已启动:service_server_01.
[INFO] [1749516768.349500384] [service_server_01]: 收到a: 5 b: 6

python版

创建功能包和节点
cd chapt3/chapt3_ws/src
ros2 pkg create example_service_rclpy --build-type ament_python --dependencies rclpy example_interfaces --node-name service_server_02

发现,依赖,setup.py中的安装配置,ROS2加好了
--node-name service_server_02会帮你创建好节点文件和添加执行文件

cd example_service_rclpy/example_service_rclpy/
touch service_client_02.py

修改setup.py
entry_points={
'console_scripts': [
"service_client_02 = example_service_rclpy.service_client_02:main",
"service_server_02 = example_service_rclpy.service_server_02:main"
],
},

service_server_02

#!/usr/bin/env python3
import rclpy
from rclpy.node import Node

class ServiceServer02(Node):
    def __init__(self,name):
        super().__init__(name)
        self.get_logger().info("节点已启动:%s!" % name)
        
def main(args=None):
    rclpy.init(args=args) # 初始化rclpy
    node = ServiceServer02("service_server_02")  # 新建一个节点
    rclpy.spin(node) # 保持节点运行,检测是否收到退出指令(Ctrl+C)
    rclpy.shutdown() # 关闭rclpy

service_client_02

#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts

class ServiceClient02(Node):
    def __init__(self,name):
        super().__init__(name)
        self.get_logger().info("节点已启动:%s!" % name)

def main(args=None):
    rclpy.init(args=args) # 初始化rclpy
    node = ServiceClient02("service_client_02")  # 新建一个节点
    node.send_request(3,4)
    rclpy.spin(node) # 保持节点运行,检测是否收到退出指令(Ctrl+C)
    rclpy.shutdown() # 关闭rclpy

server端的最终代码

#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts


class ServiceServer02(Node):
    def __init__(self,name):
        super().__init__(name)
        self.get_logger().info("节点已启动:%s!" % name)
        self.add_ints_server_ = self.create_service(AddTwoInts,"add_two_ints_srv", self.handle_add_two_ints)

    def handle_add_two_ints(self,request, response):
        self.get_logger().info(f"收到请求,计算{request.a} + {request.b}")
        response.sum = request.a + request.b
        return response

def main(args=None):
    rclpy.init(args=args) # 初始化rclpy
    node = ServiceServer02("service_server_02")  # 新建一个节点
    rclpy.spin(node) # 保持节点运行,检测是否收到退出指令(Ctrl+C)
    rclpy.shutdown() # 关闭rclpy

测试

cd chapt3/chapt3_ws/src
colcon build --packages-select example_service_rclpy
source install/setup.bash
ros2 run example_service_rclpy service_server_02


Starting >>> example_service_rclpy
Finished <<< example_service_rclpy [1.16s]

Summary: 1 package finished [1.46s]
[INFO] [1749517444.683091522] [service_server_02]: 节点已启动:service_server_02!
[INFO] [1749517454.423798292] [service_server_02]: 收到请求,计算5 + 10

另一个终端

ros2 service call /add_two_ints_srv example_interfaces/srv/AddTwoInts "{a: 5,b: 10}"

waiting for service to become available...
requester: making request: example_interfaces.srv.AddTwoInts_Request(a=5, b=10)

response:
example_interfaces.srv.AddTwoInts_Response(sum=15)

客户端实现
service_client_02.py

#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts


class ServiceClient02(Node):
    def __init__(self,name):
        super().__init__(name)
        self.get_logger().info("节点已启动:%s!" % name)
        self.client_ = self.create_client(AddTwoInts,"add_two_ints_srv")

    def result_callback_(self, result_future):
        response = result_future.result()
        self.get_logger().info(f"收到返回结果:{response.sum}")

    def send_request(self, a, b):
        while rclpy.ok() and self.client_.wait_for_service(1)==False:
            self.get_logger().info(f"等待服务端上线....")

        request = AddTwoInts.Request()
        request.a = a
        request.b = b
        self.client_.call_async(request).add_done_callback(self.result_callback_)

def main(args=None):
    rclpy.init(args=args) # 初始化rclpy
    node = ServiceClient02("service_client_02")  # 新建一个节点
    node.send_request(3,4)
    rclpy.spin(node) # 保持节点运行,检测是否收到退出指令(Ctrl+C)
    rclpy.shutdown() # 关闭rclpy
root@ubuntu-16:~/stephen/chapt3/chapt3_ws/src# vi example_service_rclpy/example_service_rclpy/service_client_02.py
root@ubuntu-16:~/stephen/chapt3/chapt3_ws/src# cd -
/root/stephen/chapt3/chapt3_ws
root@ubuntu-16:~/stephen/chapt3/chapt3_ws# ros2 run example_service_rclpy service_server_02
[INFO] [1749517751.376593714] [service_server_02]: 节点已启动:service_server_02!
[INFO] [1749517904.467982023] [service_server_02]: 收到请求,计算3 + 4



colcon build --packages-select example_service_rclpy
source install/setup.bash
root@ubuntu-16:~/stephen/chapt3/chapt3_ws# ros2 run example_service_rclpy service_client_02
[INFO] [1749517904.462961109] [service_client_02]: 节点已启动:service_client_02!
[INFO] [1749517904.469175061] [service_client_02]: 收到返回结果:7
posted @ 2025-06-10 09:25  jack-chen666  阅读(60)  评论(0)    收藏  举报