服务通信简介
服务通信适用于对实时性有要求、具有一定逻辑处理的应用场景。
三个角色:master、server、client。
注意点:①保证顺序,客户端发起请求时,服务端需已启动;②客户端和服务端都可以存在多个。
自定义内容的服务通信
- 服务通信创建srv
- 按照固定格式创建srv文件
①在工作空间下src文件夹中创建功能包plumbing_server_client,并添加依赖;
②在功能包中新建srv文件夹,并在文件夹下新建srv文件:AddInts.srv;
③srv文件中输入内容:
int32 num1
int32 num2
---
#请求与相应之间以---隔开
int32 sum
-
编辑内置文件
①package.xml文件配置
与前相同;添加message_generation与message_runtime依赖;
②CMakeList.txt文件配置
(1)find_package 下添加message_generation
;
(2)释放add_service_files,并修改srv文件名为AddInts.srv;
(3)释放generate_message对std_msgs的依赖;
(4)catkin_package中释放CATKIN_DEPENDS行。//find_package是当前创建的功能包所依赖的包,catkin_package是依赖包所依赖的功能包。 -
编译生成中间文件
编译一下,即可生成中间文件。
其中C++使用的中间文件在工作空间下的devel/include/plumbing_server_client文件夹下;
python使用的中间文件为devel/lib/python2.7(此处依实际python版本号而定)/plumbing_server_client下的AddInts.py。
为了后续编辑代码时可以代码补齐,这里最好进行一下vscode的配置,将生成的中间文件所在的目录配置进c_cpp_properties.json的includePath中。具体操作方法同前。
- 服务端实现
- 新建文件
在功能包下src文件中新建demo01_server.cpp; - 编辑代码
#include "ros/ros.h"
#include "plumbing_server_client/AddInts.h"
/*
服务端实现:解析客户端提交的数据,并运算再产生响应
1.包含头文件
2.初始化ROS节点
3.创建节点句柄
4.创建一个服务对象
5.处理请求并产生响应
6.spin()
*/
bool doNums(plumbing_server_client::AddInts::Request &request,
plumbing_server_client::AddInts::Response &response){
//1.处理请求
int num1 = request.num1;
int num2 = request.num2;
ROS_INFO("收到的请求数据:num1 = %d, num2 = %d",num1,num2);
//2.组织响应
int sum = num1 + num2;
response.sum = sum;
ROS_INFO("求和结果:sum = %d",sum);
return true;
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
// 2.初始化ROS节点
ros::init(argc,argv,"heiShui");//节点名称需唯一
// 3.创建节点句柄
ros::NodeHandle nh;
// 4.创建一个服务对象
ros::ServiceServer server = nh.advertiseService("addInts",doNums);
ROS_INFO("服务器端启动");
// 5.处理请求并产生响应
// 6.spin()
ros::spin();
return 0;
}
- 编写配置CMakeList.txt文件
①释放(建议先复制)add_executable行,并写入文件名和映射名;
②释放target_link_librarues并写入映射名;
③释放add_dependencies//保证先编译srv文件再编译请求方和响应方文件;
第一个参数为映射名,第二个为${PROJECT_NAME}_gencpp
。 - 编译并测试是否成功运行
编译后即可运行,可使用下列命令进行测试,运行结果应为6:
rosservice call addInts "num1: 1 num2: 5"
- 客户端实现
- 新建文件
方法同前; - 编辑代码
#include "ros/ros.h"
#include "plumbing_server_client/AddInts.h"
/*
客户端:提交两个整数,并处理响应的结果
1.包含头文件
2.初始化ROS节点
3.创建节点句柄
4.创建一个客户端对象
5.提交请求并处理响应
*/
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
// 2.初始化ROS节点
ros::init(argc,argv,"daBao");
// 3.创建节点句柄
ros::NodeHandle nh;
// 4.创建一个客户端对象
ros::ServiceClient client = nh.serviceClient<plumbing_server_client::AddInts>("addInts");
// 5.提交请求并处理响应
plumbing_server_client::AddInts ai;//创建一个AddInts对象
//5-1.组织请求
ai.request.num1 = 150;
ai.request.num2 = 300;
//5-2.处理响应
bool flag = client.call(ai);
if (flag)
{
ROS_INFO("响应成功!");
ROS_INFO("响应结果 = %d",ai.response.sum);
} else
{
ROS_INFO("处理失败....");
}
return 0;
}
- 编写配置文件
方法同前 - 编译并运行
使用命令分别依次运行roscore、服务端、客户端。
几点优化
- 考虑客户端先于服务端运行的情况
实际应用中,我们肯定要考虑客户端先于服务端运行的情况,此时的解决思路是,客户端运行之前自动检测服务端是否允许,若无,则一直等待而非运行客户端从而输出错误信息,可以在“处理响应”之前使用下列两个函数实现此功能:
//函数1
client.waitForExistence();
//函数2
ros::service::waitForService("addInts");//此处括号内为服务名称;
- 内容可变的通信信息
此时需注意三点:
①命令格式为rosrun <包名> <节点名> num1 num2
;
②需提前检验输入的数据个数是否正确,并获取命令中的参数,组织进request;
③使用atoi()
将字符串转变为整型数字;
代码如下:
#include "ros/ros.h"
#include "plumbing_server_client/AddInts.h"
/*
客户端:提交两个整数,并处理响应的结果
1.包含头文件
2.初始化ROS节点
3.创建节点句柄
4.创建一个客户端对象
5.提交请求并处理响应
实现参数的动态提交:
1.格式:rosrun <包名> <节点名> 12 34
2.节点执行时,需要获取命令中的参数,并组织进request
*/
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
//优化实现,要获取命令中的参数
if (argc != 3) //argc三个参数??
{
ROS_INFO("提交的参数个数不对!");
return 1;
}
// 2.初始化ROS节点
ros::init(argc,argv,"daBao");
// 3.创建节点句柄
ros::NodeHandle nh;
// 4.创建一个客户端对象
ros::ServiceClient client = nh.serviceClient<plumbing_server_client::AddInts>("addInts");
// 5.提交请求并处理响应
plumbing_server_client::AddInts ai;//创建一个AddInts对象
//5-1.组织请求
ai.request.num1 = atoi(argv[1]);//atoi()将字符串类型变为整型值
ai.request.num2 = atoi(argv[2]);
//5-2.处理响应
//调用判断服务器状态的函数
bool flag = client.call(ai);
if (flag)
{
ROS_INFO("响应成功!");
ROS_INFO("响应结果 = %d",ai.response.sum);
} else
{
ROS_INFO("处理失败....");
}
return 0;
}