前言
学习赵虚左老师ROS2课程第二节通信机制 服务通信 学习记录
一、服务通信
服务通信是以请求响应的方式实现不同节点之间数据传输的通信机制
客户端(service client)通过一个服务话题(topic)发送请求(Request)给服务端(service server),服务端作出响应(Response)通过服务话题反馈给客户端
服务端与客户端是一对多的关系
适用于:偶然的、对实时性有要求、有一定逻辑处理需求的数据传输场景
二、示例
客户端发送两个整数给服务端,服务端将整数相加并且返回给客户端
三、步骤
1.新建功能包
新建功能包cpp02_service py02_service
ros2 pkg create cpp02_service --build-type ament_cmake --dependencies rclcpp base_interfaces_demo --node-name demo01_server
ros2 pkg create py02_service --build-type ament_python --dependencies rclpy base_interfaces_demo --node-name demo01_server_py
2.定义服务通信接口消息
a.创建并编辑 .srv 文件
base_interfaces_demo功能包下新建srv文件夹新建AddInts.srv文件(首字母大写)输入以下代码
int32 num1
int32 num2
---
int32 sum
b.编辑配置文件
与话题通信配置.msg文件步骤一致(package.xml只需要配置一次)
此时CmakeList.txt变为
# 为接口文件生成源码
rosidl_generate_interfaces( ${PROJECT_NAME}
"msg/Student.msg"
"srv/AddInts.srv" #注意没有逗号
)
c.编译
d.测试
通过以下指令测试是否成功编译
ros2 interface show base_interfaces_demo/srv/AddInts
代码如下(示例):
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
3 代码(C++)
demo01_server.cpp 服务端
/*
需求:编写服务端实现,解析提交的请求数据,将解析的数据相加并响应到客户端
流程:
1.包含头文件
2.初始化ROS2客户端
3.自定义节点类
3.1.创建服务端
3.2.回调函数解析请求并发送响应
4.调用spin函数,传入自定义类对象指针
5.释放资源
*/
//1.包含头文件
#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/srv/add_ints.hpp"
using base_interfaces_demo::srv::AddInts;
using std::placeholders::_1;
using std::placeholders::_2;
//3.自定义节点类
class AddIntServer: public rclcpp::Node{
public:
AddIntServer():Node("add_int_server_node_cpp"){
RCLCPP_INFO(this->get_logger(),"服务端创建!");
// 3.1.创建服务端
/*
模板:服务接口类型
参数:
1.服务话题
2.回调函数
返回值:服务对象指针
*/
server_ = this->create_service<AddInts>("add_ints",std::bind(&AddIntServer::add,this,_1,_2));
}
// 3.2.回调函数解析请求并发送响应
void add(const AddInts::Request::SharedPtr req, const AddInts::Response::SharedPtr res){
res->sum = req->num1 + req->num2; //根据服务通信的机制响应将自动进行
RCLCPP_INFO(this->get_logger(),"收到数据:(%d,%d),进行响应:%d",req->num1,req->num2,res->sum);
}
private:
rclcpp::Service<AddInts>::SharedPtr server_;
};
int main(int argc, char ** argv)
{
//2.初始化ROS2客户端
rclcpp::init(argc,argv);
//4.调用spin函数,传入自定义类对象指针
rclcpp::spin(std::make_shared<AddIntServer>());
//5.释放资源
rclcpp::shutdown();
return 0;
}
demo02_client.cpp 客户端实现
/*
需求:创建客户端,组织数据并提交,然后处理响应结果(需要关注业务流程)
流程:
前提:main函数中需要判断提交的参数是否正确
1.包含头文件
2.初始化ROS2客户端
3.自定义节点类
3.1.创建客户端
3.2.连接服务器(对于服务通信而言,如果客户端链接不到服务器,那么不能发送请求)
3.3.发送请求
4.创建对象指针(无挂起操作)
需要调用连接服务的函数,根据连接结果做下一步处理
连接服务后,调用请求发送函数
处理响应结果
5.释放资源
*/
//1.包含头文件
#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/srv/add_ints.hpp"
using base_interfaces_demo::srv::AddInts;
using namespace std::chrono_literals;
//3.自定义节点类
class AddIntClient: public rclcpp::Node{
public:
AddIntClient():Node("add_int_client_node_cpp"){
RCLCPP_INFO(this->get_logger(),"客户端创建!");
// 3.1.创建客户端
/*
模板:服务接口
参数:服务话题名称
返回值:服务对象指针
*/
client_ = this->create_client<AddInts>("add_ints");
}
// 3.2.连接服务器(对于服务通信而言,如果客户端链接不到服务器,那么不能发送请求)
/*
连接服务器实现,如果连接成功返回true,否则返回false
*/
bool connect_server(){
//在指定超时时间内连接服务器,如果连接上了,那么返回true,否则返回false
// client_->wait_for_service(1s);
while(!client_->wait_for_service(1s))
{
//对ctrl+c进行特殊处理,如果不处理对于想要提前终止客户端的操作会异常
if(!rclcpp::ok())
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"强行终止客户端!");
return false;
}
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"服务连接中!");
}
return true;
}
// 3.3.发送请求
//编写发送请求函数。---参数是两个整型数据,返回值是提交请求后服务端的返回结果
rclcpp::Client<AddInts>::FutureAndRequestId send_request(int num1, int num2)
{
//组织请求数据
//发送
/*
rclcpp::Client<base_interfaces_demo::srv::AddInts>::FutureAndRequestId
async_send_request(std::shared_ptr<base_interfaces_demo::srv::AddInts_Request> request)
*/
auto request = std::make_shared<AddInts::Request>();
request->num1 = num1;
request->num2 = num2;
return client_->async_send_request(request);
}
private:
rclcpp::Client<AddInts>::SharedPtr client_;
};
int main(int argc, char ** argv)
{
if(argc != 3) //argc=3表示有两个额外的参数被传入程序
{
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"),"请提交两个整型数字!");
return 1;
}
//2.初始化ROS2客户端
rclcpp::init(argc,argv);
//4.创建对象指针
auto client = std::make_shared<AddIntClient>();
bool flag = client->connect_server();
if (!flag)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"服务器连接失败,程序退出!");
return 0;
}
//调用请求提交函数,接受并处理响应结果
auto future = client->send_request(atoi(argv[1]),atoi(argv[2]));
//处理响应
if (rclcpp::spin_until_future_complete(client,future) == rclcpp::FutureReturnCode::SUCCESS)//成功
{
RCLCPP_INFO(client->get_logger(),"响应成功!sum = %d",future.get()->sum);
}
else //失败
{
RCLCPP_INFO(client->get_logger(),"响应失败!");
}
//5.释放资源
rclcpp::shutdown();
return 0;
}
运行效果
4 代码(python)
demo01_server_py.py 服务端代码
"""
需求:创建服务端,解析客户端提交的数据并相应结果
流程:
1.导包
2.初始化ROS2客户端
3.自定义节点类
3.1.创建服务端
3.2.编写回调函数处理请求并产生响应
4.调用spin函数,传入自定义类对象
5.释放资源
"""
# 1.导包
import rclpy
from rclpy.node import Node
from base_interfaces_demo.srv import AddInts
# 3.自定义节点类
class AddIntServer(Node):
def __init__(self):
super().__init__("add_ints_server_node_py")
self.get_logger().info("服务端创建了!(python)")
# 3.1.创建服务端
self.server = self.create_service(AddInts,"add_ints",self.add)
# 3.2.编写回调函数处理请求并产生响应
def add(self,request,response):
response.sum = request.num1 + request.num2
self.get_logger().info("%d + %d = %d" % (request.num1,request.num2,response.sum))
return response
def main():
# 2.初始化ROS2客户端
rclpy.init()
# 4.调用spin函数,传入自定义类对象
rclpy.spin(AddIntServer())
# 5.释放资源
rclpy.shutdown()
if __name__ == '__main__':
main()
demo02_client_py.py 客户端实现
"""
需求:编写客户端实现,提交两个整型数据,并处理响应结果
流程:
1.导包
2.初始化ROS2客户端
3.自定义节点类
3.1.创建客户端
3.2.连接服务器(对于服务通信而言,如果客户端连接不到服务器,那么不能发送请求)
3.3.发送请求
4.调用spin函数,传入自定义类对象
5.释放资源
"""
# 1.导包
import rclpy
from rclpy.node import Node
from rclpy.logging import get_logger
from base_interfaces_demo.srv import AddInts
import sys
# 3.自定义节点类
class AddIntClient(Node):
def __init__(self):
super().__init__("add_ints_client_node_py")
self.get_logger().info("客户端创建了!(python)")
# 3.1.创建客户端
self.client = self.create_client(AddInts,"add_ints")
# 3.2.连接服务器(对于服务通信而言,如果客户端连接不到服务器,那么不能发送请求)
while not self.client.wait_for_service(1.0):
self.get_logger().info("正在连接服务端!")
# 3.3.发送请求
def send_request(self):
request = AddInts.Request()
request.num1 = int(sys.argv[1])
request.num2 = int(sys.argv[2])
self.future = self.client.call_async(request)
self.get_logger().info("成功发送请求!")
def main():
if len(sys.argv) != 3:
get_logger("rclpy").error("请提交两个整数!")
return
# 2.初始化ROS2客户端
rclpy.init()
# 3.创建节点
client = AddIntClient()
# 发送请求
client.send_request()
# 处理响应
rclpy.spin_until_future_complete(client,client.future)
try:
response = client.future.result()
client.get_logger().info("响应结果:sum = %d" %response.sum)
except:
client.get_logger().error("服务响应失败!")
# 5.释放资源
rclpy.shutdown()
if __name__ == '__main__':
main()
运行效果