服务通信也是ROS中一种极其常用的通信模式,服务通信是基于请求响应模式的,是一种应答机制。
也即: 一个节点A向另一个节点B发送请求,B接收处理请求并产生响应结果返回给A。
自定义srv
服务通信中,数据分成两部分,请求与响应,在 srv 文件中请求和响应使用—分割,具体实现如下:
功能包下新建 srv 目录,添加 xxx.srv 文件,内容:
1 2 3 4 5 6 7
| # 客户端请求时发送的两个数字 int32 num1 int32 num2 --- # 服务器响应发送的数据 int32 sum
|
编辑配置文件
package.xml中添加编译依赖与执行依赖
1 2 3
| <build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
|
CMakeLists.txt编辑 srv 相关配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation ) # 需要加入 message_generation,必须有 std_msgs
add_service_files( FILES AddInts.srv )
generate_messages( DEPENDENCIES std_msgs )
|
cpp实现
vscode配置
1 2 3 4 5
| "includePath": [ "/opt/ros/noetic/include/**", "/usr/include/**", "/xxx/yyy工作空间/devel/include/**" //配置 head 文件的路径 ],
|
服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| /* 需求: 编写两个节点实现服务通信,客户端节点需要提交两个整数到服务器 服务器需要解析客户端提交的数据,相加后,将结果响应回客户端, 客户端再解析
服务器实现: 1.包含头文件 2.初始化 ROS 节点 3.创建 ROS 句柄 4.创建 服务 对象 5.回调函数处理请求并产生响应 6.由于请求有多个,需要调用 ros::spin()
*/ #include "ros/ros.h" #include "demo03_server_client/AddInts.h"
// bool 返回值由于标志是否处理成功 bool doReq(demo03_server_client::AddInts::Request& req, demo03_server_client::AddInts::Response& resp){ int num1 = req.num1; int num2 = req.num2;
ROS_INFO("服务器接收到的请求数据为:num1 = %d, num2 = %d",num1, num2);
//逻辑处理 if (num1 < 0 || num2 < 0) { ROS_ERROR("提交的数据异常:数据不可以为负数"); return false; }
//如果没有异常,那么相加并将结果赋值给 resp resp.sum = num1 + num2; return true;
}
int main(int argc, char *argv[]) { setlocale(LC_ALL,""); // 2.初始化 ROS 节点 ros::init(argc,argv,"AddInts_Server"); // 3.创建 ROS 句柄 ros::NodeHandle nh; // 4.创建 服务 对象 ros::ServiceServer server = nh.advertiseService("AddInts",doReq); ROS_INFO("服务已经启动...."); // 5.回调函数处理请求并产生响应 // 6.由于请求有多个,需要调用 ros::spin() ros::spin(); return 0; }
|
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| /* 需求: 编写两个节点实现服务通信,客户端节点需要提交两个整数到服务器 服务器需要解析客户端提交的数据,相加后,将结果响应回客户端, 客户端再解析
服务器实现: 1.包含头文件 2.初始化 ROS 节点 3.创建 ROS 句柄 4.创建 客户端 对象 5.请求服务,接收响应
*/ // 1.包含头文件 #include "ros/ros.h" #include "demo03_server_client/AddInts.h"
int main(int argc, char *argv[]) { setlocale(LC_ALL,"");
// 调用时动态传值,如果通过 launch 的 args 传参,需要传递的参数个数 +3 if (argc != 3) // if (argc != 5)//launch 传参(0-文件路径 1传入的参数 2传入的参数 3节点名称 4日志路径) { ROS_ERROR("请提交两个整数"); return 1; }
// 2.初始化 ROS 节点 ros::init(argc,argv,"AddInts_Client"); // 3.创建 ROS 句柄 ros::NodeHandle nh; // 4.创建 客户端 对象 ros::ServiceClient client = nh.serviceClient<demo03_server_client::AddInts>("AddInts"); //等待服务启动成功 //方式1 ros::service::waitForService("AddInts"); //方式2 // client.waitForExistence(); // 5.组织请求数据 demo03_server_client::AddInts ai; ai.request.num1 = atoi(argv[1]); //转成整型 ai.request.num2 = atoi(argv[2]); // 6.发送请求,返回 bool 值,标记是否成功 bool flag = client.call(ai); // 7.处理响应 if (flag) { ROS_INFO("请求正常处理,响应结果:%d",ai.response.sum); } else { ROS_ERROR("请求处理失败...."); return 1; }
return 0; }
|
配置 CMakeLists.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| add_executable(AddInts_Server src/AddInts_Server.cpp) add_executable(AddInts_Client src/AddInts_Client.cpp)
add_dependencies(AddInts_Server ${PROJECT_NAME}_gencpp) add_dependencies(AddInts_Client ${PROJECT_NAME}_gencpp)
target_link_libraries(AddInts_Server ${catkin_LIBRARIES} ) target_link_libraries(AddInts_Client ${catkin_LIBRARIES} )
|
需要先启动服务:rosrun 包名 服务
然后再调用客户端 :rosrun 包名 客户端 参数1 参数2
优化:
在客户端发送请求前添加:client.waitForExistence();
或:ros::service::waitForService(“AddInts”);
这是一个阻塞式函数,只有服务启动成功后才会继续执行
此处可以使用 launch 文件优化,但是需要注意 args 传参特点
python实现
vscode配置
配置settings.json
1 2 3 4 5 6
| { "python.autoComplete.extraPaths": [ "/opt/ros/noetic/lib/python3/dist-packages", ] }
|
服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #! /usr/bin/env python """ 需求: 编写两个节点实现服务通信,客户端节点需要提交两个整数到服务器 服务器需要解析客户端提交的数据,相加后,将结果响应回客户端, 客户端再解析
服务器端实现: 1.导包 2.初始化 ROS 节点 3.创建服务对象 4.回调函数处理请求并产生响应 5.spin 函数
""" # 1.导包 import rospy from demo03_server_client.srv import AddInts,AddIntsRequest,AddIntsResponse # 回调函数的参数是请求对象,返回值是响应对象 def doReq(req): # 解析提交的数据 sum = req.num1 + req.num2 rospy.loginfo("提交的数据:num1 = %d, num2 = %d, sum = %d",req.num1, req.num2, sum)
# 创建响应对象,赋值并返回 # resp = AddIntsResponse() # resp.sum = sum resp = AddIntsResponse(sum) return resp
if __name__ == "__main__": # 2.初始化 ROS 节点 rospy.init_node("addints_server_p") # 3.创建服务对象 server = rospy.Service("AddInts",AddInts,doReq) # 4.回调函数处理请求并产生响应 # 5.spin 函数 rospy.spin()
|
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| #! /usr/bin/env python
""" 需求: 编写两个节点实现服务通信,客户端节点需要提交两个整数到服务器 服务器需要解析客户端提交的数据,相加后,将结果响应回客户端, 客户端再解析
客户端实现: 1.导包 2.初始化 ROS 节点 3.创建请求对象 4.发送请求 5.接收并处理响应
优化: 加入数据的动态获取
""" #1.导包 import rospy from demo03_server_client.srv import * import sys
if __name__ == "__main__":
#优化实现 if len(sys.argv) != 3: rospy.logerr("请正确提交参数") sys.exit(1)
# 2.初始化 ROS 节点 rospy.init_node("AddInts_Client_p") # 3.创建请求对象 client = rospy.ServiceProxy("AddInts",AddInts) # 请求前,等待服务已经就绪 # 方式1: # rospy.wait_for_service("AddInts") # 方式2 client.wait_for_service() # 4.发送请求,接收并处理响应 # 方式1 # resp = client(3,4) # 方式2 # resp = client(AddIntsRequest(1,5)) # 方式3 req = AddIntsRequest() # req.num1 = 100 # req.num2 = 200
#优化 req.num1 = int(sys.argv[1]) req.num2 = int(sys.argv[2])
resp = client.call(req) rospy.loginfo("响应结果:%d",resp.sum)
|
设置权限
终端下进入 scripts 执行:chmod +x *.py
配置 CMakeLists.txt
1 2 3 4 5 6
| catkin_install_python(PROGRAMS scripts/AddInts_Server_p.py scripts/AddInts_Client_p.py DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} )
|
需要先启动服务:rosrun 包名 服务
然后再调用客户端 :rosrun 包名 客户端 参数1 参数2