由于任务需要,安排测试单机可以模拟多少个client来并发访问一个service。
我的思路:在本机启动一个ros service,然后编写一个client来访问这个service,然后测试client能否成功访问。然后再编写launch文件,来批量启动client。
service的功能:名字“add_two_ints_server”,该节点将接收到两个整形数字,并返回它们的和。
client的功能:输入两个int,发送给service,然后接收返回值。
具体过程:
0. 创建package
创建一个package,用于整个任务测试,package名字为beginner_tutorials
cd ~/catkin_ws/src
catkin_create_pkg beginner_tutorials std_msgs rospy roscpp
1. 根据roswiki tutorial的Creating_a_srv编写一个service
1.1 创建一个 msg----Num.msg
在beginner_tutorials包里创建新的消息:
$ cd ~/catkin_ws/src/beginner_tutorials $ mkdir msg $ echo "int64 num" > msg/Num.msg
上面是最简单的例子——在.msg文件中只有一行数据。当然,你可以仿造上面的形式多增加几行以得到更为复杂的消息:
string first_name string last_name uint8 age uint32 score
接下来,还有关键的一步:我们要确保msg文件被转换成为C++,Python和其他语言的源代码:
查看package.xml, 确保它包含一下两条语句:
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
如果没有,添加进去。 注意,在构建的时候,我们只需要"message_generation"。然而,在运行的时候,我们只需要"message_runtime"。
打开CMakeLists.txt文件,利用find_packag函数,增加对message_generation的依赖,这样就可以生成消息了。 (你可以直接在COMPONENTS的列表里增加message_generation(也就是说在创建beginner_tutorials包的时候,添加这个依赖项)),就像这样:
find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation)
同样,你需要确保你设置了运行依赖:
catkin_package( ... CATKIN_DEPENDS message_runtime ... ...)
找到如下代码块:
# add_message_files( # FILES # Message1.msg # Message2.msg # )
去掉注释符号#,用你的.msg文件替代Message*.msg,就像下边这样:
add_message_files( FILES Num.msg )
手动添加.msg文件后,我们要确保CMake知道在什么时候重新配置我们的project。 确保添加了如下代码:
generate_messages()
现在,你可以生成自己的消息源代码了。
附:-----------------------------------------------------------------------------------------------------
以上就是你创建消息的所有步骤。下面通过rosmsg show命令,检查ROS是否能够识消息。
使用方法: $ rosmsg show [message type]
样例:
$ rosmsg show beginner_tutorials/Num
你将会看到:
int64 num
在上边的样例中,消息类型包含两部分:
beginner_tutorials -- 消息所在的package
Num -- 消息名Num.
如果你忘记了消息所在的package,你也可以省略掉package名。输入:
$ rosmsg show Num
你将会看到:
[beginner_tutorials/Num]: int64 num
---------------------------------------------------------------------------------------------------------------
1.2 创建一个srv----AddTwoInts.srv
在刚刚那个beginner_tutorials包中创建一个服务:
$ roscd beginner_tutorials
$ mkdir srv
这次我们不再手动创建服务,而是从其他的package中复制一个服务。 roscp是一个很实用的命令行工具,它实现了将文件从一个package复制到另外一个package的功能。
使用方法: $ roscp [package_name] [file_to_copy_path] [copy_path]
现在我们可以从rospy_tutorials package中复制一个服务文件了:
$ roscp rospy_tutorials AddTwoInts.srv srv/AddTwoInts.srv
这里,因为不是我们自己写的,所以很好奇这个.srv文件到底是啥,我们打开它,发现很简单,内容只有4行,如下:
int64 a int64 b --- int64 sum还有很关键的一步:我们要确保srv文件被转换成C++,Python和其他语言的源代码。 就是前边所介绍的,在 CMakeLists.txt文件中增加了对 message_generation的依赖。
find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation)
同样,跟msg文件类似,你也需要在CMakeLists.txt文件中做一些修改。查看上边的说明,增加额外的依赖项。
删掉#,去除对下边语句的注释:
# add_service_files( # FILES # Service1.srv # Service2.srv # )
用你自己的srv文件名替换掉那些Service*.srv文件:
add_service_files( FILES AddTwoInts.srv )现在,你可以生成自己的服务源代码了。
附:---------------------------------------------------------------------------------------------------------------
以上就是创建一个服务所需的所有步骤。下面通过rosmsg show命令,检查ROS是否能够识该服务。
使用方法: $ rossrv show <service type>
例子:
$ rossrv show beginner_tutorials/AddTwoInts
你将会看到(其实就是.srv的内容):
int64 a int64 b --- int64 sum
跟rosmsg类似, 你也可以不指定具体的package名来查找服务文件:
$ rossrv show AddTwoInts [beginner_tutorials/AddTwoInts]: int64 a int64 b --- int64 sum [rospy_tutorials/AddTwoInts]: int64 a int64 b --- int64 sum
1.3 重新编译beginner_tutorials包
接下来,在CMakeLists.txt中找到如下部分:
# generate_messages( # DEPENDENCIES # # std_msgs # Or other packages containing msgs # )
去掉注释并附加上所有你消息文件所依赖的那些含有.msg文件的package(这个例子是依赖std_msgs,不要添加roscpp,rospy),结果如下:
generate_messages( DEPENDENCIES std_msgs )
由于增加了新的消息,所以我们需要重新编译我们的package:
# In your catkin workspace $ cd ../.. $ catkin_make $ cd
所有在msg路径下的.msg文件都将转换为ROS所支持语言的源代码。生成的C++头文件将会放置在~/catkin_ws/devel/include/beginner_tutorials/。 Python脚本语言会在 ~/catkin_ws/devel/lib/python2.7/dist-packages/beginner_tutorials/msg 目录下创建。 lisp文件会出现在 ~/catkin_ws/devel/share/common-lisp/ros/beginner_tutorials/msg/ 路径下.
我们同样很好奇生成.h文件的内容是啥,我们打开它,内容如下(看不懂):
// Generated by gencpp from file beginner_tutorials/AddTwoInts.msg // DO NOT EDIT! #ifndef BEGINNER_TUTORIALS_MESSAGE_ADDTWOINTS_H #define BEGINNER_TUTORIALS_MESSAGE_ADDTWOINTS_H #include <ros/service_traits.h> #include <beginner_tutorials/AddTwoIntsRequest.h> #include <beginner_tutorials/AddTwoIntsResponse.h> namespace beginner_tutorials { struct AddTwoInts { typedef AddTwoIntsRequest Request; typedef AddTwoIntsResponse Response; Request request; Response response; typedef Request RequestType; typedef Response ResponseType; }; // struct AddTwoInts } // namespace beginner_tutorials namespace ros { namespace service_traits { template<> struct MD5Sum< ::beginner_tutorials::AddTwoInts > { static const char* value() { return "6a2e34150c00229791cc89ff309fff21"; } static const char* value(const ::beginner_tutorials::AddTwoInts&) { return value(); } }; template<> struct DataType< ::beginner_tutorials::AddTwoInts > { static const char* value() { return "beginner_tutorials/AddTwoInts"; } static const char* value(const ::beginner_tutorials::AddTwoInts&) { return value(); } }; // service_traits::MD5Sum< ::beginner_tutorials::AddTwoIntsRequest> should match // service_traits::MD5Sum< ::beginner_tutorials::AddTwoInts > template<> struct MD5Sum< ::beginner_tutorials::AddTwoIntsRequest> { static const char* value() { return MD5Sum< ::beginner_tutorials::AddTwoInts >::value(); } static const char* value(const ::beginner_tutorials::AddTwoIntsRequest&) { return value(); } }; // service_traits::DataType< ::beginner_tutorials::AddTwoIntsRequest> should match // service_traits::DataType< ::beginner_tutorials::AddTwoInts > template<> struct DataType< ::beginner_tutorials::AddTwoIntsRequest> { static const char* value() { return DataType< ::beginner_tutorials::AddTwoInts >::value(); } static const char* value(const ::beginner_tutorials::AddTwoIntsRequest&) { return value(); } }; // service_traits::MD5Sum< ::beginner_tutorials::AddTwoIntsResponse> should match // service_traits::MD5Sum< ::beginner_tutorials::AddTwoInts > template<> struct MD5Sum< ::beginner_tutorials::AddTwoIntsResponse> { static const char* value() { return MD5Sum< ::beginner_tutorials::AddTwoInts >::value(); } static const char* value(const ::beginner_tutorials::AddTwoIntsResponse&) { return value(); } }; // service_traits::DataType< ::beginner_tutorials::AddTwoIntsResponse> should match // service_traits::DataType< ::beginner_tutorials::AddTwoInts > template<> struct DataType< ::beginner_tutorials::AddTwoIntsResponse> { static const char* value() { return DataType< ::beginner_tutorials::AddTwoInts >::value(); } static const char* value(const ::beginner_tutorials::AddTwoIntsResponse&) { return value(); } }; } // namespace service_traits } // namespace ros #endif // BEGINNER_TUTORIALS_MESSAGE_ADDTWOINTS_H
自动生成.h文件总共有4个,
---------------------------------------------------------------------------------------------------------------
2. 编写Service节点
在beginner_tutorials包中创建src/add_two_ints_server.cpp文件,并复制粘贴下面的代码:
#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"//AddTwoInts.h是重新编译beginner_tutorials包自动生成的
bool add(beginner_tutorials::AddTwoInts::Request &req,
beginner_tutorials::AddTwoInts::Response &res)
{
res.sum = req.a + req.b;
ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
ROS_INFO("sending back response: [%ld]", (long int)res.sum);
return true;
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "add_two_ints_server");
ros::NodeHandle n;
ros::ServiceServer service = n.advertiseService("add_two_ints", add);//广告服务名add_two_ints
ROS_INFO("Ready to add two ints.");
ros::spin();
return 0;
}
代码解释
现在,让我们来逐步分析代码。
#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"
beginner_tutorials/AddTwoInts.h是由编译系统自动根据我们先前创建的srv文件生成的对应该srv文件的头文件。
bool add(beginner_tutorials::AddTwoInts::Request &req,
beginner_tutorials::AddTwoInts::Response &res)
这个函数提供两个int值求和的服务,int值从request里面获取,而返回数据装入response内,这些数据类型都定义在srv文件内部,函数返回一个boolean值。
{
res.sum = req.a + req.b;
ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
ROS_INFO("sending back response: [%ld]", (long int)res.sum);
return true;
}
现在,两个int值已经相加,并存入了response。然后一些关于request和response的信息被记录下来。最后,service完成计算后返回true值。
ros::ServiceServer service = n.advertiseService("add_two_ints", add);
这里,service已经建立起来,并在ROS内发布出来。
3. 编写client节点
在beginner_tutorials包中创建src/add_two_ints_client.cpp文件,并复制粘贴下面的代码:
#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"
#include <cstdlib>
int main(int argc, char **argv)
{
ros::init(argc, argv, "add_two_ints_client");
if (argc != 3)
{
ROS_INFO("usage: add_two_ints_client X Y");
return 1;
}
ros::NodeHandle n;
ros::ServiceClient client = n.serviceClient<beginner_tutorials::AddTwoInts>("add_two_ints");//接收add_two_ints服务
beginner_tutorials::AddTwoInts srv;
srv.request.a = atoll(argv[1]);
srv.request.b = atoll(argv[2]);
clock_t start,finish;//我自己添加的,用于计算反馈时间
start=clock();
if (client.call(srv)) {
finish=clock();
ROS_INFO("Sum: %ld, consum: %ld ms", (long int)srv.response.sum,(long int)1000*(finish-start)/CLOCKS_PER_SEC);
}
else
{
ROS_ERROR("Failed to call service add_two_ints");
return 1;
}
return 0;
}
代码解释
现在,让我们来逐步分析代码。
ros::ServiceClient client = n.serviceClient<beginner_tutorials::AddTwoInts>("add_two_ints");
这段代码为add_two_ints service创建一个client。ros::ServiceClient 对象待会用来调用service。
beginner_tutorials::AddTwoInts srv;
srv.request.a = atoll(argv[1]);
srv.request.b = atoll(argv[2]);
这里,我们实例化一个由ROS编译系统自动生成的service类,并给其request成员赋值。一个service类包含两个成员request和response。同时也包括两个类定义Request和Response。
if (client.call(srv))
这段代码是在调用service。由于service的调用是模态过程(调用的时候占用进程阻止其他代码的执行),一旦调用完成,将返回调用结果。如果service调用成功,call()函数将返回true,srv.response里面的值将是合法的值。如果调用失败,call()函数将返回false,srv.response里面的值将是非法的。
4. 重新编译package
再来编辑一下beginner_tutorials里面的CMakeLists.txt,文件位于~/catkin_ws/src/beginner_tutorials/CMakeLists.txt,并将下面的代码添加在文件末尾:
add_executable(add_two_ints_server src/add_two_ints_server.cpp)
target_link_libraries(add_two_ints_server ${catkin_LIBRARIES})
add_dependencies(add_two_ints_server beginner_tutorials_gencpp)
add_executable(add_two_ints_client src/add_two_ints_client.cpp)
target_link_libraries(add_two_ints_client ${catkin_LIBRARIES})
add_dependencies(add_two_ints_client beginner_tutorials_gencpp)
这段代码将生成两个可执行程序”add_two_ints_server”和”add_two_ints_client”,这两个可执行程序默认被放在你的devel space下的包目录下,默认为~/catkin_ws/devel/lib/share/。你可以直接调用可执行程序,或者使用rosrun命令去调用它们。
现在运行catkin_make命令:
#In your catkin workspace
cd ~/catkin_ws
catkin_make
如果你的编译过程因为某些原因而失败:
确保你已经依照先前的creating the AddTwoInts.srv教程里的步骤完成操作。
5. 运行service和client
让我们从运行Service开始 之前,确保在catkin工作空间下(~/catkin_ws$):
$ source ./devel/setup.bash
$ rosrun beginner_tutorials add_two_ints_server (C++)
$ rosrun beginner_tutorials add_two_ints_server.py (Python)
你将会看到如下类似的信息:
Ready to add two ints.
(在新终端)运行Client
现在,运行Client并附带一些参数:
$ source ./devel/setup.bash
$ rosrun beginner_tutorials add_two_ints_client 1 3 (C++)
$ rosrun beginner_tutorials add_two_ints_client.py 1 3 (Python)
你将会看到如下类似的信息:
request: x=1, y=3
sending back response: [4]
现在,你已经成功地运行了你的第一个Service和Client程序
6. 编写launch 文件来批量启动client
roslaunch可以用来启动定义在launch文件中的多个节点。用法: $ roslaunch [package] [filename.launch]
先切换到beginner_tutorials程序包目录下:
$ roscd beginner_tutorials
然后创建一个launch文件夹:
$ mkdir launch $ cd launch
现在我们来创建一个名为test1.launch的空launch文件,并复制粘贴以下内容到该文件里面:
1 <launch>
2
3 </launch>
最终在如下位置找到一个可用的:http://blog.chinaunix.net/uid-29742351-id-4961536.html
它给出的例子是同时启动service和client,如下:
<node name="add_two_ints_server" pkg="beginner_tutorials" type="add_two_ints_server" /> <node name="add_two_ints_client" pkg="beginner_tutorials" type="add_two_ints_client" args="$(arg a) $(arg b)" />
对于我们来说,我们也可以直接用这个文件,也可以在launch中只启动多个client,而在terminal中启动service。我们修改后的launch文件如下:
<launch> <node name="client1" pkg="beginner_tutorials" type="add_two_ints_client" args="$(arg a) $(arg b)" /> <node name="client2" pkg="beginner_tutorials" type="add_two_ints_client" args="$(arg a) $(arg b)" /> <node name="client3" pkg="beginner_tutorials" type="add_two_ints_client" args="$(arg a) $(arg b)" /> <node name="client4" pkg="beginner_tutorials" type="add_two_ints_client" args="$(arg a) $(arg b)" /> <node name="client5" pkg="beginner_tutorials" type="add_two_ints_client" args="$(arg a) $(arg b)" /> <node name="client6" pkg="beginner_tutorials" type="add_two_ints_client" args="$(arg a) $(arg b)" /> <node name="client7" pkg="beginner_tutorials" type="add_two_ints_client" args="$(arg a) $(arg b)" /> <node name="client8" pkg="beginner_tutorials" type="add_two_ints_client" args="$(arg a) $(arg b)" /> <node name="client9" pkg="beginner_tutorials" type="add_two_ints_client" args="$(arg a) $(arg b)" /> <node name="client10" pkg="beginner_tutorials" type="add_two_ints_client" args="$(arg a) $(arg b)" /> </launch>
7. 通过launch文件启动多个client
这里我们在launch文件中只编写了多个client,而不包括service,所以我们还要在terminal中先启动service。
~/catkin_ws$ rosrun beginner_tutorials add_two_ints_server
然后在另一个terminal上运行launch文件:
~/catkin_ws$ roslaunch beginner_tutorials test1.launch a:=123 b:=91
然后根据client的输出内容,可以到响应的目录查看结果和日志。
附 :package.xml和CMakelist.txt
1. package.xml
<?xml version="1.0"?> <package format="2"> <name>beginner_tutorials</name> <version>0.0.0</version> <description>The beginner_tutorials package</description> <maintainer email="[email protected]">oldking</maintainer> <buildtool_depend>catkin</buildtool_depend> <build_depend>roscpp</build_depend> <build_depend>rospy</build_depend> <build_depend>std_msgs</build_depend> <build_depend>message_generation</build_depend> <build_export_depend>roscpp</build_export_depend> <build_export_depend>rospy</build_export_depend> <build_export_depend>std_msgs</build_export_depend> <exec_depend>roscpp</exec_depend> <exec_depend>rospy</exec_depend> <exec_depend>std_msgs</exec_depend> <exec_depend>message_runtime</exec_depend> <!-- The export tag contains other, unspecified, tags --> <export> <!-- Other tools can request additional information be placed here --> </export> </package>
2. CMakeList.txt
cmake_minimum_required(VERSION 2.8.3) project(beginner_tutorials) find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation ) ## Generate messages in the 'msg' folder add_message_files( FILES Num.msg ) ## Generate services in the 'srv' folder add_service_files( FILES AddTwoInts.srv ) ## Generate added messages and services with any dependencies listed here generate_messages( DEPENDENCIES std_msgs ) catkin_package( # INCLUDE_DIRS include # LIBRARIES beginner_tutorials CATKIN_DEPENDS roscpp rospy std_msgs message_runtime # DEPENDS system_lib ) include_directories( # include ${catkin_INCLUDE_DIRS} ) add_executable(add_two_ints_server src/add_two_ints_server.cpp) target_link_libraries(add_two_ints_server ${catkin_LIBRARIES}) add_dependencies(add_two_ints_server beginner_tutorials_gencpp) add_executable(add_two_ints_client src/add_two_ints_client.cpp) target_link_libraries(add_two_ints_client ${catkin_LIBRARIES}) add_dependencies(add_two_ints_client beginner_tutorials_gencpp)