• 正文
  • 相关推荐
申请入驻 产业图谱

ROS2 Service 服务通信详解|C++ 代码示例(与 Topic 话题对比)

1小时前
55
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

之前的文章,介绍过ROS2 中话题(Topic)通信,本篇来介绍另一种通信方式——服务(Service)

1 Service功能简介

ROS2 的 Service 是一问一答的请求 - 应答(Request-Response)通信模型。

与Topic的对比来看:

Topic:流式单向广播

      • Publisher 不停发,Subscriber 被动接收没有 “返回结果”适合持续数据流(摄像头、IMU、里程计)

Service:触发式同步交互

Client(客户端)主动发起一次请求(带参数)

Server(服务端)监听服务,收到请求执行业务逻辑

Server 生成应答数据返回给 Client

本次通信结束,一次请求对应一次回复

简单来说:Topic 是 “广播数据流”,Service 是 “调用函数,带入参、带回返回值”

2 API接口

2.1 服务端

2.1.1 create_service

template<typename ServiceT>
Service::SharedPtr create_service(
  const std::string & service_name,
  ServiceCallbackType callback,
  const rclcpp::QoS & qos_profile = rclcpp::ServicesQoS()
);

参数介绍

service_name:服务名字,字符串,全局话题名,推荐加/开头,如"add_two_ints"

callback:回调函数,收到客户端请求就执行。 回调函数固定签名:

void callback(
    const std::shared_ptr<XXXService::Request> request,
    std::shared_ptr<XXXService::Response> response
);

request:只读,客户端传过来的数据,request->a,

request->bresponse:输出参数,引用传递,你在回调里写response->sum = xxx,ROS2 自动把 response 发回客户端

qos_profile:QoS 质量策略,Service 默认rclcpp::ServicesQoS(),一般不用修改。

创建举例

service_ = this->create_service<test_02_service::srv::AddTwoInts>(
    "add_two_ints",
    std::bind(&AddTwoIntServer::service_callback, this, _1, _2)
);

std::bind 绑定类成员函数,_1对应 request,_2对应 response。

2.1.2 spin

rclcpp::spin(node);

服务端必须 spin,spin 会持续监听事件,等待客户端请求,触发回调。如果没有 spin,服务端不会收到任何请求。

2.2 客户端

2.2.1 create_client

template<typename ServiceT>
Client::SharedPtr create_client(
  const std::string & service_name,
  const rclcpp::QoS & qos_profile = rclcpp::ServicesQoS()
);

参数介绍

service_name:必须和服务端服务名一模一样,大小写敏感qos:服务 QoS,默认即可

创建举例

client_ = this->create_client<test_02_service::srv::AddTwoInts>("add_two_ints");

2.2.2 wait_for_service

等待服务端上线

bool wait_for_service(std::chrono::duration<Rep, Period> timeout)

返回值

    返回 true:服务已经就绪,可以发请求返回 false:超时没找到服务(服务端没启动、名字写错、网络不通)

建议每次发请求前调用,避免直接发送请求失败。

示例

if (!client_->wait_for_service(1s))
{
    RCLCPP_ERROR(this->get_logger(), "服务不可用!");
    return false;
}

2.2.3 async_send_request

异步发送请求

std::shared_future<typename ServiceT::Response::SharedPtr>
async_send_request(typename ServiceT::Request::SharedPtr request);
    • 传入:

request

    •  请求结构体指针,填充 a、b 等参数返回:

future

    •  异步结果句柄,

不会阻塞当前线程,只是把请求发出去。

ROS2 推荐使用async 异步接口,不推荐旧版本同步阻塞调用

2.2.4 spin_until_future_complete

一边 spin 节点处理事件,一边等待 future 应答返回

FutureReturnCode spin_until_future_complete(
  rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base,
  const std::shared_future<ResponseT::SharedPtr> & future,
  std::chrono::duration<Rep,Period> timeout = std::chrono::duration<Rep,Period>::max()
)

返回枚举:

rclcpp::FutureReturnCode::SUCCESS:成功收到应答

TIMEOUT:等待超时,没收到回复

INTERRUPTED:被中断

拿到 future 结果:auto response = result_future.get();

3 代码示例

3.1 时序图

3.2 工程创建

cd ~/dev_ws/src
ros2 pkg create --build-type ament_cmake test_04_service --dependencies rclcpp rosidl_default_generators
cd test_04_service

此时查看工程目录

xxpcb@xxpcb-ubuntu20:~/dev_ws/src/test_04_service$ tree
.
├── CMakeLists.txt
├── include
│   └── test_04_service
├── package.xml
└── src

3 directories, 2 files
xxpcb@xxpcb-ubuntu20:~/dev_ws/src/test_04_service$

然后创建代码

touch src/service_server.cpp
touch src/service_client.cpp

还有创建srv文件

mkdir srv
touch srv/AddTwoInts.srv

srv中写入如下内容:

# Request(Client → Server)
int64 a
int64 b
---
# Response(Server → Client)
int64 sum

这里---上面的客户端请求的参数, 下面是服务端回复的结果

此时的工程目录

xxpcb@xxpcb-ubuntu20:~/dev_ws/src/test_04_service$ tree
.
├── CMakeLists.txt
├── include
│   └── test_04_service
├── package.xml
├── src
│   ├── service_client.cpp
│   └── service_server.cpp
└── srv
    └── AddTwoInts.srv

4 directories, 5 files
xxpcb@xxpcb-ubuntu20:~/dev_ws/src/test_04_service$

3.3 服务端代码

service_server.cpp

主要逻辑是:

    create_service创建服务端service_callback进行请求处理rclcpp::spin
#include <rclcpp/rclcpp.hpp>
#include "test_04_service/srv/add_two_ints.hpp"

using namespace std::placeholders;

class AddTwoIntServer : public rclcpp::Node
{
public:
    AddTwoIntServer() : Node("add_two_ints_server")
    {
        service_ = this->create_service<test_04_service::srv::AddTwoInts>(
            "add_two_ints",
            std::bind(&AddTwoIntServer::service_callback, this, _1, _2)
        );
        RCLCPP_INFO(this->get_logger(), "服务端已启动,等待客户端请求...");
    }

private:
    void service_callback(
        const std::shared_ptr<test_04_service::srv::AddTwoInts::Request> request,
        std::shared_ptr<test_04_service::srv::AddTwoInts::Response> response
    )
    {
        response->sum = request->a + request->b;
        RCLCPP_INFO(this->get_logger(),
            "收到请求: a=%ld, b=%ld → sum=%ld",
            request->a, request->b, response->sum);
    }

    rclcpp::Service<test_04_service::srv::AddTwoInts>::SharedPtr service_;
};

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

3.4 客户端代码

service_client.cpp

主要逻辑是:

    create_client创建客户端send_request发送请求

    • async_send_request发送异步请求spin_until_future_complete
#include <rclcpp/rclcpp.hpp>
#include "test_04_service/srv/add_two_ints.hpp"

usingnamespacestd::chrono_literals;

class AddTwoIntClient :public rclcpp::Node
{
public:
    AddTwoIntClient() : Node("add_two_ints_client")
    {
        client_ = this->create_client<test_04_service::srv::AddTwoInts>("add_two_ints");
    }

    bool send_request(int64_t a, int64_t b)
    {
        if (!client_->wait_for_service(1s))
        {
            RCLCPP_ERROR(this->get_logger(), "服务不可用!");
            returnfalse;
        }

        auto request = std::make_shared<test_04_service::srv::AddTwoInts::Request>();
        request->a = a;
        request->b = b;

        auto result_future = client_->async_send_request(request);

        if (rclcpp::spin_until_future_complete(this->get_node_base_interface(), result_future)
            == rclcpp::FutureReturnCode::SUCCESS)
        {
            auto response = result_future.get();
            RCLCPP_INFO(this->get_logger(), "收到应答 sum = %ld", response->sum);
            returntrue;
        }
        else
        {
            RCLCPP_ERROR(this->get_logger(), "请求失败!");
            returnfalse;
        }
    }

private:
    rclcpp::Client<test_04_service::srv::AddTwoInts>::SharedPtr client_;
};

int main(int argc, char ** argv)
{
    rclcpp::init(argc, argv);
    auto node = std::make_shared<AddTwoIntClient>();
    node->send_request(100, 200);
    rclcpp::shutdown();
    return0;
}

3.5 运行结果

在CMake中添加:

# 生成srv接口
rosidl_generate_interfaces(${PROJECT_NAME}
  "srv/AddTwoInts.srv"
)

# 服务端可执行文件
add_executable(service_server src/service_server.cpp)
ament_target_dependencies(service_server
  rclcpp
)

# 把本包生成的srv接口链接到目标
rosidl_target_interfaces(service_server
  ${PROJECT_NAME} "rosidl_typesupport_cpp")

# 客户端可执行文件
add_executable(service_client src/service_client.cpp)
ament_target_dependencies(service_client
  rclcpp
)

# 把本包生成的srv接口链接到目标
rosidl_target_interfaces(service_client
  ${PROJECT_NAME} "rosidl_typesupport_cpp")

install(TARGETS
  service_server
  service_client
  DESTINATION lib/${PROJECT_NAME}
)

package.xml中添加一句

  <member_of_group>rosidl_interface_packages</member_of_group>

然后编译

cd ~/dev_ws
rm -rf build log install
colcon build --packages-select test_04_service
source install/setup.bash

运行服务端

ros2 run test_04_service service_server

另一窗口, 运行客户端

source install/setup.bash
ros2 run test_04_service service_client

运行结果如下:

4 总结

本篇介绍了ROS2中客户端-服务器的机制, 并通过C++示例代码演示使用方法.

相关推荐

控制科学与工程硕士,日常分享单片机、嵌入式、C/C++、Linux等学习经验干货~