出处:ros学习讲义地址
0服务通讯的理论模型
话题通信是订阅模式(广播式),每隔一段时间(频率)发送一次数据,服务通信(应答式)是请求模式,只有请求了才会发送数据
1服务通信自定义srv
srv代表请求+响应
srv 文件内的可用数据类型与 msg 文件一致,且定义 srv 实现流程与自定义 msg 实现流程类似:
1、按照固定格式创建srv文件
2、编辑配置文件
3、编译生成中间文件
步骤同讲义内容一样讲义链接
生成中间文件(在demo04_ws/devel/include/下)后,一样的将所生产文件的路径添加c_cpp_properties.json中
2、编写服务端实现
/*
需求:
编写两个节点实现服务通信,客户端节点需要提交两个整数到服务器
服务器需要解析客户端提交的数据,相加后,将结果响应回客户端,
客户端再解析
服务器实现:
1.包含头文件
2.初始化 ROS 节点
3.创建 ROS 句柄
4.创建 服务 对象
5.回调函数处理请求并产生响应
6.由于请求有多个,需要调用 ros::spin()
*/
#include "ros/ros.h"
#include "plumbing_server_client/AddInts.h"
// bool 返回值由于标志是否处理成功
bool doReq(plumbing_server_client::AddInts::Request& req,
plumbing_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");//节点名称为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;
}
3、编写客户端实现
/*
需求:
编写两个节点实现服务通信,客户端节点需要提交两个整数到服务器
服务器需要解析客户端提交的数据,相加后,将结果响应回客户端,
客户端再解析
服务器实现:
1.包含头文件
2.初始化 ROS 节点
3.创建 ROS 句柄
4.创建 客户端 对象
5.请求服务,接收响应
*/
// 1.包含头文件
#include "ros/ros.h"
#include "plumbing_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<plumbing_server_client::AddInts>("AddInts");
//等待服务启动成功
//方式1
ros::service::waitForService("AddInts");
//方式2
// client.waitForExistence();
// 5.组织请求数据
plumbing_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;
}
4、配置CMakeLists
add_executable(demo01_server src/demo01_server.cpp)
add_executable(demo02_client src/demo02_client.cpp)
add_dependencies(demo01_server ${PROJECT_NAME}_gencpp)
add_dependencies(demo02_client ${PROJECT_NAME}_gencpp)
target_link_libraries(demo01_server
${catkin_LIBRARIES}
)
target_link_libraries(demo02_client
${catkin_LIBRARIES}
)
5、终端命令行
1.命令行:roscore
2命令行:
cd demo04_ws/
source ./devel/setup.bash
rosrun plumbing_server_client demo01_server 需要先启动服务:rosrun 包名 服务
3命令行:
cd demo04_ws/
source ./devel/setup.bash
rosrun plumbing_server_client demo02_client 1 2 rosrun 包名 客户端 参数1 参数2
6、优化
优化1:
动态传值,也就是每次命令行手动输入参数,参照上面客户端代码
优化2:
问题:如果先启动客户端,那么会导致运行失败
解决方法:
在客户端发送请求前添加:client.waitForExistence();
或:ros::service::waitForService(“AddInts”); 可以参照上面客户端代码
这是一个阻塞式函数,只有服务启动成功后才会继续执行