之前博客《RO发布者S复习笔记之——系统框架及其编程规范》的(发布者节点和订阅者节点的创建和运行)一节中以及介绍如何创建发布者、订阅者、话题消息的通信。本博文继这个博客,来复习一下创建和运行服务服务器与客户端节点。(一些设置在之前博客中介绍了就不再重复了,直接讲如何构建服务)
服务由服务服务器(service server)和服务客户端(service client)组成,其中服务服务器仅在收到请求(request)时才会响应(response),而服务客户端则会发送请求并接收响应。与话题不同,服务是一次性消息通信。因此,当服务的请求和响应完成
时,两个节点的连接会被断开。
这种服务通常在让机器人执行特定任务时用到。或者用于需要在特定条件下做出反应的节点。由于它是一次性的通信方式,因此对网络的负载很小,所以是一种非常有用的通信手段,例如被用作一种代替话题的通信手段。
构建一个完整的功能包包括了以下几步
- 创建功能包
- 修改功能包配置文件(package.xml)
- 修改构建配置文件(CMakeLists.txt)
- 创建消息/服务/动作等文件
- 编写节点
- 编译
由于本文是基于之前博文《RO发布者S复习笔记之——系统框架及其编程规范》的开发,所以部分已经重复的就不再详述了。
首先。基于之前创建的功能包my_first_ros_pkg进行修改
由于要添加的是服务。先修改CMakeLists.txt文件。声明服务文件
## Generate services in the 'srv' folder
# add_service_files(
# FILES
# Service1.srv
# Service2.srv
# )
## 服务声明:SrvTutorial.srv
add_service_files(FILES SrvTutorial.srv)
声明了服务文件后,对服务文件进行创建
roscd my_first_ros_pkg
mkdir srv
cd srv
gedit SrvTutorial.srv
结果如下图所示(注意路径)
消息的内容如下所示
int64 a
int64 b
---
int64 result
以int64格式设计服务请求(request)a、b,和结果服务响应(response)result。“---”是分隔符,用于分隔请求和响应。除了请求和响应之间有一个分隔符之外,服务的消息与之前话题的消息相同。
然后回到CMakeLists.txt文件。添加服务服务器的节点。设置可执行文件、目标链接库和附加依赖项。
## 这是service_server节点的构建选项。可执行文件
add_executable(service_server src/service_server.cpp)
## service_server节点的构建选项。附加依赖项。
add_dependencies(service_server ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
## service_server节点的构建选项。目标链接库和
target_link_libraries(service_server
${catkin_LIBRARIES}
)
添加完服务服务器后,我们来定义service_server.cpp文件来创建service_server可执行文
roscd my_first_ros_pkg/src
gedit service_server.cpp
#include "ros/ros.h" // ROS的基本头文件
#include "my_first_ros_pkg/SrvTutorial.h" // SrvTutorial服务头文件(构建后自动生成)
//这里的my_first_ros_pkg是节点所在的功能包
//服务服务器是响应服务的请求,响应来自服务客户端的请求,并将结果反馈到服务客户端
// 如果有服务请求,将执行以下处理
// 将服务请求设置为req,服务响应则设置为res。
//SrvTutorial为定义的服务消息文件
bool calculation(my_first_ros_pkg::SrvTutorial::Request &req,
my_first_ros_pkg::SrvTutorial::Response &res)
{
// 在收到服务请求时,将a和b的和保存在服务响应值中
res.result = req.a + req.b;//服务响应的结果就是把服务请求的两个值相加
// 显示服务请求中用到的a和b的值以及服务响应result值
ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);//显示出来
ROS_INFO("sending back response: %ld", (long int)res.result);
return true;
}
int main(int argc, char **argv) // 节点主函数
{
ros::init(argc, argv, "service_server"); // 初始化节点名称。"service_server“就算当前节点的名称,这句话的作用应该就是在ROS中初始化这个节点
ros::NodeHandle nh; // 声明节点句柄
// 声明服务服务器
// 声明利用my_first_ros_pkg功能包的SrvTutorial服务文件的
// 服务服务器my_first_ros_pkg_server
// 自定义服务名称是ros_tutorial_srv,且当有服务请求时,执行calculation函数。
ros::ServiceServer my_first_ros_pkg_server = nh.advertiseService("ros_tutorial_srv", calculation);//这句可以跟之前的topic发布者文件进行对比,可以看出ros中声明服务服务器和topic发布者的关系
ROS_INFO("ready srv server!");//显示
ros::spin(); // 等待服务请求
return 0;
}
然后回到CMakeLists.txt文件。添加服务客户端的节点。设置可执行文件、目标链接库和附加依赖项。
## 这是service_client节点的构建选项。可执行文件
add_executable(service_client src/service_client.cpp)
##service_client节点的构建选项。附加依赖项。
add_dependencies(service_client ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
## service_client节点的构建选项。目标链接库和
target_link_libraries(service_client
${catkin_LIBRARIES}
)
下面定义服务客户端节点
roscd my_first_ros_pkg/src
gedit service_client.cpp
#include "ros/ros.h" // ROS的基本头文件
#include "my_first_ros_pkg/SrvTutorial.h" // SrvTutorial服务头文件(构建后自动生成)
#include <cstdlib> // 使用atoll函数所需的库
int main(int argc, char **argv) // 节点主函数
{
ros::init(argc, argv, "service_client"); // 初始化节点名称
if (argc != 3) // 处理输入值错误
{
ROS_INFO("cmd : rosrun my_first_ros_pkg service_client arg0 arg1");
ROS_INFO("arg0: double number, arg1: double number");
return 1;
}
ros::NodeHandle nh; // 声明与ROS系统通信的节点句柄
// 声明客户端,声明利用my_first_ros_pkg功能包的SrvTutorial服务文件的
// 服务客户端my_first_ros_pkg_client。
// 定义的服务名称是"ros_tutorial_srv"
ros::ServiceClient my_first_ros_pkg_client = nh.serviceClient<my_first_ros_pkg::SrvTutorial>("ros_tutorial_srv");
// 声明一个使用SrvTutorial服务文件的叫做srv的服务
my_first_ros_pkg::SrvTutorial srv;
// 在执行服务客户端节点时用作输入的参数分别保存在a和b中
srv.request.a = atoll(argv[1]);
srv.request.b = atoll(argv[2]);
// 请求服务,如果请求被接受,则显示响应值
if (my_first_ros_pkg_client.call(srv))
{
ROS_INFO("send srv, srv.Request.a and b: %ld, %ld", (long int)srv.request.a, (long int)srv.request.b);
ROS_INFO("receive srv, srv.Response.result: %ld", (long int)srv.response.result);
}
else
{
ROS_ERROR("Failed to call service ros_tutorial_srv");
return 1;
}
return 0;
}
整个CMakeLists.txt文件如下图所示
cmake_minimum_required(VERSION 2.8.3)
project(my_first_ros_pkg)
#############################*****************************************************
## Compile as C++11, supported in ROS Kinetic and newer
# add_compile_options(-std=c++11)
## Find catkin macros and libraries
## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
## is used, also find other catkin packages
## 这是进行catkin构建时所需的组件包。
## 依赖包是message_generation、std_msgs和roscpp。如果这些包不存在,在构建过程中会发生错误。
find_package(catkin REQUIRED COMPONENTS
message_generation
roscpp
std_msgs
)
#############################*****************************************************
## System dependencies are found with CMake's conventions
# find_package(Boost REQUIRED COMPONENTS system)
## Uncomment this if the package has a setup.py. This macro ensures
## modules and global scripts declared therein get installed
## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html
# catkin_python_setup()
################################################
## Declare ROS messages, services and actions ##
################################################
## To declare and build messages, services or actions from within this
## package, follow these steps:
## * Let MSG_DEP_SET be the set of packages whose message types you use in
## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...).
## * In the file package.xml:
## * add a build_depend tag for "message_generation"
## * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET
## * If MSG_DEP_SET isn't empty the following dependency has been pulled in
## but can be declared for certainty nonetheless:
## * add a exec_depend tag for "message_runtime"
## * In this file (CMakeLists.txt):
## * add "message_generation" and every package in MSG_DEP_SET to
## find_package(catkin REQUIRED COMPONENTS ...)
## * add "message_runtime" and every package in MSG_DEP_SET to
## catkin_package(CATKIN_DEPENDS ...)
## * uncomment the add_*_files sections below as needed
## and list every .msg/.srv/.action file to be processed
## * uncomment the generate_messages entry below
## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...)
#############################*****************************************************
## Generate messages in the 'msg' folder
# add_message_files(
# FILES
# Message1.msg
# Message2.msg
# )
## 消息声明:MsgTutorial.msg
add_message_files(FILES MsgTutorial.msg)
#############################*****************************************************
## Generate services in the 'srv' folder
# add_service_files(
# FILES
# Service1.srv
# Service2.srv
# )
## 服务声明:SrvTutorial.srv
add_service_files(FILES SrvTutorial.srv)
#############################*****************************************************
## Generate actions in the 'action' folder
# add_action_files(
# FILES
# Action1.action
# Action2.action
# )
#############################*****************************************************
## Generate added messages and services with any dependencies listed here
# generate_messages(
# DEPENDENCIES
# std_msgs
# )
## 这是设置依赖性消息的选项。
## 如果未安装std_msgs,则在构建过程中会发生错误。
generate_messages(
DEPENDENCIES
std_msgs
)
#############################*****************************************************
################################################
## Declare ROS dynamic reconfigure parameters ##
################################################
## To declare and build dynamic reconfigure parameters within this
## package, follow these steps:
## * In the file package.xml:
## * add a build_depend and a exec_depend tag for "dynamic_reconfigure"
## * In this file (CMakeLists.txt):
## * add "dynamic_reconfigure" to
## find_package(catkin REQUIRED COMPONENTS ...)
## * uncomment the "generate_dynamic_reconfigure_options" section below
## and list every .cfg file to be processed
## Generate dynamic reconfigure parameters in the 'cfg' folder
# generate_dynamic_reconfigure_options(
# cfg/DynReconf1.cfg
# cfg/DynReconf2.cfg
# )
#############################*****************************************************
###################################
## catkin specific configuration ##
###################################
## The catkin_package macro generates cmake config files for your package
## Declare things to be passed to dependent projects
## INCLUDE_DIRS: uncomment this if your package contains header files
## LIBRARIES: libraries you create in this project that dependent projects also need
## CATKIN_DEPENDS: catkin_packages dependent projects also need
## DEPENDS: system dependencies of this project that dependent projects also need
## catkin功能包选项,描述了库、catkin构建依赖项和系统依赖的功能包。
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES my_first_ros_pkg
# CATKIN_DEPENDS roscpp std_msgs
# DEPENDS system_lib
LIBRARIES my_first_ros_pkg#表示将使用随后而来的功能包的库
CATKIN_DEPENDS std_msgs roscpp
)
#############################*****************************************************
###########
## Build ##
###########
## 设置包含目录。
## Specify additional locations of header files
## Your package locations should be listed before other locations
include_directories(
# include
${catkin_INCLUDE_DIRS}
)
#############################*****************************************************
## Declare a C++ library
# add_library(${PROJECT_NAME}
# src/${PROJECT_NAME}/my_first_ros_pkg.cpp
# )
#############################*****************************************************
## Add cmake target dependencies of the library
## as an example, code may need to be generated before libraries
## either from message generation or dynamic reconfigure
# add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
## Declare a C++ executable
## With catkin_make all packages are built within a single CMake context
## The recommended prefix ensures that target names across packages don't collide
# add_executable(${PROJECT_NAME}_node src/my_first_ros_pkg_node.cpp)
add_executable(hello_world_node src/hello_world_node.cpp)
## topic_publisher节点的构建选项。
add_executable(topic_publisher src/topic_publisher.cpp)
## topic_subscriber节点的构建选项。
add_executable(topic_subscriber src/topic_subscriber.cpp)
## 这是service_server节点的构建选项。可执行文件
add_executable(service_server src/service_server.cpp)
## 这是service_client节点的构建选项。可执行文件
add_executable(service_client src/service_client.cpp)
#############################*****************************************************
## Rename C++ executable without prefix
## The above recommended prefix causes long target names, the following renames the
## target back to the shorter version for ease of user use
## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node"
# set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "")
## Add cmake target dependencies of the executable
## same as for the library above
# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
## topic_publisher节点的构建选项。
add_dependencies(topic_publisher ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
## topic_subscriber节点的构建选项。
add_dependencies(topic_subscriber ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
## service_server节点的构建选项。附加依赖项。
add_dependencies(service_server ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
##service_client节点的构建选项。附加依赖项。
add_dependencies(service_client ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
#############################*****************************************************
## Specify libraries to link a library or executable target against
# target_link_libraries(${PROJECT_NAME}_node
# ${catkin_LIBRARIES}
# )
target_link_libraries(hello_world_node
${catkin_LIBRARIES}
)
## topic_publisher节点的构建选项。
target_link_libraries(topic_publisher
${catkin_LIBRARIES}
)
## topic_subscriber节点的构建选项。
target_link_libraries(topic_subscriber
${catkin_LIBRARIES}
)
## service_server节点的构建选项。目标链接库和
target_link_libraries(service_server
${catkin_LIBRARIES}
)
## service_client节点的构建选项。目标链接库和
target_link_libraries(service_client
${catkin_LIBRARIES}
)
#############################*****************************************************
#############
## Install ##
#############
# all install targets should use catkin DESTINATION variables
# See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html
## Mark executable scripts (Python etc.) for installation
## in contrast to setup.py, you can choose the destination
# install(PROGRAMS
# scripts/my_python_script
# DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
# )
## Mark executables for installation
## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.html
# install(TARGETS ${PROJECT_NAME}_node
# RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
# )
## Mark libraries for installation
## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.html
# install(TARGETS ${PROJECT_NAME}
# ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
# LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
# RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION}
# )
## Mark cpp header files for installation
# install(DIRECTORY include/${PROJECT_NAME}/
# DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
# FILES_MATCHING PATTERN "*.h"
# PATTERN ".svn" EXCLUDE
# )
## Mark other files for installation (e.g. launch and bag files, etc.)
# install(FILES
# # myfile1
# # myfile2
# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
# )
#############
## Testing ##
#############
## Add gtest based cpp test target and link libraries
# catkin_add_gtest(${PROJECT_NAME}-test test/test_my_first_ros_pkg.cpp)
# if(TARGET ${PROJECT_NAME}-test)
# target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME})
# endif()
## Add folders to be run by python nosetests
# catkin_add_nosetests(test)
然后运行下面命令创建
cd ~/catkin_ws && catkin_make
测试
roscore
rosrun my_first_ros_pkg service_server
rosrun my_first_ros_pkg service_client 2 3
可以看出。服务服务器是一直开启的。而服务客户端每次运行完,都会结束。等待下次请求。而在服务服务端也会显示请求和反馈
如果只输入一个数或者多于两个数,就会报错
这是由于
int main(int argc,char * argv[])
argc为命令行中参数个数,包括命令名本身(也就是程序名exe)。所以输入两个数+命令本身,一共就是3个参数。
argv是一个指向char的指针数组,其中的指针指向命令行参数,例如,argv[0]就是指向命令行参数中的第一个字符串,默认为命令名本身(也就是程序名exe)。
下面就是把第二和第三个参数输入了
srv.request.a = atoll(argv[1]);
srv.request.b = atoll(argv[2]);
而如果服务服务器的节点断掉,报错如下
接下来博客《ROS复习笔记之——创建和运行动作服务器与客户端节点》会介绍动作服务器和客户端的创建
参考资料
http://wiki.ros.org/Books/ROS_Robot_Programming_English
https://blog.csdn.net/mars_xiaolei/article/details/89399101(C++中关于main)