ROS2学习笔记14--编写一个简单的服务器和客户端(C++)
Posted 鸿_H
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ROS2学习笔记14--编写一个简单的服务器和客户端(C++)相关的知识,希望对你有一定的参考价值。
概要:这篇主要介绍c++版本服务端和客户端的编写
环境:ubuntu20.04,ros2-foxy,vscode
最后如果没有陈述实操过程中碰到问题的话,则表示该章节都可被本人正常复现.
2.2.5编写一个简单的服务器和客户端(C++)(原文:https://docs.ros.org/en/foxy/Tutorials/Writing-A-Simple-Cpp-Service-And-Client.html
)
>>
教程>>
编写一个简单的服务器和客户端(C++)
你正阅读的是ros2
较老版本(Foxy
),但仍然支持的说明文档.想查看最新版本的信息,请看galactic版本链接( https://docs.ros.org/en/galactic/Tutorials.html
)
编写一个简单的服务器和客户端(C++)
目标:使用c++构建和运行一个服务器节点和一个客户端节点
课程等级:初级
时长:20min
目录
1.背景
2.预备知识
3.步骤
3.1创建一个包
3.2编写服务器节点
3.3编写客户端节点
3.4编译和运行
4.总结
5.下一步
6.相关内容
1.背景
当节点使用服务来通信时,发送请求信息的节点叫客户端节点,响应请求的节点叫服务器节点.请求-响应结构由.srv
文件决定.
这里使用的案例是一个简单的整数加法系统,一个节点请求两个整数之和,另外节点响应结果.
2.预备知识
在前面课程中,你学习了如何创建工作空间以及包.
3.步骤
3.1创建一个包
新开一个终端并source
一下ros2
环境变量以保证ros2
指令都是没有问题的.
进入前面课程创建的dev_ws
工作空间目录
回忆一下,包是应该创建在src
目录下的,而不是工作空间根目录的.进入dev_ws/src
目录并且创建一个新包:
ros2 pkg create --build-type ament_cmake cpp_srvcli --dependencies rclcpp example_interfaces
你的终端会返回信息,表明你的包cpp_srvcli
以及它所有的必须文件和文件夹都创建完成.
这个--dependencies
参数自动添加到package.xml
和CMakeLists.txt
的必要的依赖(标签)中.
包里面的.srv.file
文件,你需要利用example_interfaces
在里面构建你的请求和响应.
int64 a
int64 b
---
int64 sum
上面两行是请求的参数,虚线下面是响应.
3.1.1更新package.xml文件
由于包创建时使用了--dependencies
,你不必手动添加依赖到package.xml
或者CMakeLists.txt
里面
老规矩,确保package.xml
里面description
, maintainer email
name
和license
信息都填写完成.
<description>C++ client server tutorial</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache License 2.0</license>
3.2编写服务器节点
进入dev_ws/src/cpp_srvcli/src
目录,创建名字为add_two_ints_server.cpp
源文件,复制一下代码到里面:
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"
#include <memory>
void add(const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> 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<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_server");
rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr service =
node->create_service<example_interfaces::srv::AddTwoInts>("add_two_ints", &add);
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add two ints.");
rclcpp::spin(node);
rclcpp::shutdown();
}
3.2.1代码解析
首先两个#include
声明是你的包的依赖.
add
函数接受自请求的两个整数,并且返回求和计算结果,控制台会以日志展示函数状态.
void add(const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> 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
主函数实现以下功能,一行接一行:
*初始后ros2客户端库
rclcpp::init(argc, argv);
*创建名为add_two_ints_server
的节点
std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_server");
*为上面节点创建add_two_ints
服务器,并且使用&add
方式,自动将其放到(系统)网络里面广播.
rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr service =
node->create_service<example_interfaces::srv::AddTwoInts>("add_two_ints", &add);
当其准备好时会打印日志信息:
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add two ints.");
运转节点使得服务可用
rclcpp::spin(node);
3.2.2 添加executable
使用ros2 run
指令,add_executable
宏表示会生成一个可执行文件.添加下面代码块到CMakeLists.txt
文件里面,生成一个叫server
的服务.
add_executable(server src/add_two_ints_server.cpp)
ament_target_dependencies(server
rclcpp example_interfaces)
把下面几行代码放到CMakeLists.txt
文件底部但在ament_package()
上面,ros2 run
就可以找到这个可执行文件:
install(TARGETS
server
DESTINATION lib/${PROJECT_NAME})
现在可以编译你的包了,source
一下本地配置文件,然后运行它.让我们首先创建一个客户端,这样子你就可以看见完整运转的系统了.
3.3编写客户端节点
进入dev_ws/src/cpp_srvcli/src
目录,新建一个名为add_two_ints_client.cpp
文件,把下面代码放到里面:
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"
#include <chrono>
#include <cstdlib>
#include <memory>
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<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_client");
rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client =
node->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");
auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();
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.3.1代码解析
类似服务器节点,下面几行代码创建一个节点,然后创建该节点对应的客户端:
std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_client");
rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client =
node->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");
下一步是请求生成.它的结构样式是在前面提及的.srv
文件里面:
auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();
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.3.2添加executable
回到CMakeLists.txt
,为新节点添加所需的executable
和target
内容. 删除自动生成样板不必要内容之后,你的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(server
rclcpp example_interfaces)
add_executable(client src/add_two_ints_client.cpp)
ament_target_dependencies(client
rclcpp example_interfaces)
install(TARGETS
server
client
DESTINATION lib/${PROJECT_NAME})
ament_package()
3.4编译和运行
编译前,在你的工作空间(dev_ws
)运行rosdep
检查所缺依赖是很nice
的练习.
linux:
rosdep install -i --from-path src --rosdistro foxy -y
定位到dev_ws
工作空间根目录,编译你的工作空间:
colcon build --packages-select cpp_srvcli
新开终端,source
一下配置文件:
linux:
. install/setup.bash
然后运行服务器节点:
ros2 run cpp_srvcli server
然后等待一下,终端应该返回一下信息:
[INFO] [rclcpp]: Ready to add two ints.
新开一个终端,在dev_ws
再次source
一下配置信息(环境变量).然后开启客户端节点,两个整数要用空格分开:
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
,停止节点的持续运行.
4.总结
你创建了两个节点来请求和响应整个服务过程的数据.你添加dependencies
和executables
到包配置文件里面,所以你才可以编译和运行这包,并且看到服务端/用户端系统运转.
5.下一步
下面一些课程,你将会(学习)初始化端口来通过话题和服务传输数据.接下来,你会学习如何创建定制化端口.
6.相关内容
有几种c++
编写方式供你选择用来服务器和客户端的.可以在这里(https://github.com/ros2/examples/tree/foxy/rclcpp/services
)检查包里面minimal_service
和minimal_client
文件.
其他
个人认为重点:
服务器和客户端节点代码句子内容理解;相应配置文件的内容添加.
这课程是在等毕业证那十几天搞的,室友问,现在在线翻译这么强大,为啥还在这里瞎折腾呢?我说,我的目地是好好认真看一下,了解一下,自己折腾,目前是我想到最好的办法来获得最佳效果,即使这翻译有点别扭,哈哈哈.
#####################
不积硅步,无以至千里
好记性不如烂笔头
感觉有点收获的话,麻烦大大们点赞收藏哈
以上是关于ROS2学习笔记14--编写一个简单的服务器和客户端(C++)的主要内容,如果未能解决你的问题,请参考以下文章
ROS2学习笔记13--编写一个简单的发布器和侦听器(C++)