服务模型:
![](https://i-blog.csdnimg.cn/blog_migrate/aaa939aa626ee0ccc8f748a557fc4e16.png)
想对于发布订阅模型的区别:请求一次才会接收一次,而不是不断的发布。
自定义服务数据
1.定义srv文件
2.在package.xml中添加功能包依赖
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
3.在CMakeLists.txt添加编译选项
add_service_files(FILES Person.srv)
generate_messages(DEPENDENCIES std_msgs)
4.编译生成语言的相关文件
第一个.h文件是后两个的集合,前面的PersonRequest.h是“---”上面的,Person Response.h是“---”下面的。
添加到cMakeLists.txt
客户端:
/**
* 该例程将请求/show_person服务,服务数据类型learning_service::Person
*/
#include <ros/ros.h>
#include "learning_service/Person.h" //头文件包含,这里包含的是刚动态生成的头文件
int main(int argc, char** argv)
{
// 初始化ROS节点
ros::init(argc, argv, "person_client");
// 创建节点句柄
ros::NodeHandle node;
// 发现/show_person服务后,创建一个服务客户端,连接名为/show_person的service
//waitForService,等待这个show_person这样一个服务的提供者运行起来
ros::service::waitForService("/show_person");
ros::ServiceClient person_client = node.serviceClient<learning_service::Person>("/show_person");
//在这里创建Client这一端,Client这一端会去发布的请求数据是learning_service::Person(自定义的),另外其数据的service的对象是要用的show_person通道
// 初始化learning_service::Person的请求数据
//针对person数据添加进来名字年龄性别,这里与之前的使用方法完全一样
learning_service::Person srv;
srv.request.name = "Tom";
srv.request.age = 20;
srv.request.sex = learning_service::Person::Request::male;
//因为之前定义的宏,所以male的定义可以用learning_service::Person因为是在request里面,所以与message的区别是新加了一个request命名空间
// 请求服务调用
//接下来通过person_client发布一个call去请求把该请求数据发出去,然后回卡在这里等待srv的反馈结果最终反馈收到之后会跳到最后一行显示反馈的结果
ROS_INFO("Call service to show person[name:%s, age:%d, sex:%d]",
srv.request.name.c_str(), srv.request.age, srv.request.sex);
person_client.call(srv);
// 显示服务调用结果
ROS_INFO("Show person result : %s", srv.response.result.c_str());
return 0;
};
//要注意learning_service::Person的使用方式,要include头文件,然后置于尖括号里,之后填充具体数据,使用起来就是learning_service的具体空间加上Person名字learning_service::Person srv注意这里的Person名字与定义的srv的文件名Person srv是一样的,因为我们生成头文件的时候头文件的命名就会完全根据这个文件的名字来命名
//这就是我们在Client这一端使用person数据类型的方式
服务端
/**
* 该例程将执行/show_person服务,服务数据类型learning_service::Person
*/
#include <ros/ros.h>
#include "learning_service/Person.h"
//一样先调用include头文件
// service回调函数,输入参数req,输出参数res
bool personCallback(learning_service::Person::Request &req,
learning_service::Person::Response &res)
{
// 显示请求数据,人的名字年龄性别
ROS_INFO("Person: name:%s age:%d sex:%d", req.name.c_str(), req.age, req.sex);
// 设置反馈数据,ok可以改,因为这是一个字符串反馈给Client客户端的,比如OK,success都可以
res.result = "OK";
return true;
}
int main(int argc, char **argv)
{
// ROS节点初始化
ros::init(argc, argv, "person_server");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个名为/show_person的server,注册回调函数personCallback
//创建一个server的实例,server这边会一直在提供show_person这样一个server服务,然后一旦有服务请求进来之后调用personCallback进入到回调函数
ros::ServiceServer person_service = n.advertiseService("/show_person", personCallback);
// 循环等待回调函数
ROS_INFO("Ready to show person informtion.");
ros::spin();
//最终有一个spin,会不断的等待request数据进来,一旦有request进来之后就会进入到回调函数里来,所以我们的server端是一个循环,会一直在spin里执行,我们的Client端request一次之后会结束,我们可以不断的去运行这个Client这端的程序,server这端只用运行一次就可以
return 0;
}
//在这里我们需要注意定义好的Person,先包含头文件然后可以去使用,同时注意回调函数里的输入参数,一般都是这样的类型,前面learning_service::Person然后Request端和Response端,这两个命名空间是ROS强制定义的,只需要修改前面这段learning_service::Person就可以改成所定义的数据类型;还有后面数据类型的使用方式 req.name.c_str(), req.age, req.sex
//这就是我们自定义的数据在person的server端的使用的方式