ROS复习笔记之——创建和运行服务服务器与客户端节点

之前博客《RO发布者S复习笔记之——系统框架及其编程规范》的(发布者节点和订阅者节点的创建和运行)一节中以及介绍如何创建发布者、订阅者、话题消息的通信。本博文继这个博客,来复习一下创建和运行服务服务器与客户端节点。(一些设置在之前博客中介绍了就不再重复了,直接讲如何构建服务)

服务由服务服务器(service server)和服务客户端(service client)组成,其中服务服务器仅在收到请求(request)时才会响应(response),而服务客户端则会发送请求并接收响应。与话题不同,服务是一次性消息通信。因此,当服务的请求和响应完成
时,两个节点的连接会被断开。

这种服务通常在让机器人执行特定任务时用到。或者用于需要在特定条件下做出反应的节点。由于它是一次性的通信方式,因此对网络的负载很小,所以是一种非常有用的通信手段,例如被用作一种代替话题的通信手段。

构建一个完整的功能包包括了以下几步

  1. 创建功能包
  2. 修改功能包配置文件(package.xml)
  3. 修改构建配置文件(CMakeLists.txt)
  4. 创建消息/服务/动作等文件
  5. 编写节点
  6. 编译

由于本文是基于之前博文《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)

发布了237 篇原创文章 · 获赞 254 · 访问量 29万+

猜你喜欢

转载自blog.csdn.net/gwplovekimi/article/details/104356409