ROS客户端Client与服务端Server的编程实现

本文深入讲解ROS中的服务通信机制,包括客户端与服务端的编程实现。通过实例演示如何使用ROS客户端向服务端发送请求,以及服务端如何响应并执行相应操作。同时,介绍了ROS服务相关的常用命令。

ROS客户端Client与服务端Server的编程实现


前情


之前学习了ROS节点中订阅者和发布者通讯方式,接下来了解的是另外一种通讯方式,也就是客户端和服务器之间的通讯。
包含客户端和服务端的服务模型(客户端/服务器)如下图(来自古月居ROS21讲)
在这里插入图片描述


建立客户端


  • 进入src文件夹,新建功能包
	~/catkin_ws/src$ catkin_create_pkg learning_service roscpp rospy std_msgs geometry_msgs turtlesim

其中后面的几项是功能包需要依赖的库。

  • 在功能包/src下创建文件
	touch turtle_spawn.cpp
  • 键入代码
    代码来自古月居ROS21讲
	#include <ros/ros.h>
	#include <turtlesim/Spawn.h>1
	
	int mian(int argc, char *argv)
	{
		//init ros node
		ros::init(argc,argv,"turtle_spawn");
		//create nodeHandle
		ros::NodeHandle node;
		ros::service::waitForService("/spawn");
		ros::ServiceClient add_turtle= node.serviceClient<turtlesim::Spawn>("/spawn");
		
		//初始化 turtlesim::spawn 的请求数据
		turtlesim::Spawn srv;
		srv.request.x=2.0;
		srv.request.y=2.0;
		srv.request.name="turtle2";
		
		ROS_INFO("Call service to spawn turtle[x:%0.6f,y:%0.6f,name:%s]",srv.request.x,srv.request.y,srv.request.name.c_str());
		add_turtle.call(srv);
		ROS_INFO("Spawn turtle successfully [name:%s]",srv.response.name.c_str());
		return 0;
	}

代码解释
1.

	ros::service::waitForService("/spawn");

这是一个阻塞型程序,一直等待 /spawn 的服务器存在,只有感知到这个服务器,程序才会继续进行
2.

	ros::ServiceClient add_turtle= node.serviceClient<turtlesim::Spawn>("/spawn");

实例化一个ServiceClient类的对象,创建了一个客户端,这个客户端是给 /spawn 这个服务发送请求的,请求的数据类型为 turtlesim::Spawn
3.
创建了客户端之后,紧接着就是封装请求数据,下面的程序就是定义了请求数据中的数据内容:

	turtlesim::Spawn srv;
	srv.request.x=2.0;
	srv.request.y=2.0;
	srv.request.name="turtle2";

turtlesim::Spawn 除了xy之外还有海龟的角度θ,如果不定义,默认为0;
请求的目的就是新生成一个海龟,这个海龟的信息就是xy,name
4.

ROS_INFO("Call service to spawn turtle[x:%0.6f,y:%0.6f,name:%s]",srv.request.x,srv.request.y,srv.request.name.c_str());

发给终端的日志信息,告诉我们已经发出请求了
5.

add_turtle.call(srv);

对服务端发出请求的指令
call()也是一个阻塞型函数,将request发出去之后一直等待直到

	ROS_INFO("Spawn turtle successfully [name:%s]",srv.response.name.c_str());

显示服务器的回应

  • 更改编译规则
    打开CMakeList.txt,修改编译规则

在这里插入图片描述

  • 回到根目录,编译

编译成功后,在/devel/lib/下就会出现对应的功能包名字的文件夹,里面存放着对应新的包的可执行文件

-测试,

	$ roscore
	$ rosrun turtlesim turtlesim_node
	$ rosrun learning_service turtle_spawn 

建立服务端


在src文件夹中创建新文件,编辑代码
注意,要包含以下头文件

	#include<ros/ros.h>
	#include<geometry_msgs/Twist.h>
	#include<std_srvs/Trigger.h>
头文件说明
#include<geometry_msgs/Twist.h>包含控制小海龟速度指令的头文件
#include<std_srvs/Trigger.h>包含服务数据类型std_srvs/Trigger 的头文件

全部代码为
代码来自古月居ROS21讲

#include<ros/ros.h>
#include<geometry_msgs/Twist.h>
#include<std_srvs/Trigger.h>

//声明全局对象和变量,这是因为在主函数和回调函数中都需要用到
ros::Publisher turtle_vel_pub;
bool pubCommand = false;//pubCommand作为一个flag标志位,标志海龟是运动还是停止

//service 回调函数,输入参数req,输出参数res,参数都是指针传入
bool commandCallback(std_srvs::Trigger::Request &req,std_srvs::Trigger::Response &res){
	pubCommand = !pubCommand;
	
	//显示请求数据
	ROS_INFO("Publish turtle velocity command[%s]",pubCommand==true?"Yes":"No");
	
	//设置反馈数据
	res.success = true;
	res.message = "Change turtle command state!";
	
	return true;
}

int main(int argc,char **argv){
	
	ros::init(argc,argv,"turtle_command_server");
	
	ros::NodeHandle n;
	// 创建一个服务器,这跟订阅者类似,因为不知道客户端的请求什么时候进来,
	//所以通过一个回调函数的机制来处理request指令,
	//一旦服务端收到针对服务名字"/turtle_command"的request请求之后
	//立刻会调用回调函数,在回调函数里针对request的内容的处理
	//比如这里是想通过客户端发送请求让小海龟动起来,这里就是在回调函数里让海龟运动
	ros::ServiceServer command_service = n.advertiseService("/turtle_command",commandCallback);
	//除了创建了服务端之外还创建了一个发布者,用于控制小海龟
	turtle_vel_pub = n.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel",10);
	
	ROS_INFO("Ready to receive turtle command.");
	
	ros::Rate loop_rate(10);
	
	while(ros::ok())
	{
		//查看回调函数队列有没有请求信息进来,如果有,则执行一次回调函数,然后继续向下
		ros::spinOnce();
		//如果pubCommand是false的话,就不会对小海龟发出指令
		if(pubCommand)
		{
			geometry_msgs::Twist vel_msg;
			vel_msg.linear.x=0.5;
			vel_msg.angular.z=0.2;
			turtle_vel_pub.publish(vel_msg);
		}
		
		loop_rate.sleep();
	}
	return 0;
}

代码解释在代码块的注释中
ros::spin() 和 ros::spinOnce() 称为ROS消息回调处理函数,通常会出现在ROS的主循环中,程序需要不断调用ros::spin() 或 ros::spinOnce(),两者区别在于前者调用后不会再返回,也就是你的主程序到这儿就不往下执行了,而后者在调用后还可以继续执行之后的程序。
如果你的程序写了相关的消息订阅函数,那么程序在执行过程中,除了主程序以外,ROS还会自动在后台按照你规定的格式,接受订阅的消息,但是所接到的消息并不是立刻就被处理,而是必须要等到ros::spin()或ros::spinOnce()执行的时候才被调用,这就是消息回到函数的原理。
就像上面说的,ros::spin() 在调用后不会再返回,也就是你的主程序到这儿就不往下执行了,而 ros::spinOnce() 后者在调用后还可以继续执行之后的程序。
其实看函数名也能理解个差不多,一个是一直调用;另一个是只调用一次,如果还想再调用,就需要加上循环了。
这里一定要记住,ros::spin()函数一般不会出现在循环中,因为程序执行到spin()后就不调用其他语句了,也就是说该循环没有任何意义,还有就是spin()函数后面一定不能有其他语句(return 0 除外),有也是白搭,不会执行的。ros::spinOnce()的用法相对来说很灵活,但往往需要考虑调用消息的时机,调用频率,以及消息池的大小,这些都要根据现实情况协调好,不然会造成数据丢包或者延迟的错误。
spin()是一直调用被调用,直到ctrl+c; 而 ros::spinOnce() 是,如果还想再调用,就需要加上循环语句 while(ros::ok())。
对于ros::spinOnce(),有些传输特别快的消息,尤其需要注意合理控制消息池大小和ros::spinOnce()执行频率; 比如:消息发送频率为10Hz,那么 当ros::spinOnce()的调用频率为5Hz,那么接收端消息池的大小就一定要大于2,才能保证数据不丢失,无延迟。

  • 修改编译规则
    打开CMakeList.txt, 加入如下语句
    在这里插入图片描述
  • 编译
    回到根目录,编译
  • 设置环境变量
  • 检查环境变量
  • 测试
	$ roscore
	$ rosrun turtlesim turtlesim_node
	$  rosrun learning_service turtle_command_server
	$ rosservice call /turtle_command "{}"

其中 /turtle_command 是刚才程序中定义的server服务端名称 , 即发送请求,当spinOnce()扫描到请求之后,执行回调。小海龟就动啦!

rosservice ros服务

COMMANDEXPAIN
rosservice list显示活动的服务信息
rosservice info [服务名称]显示指定服务的信息
rosservice type [服务名称]显示服务类型
rosservice find [服务类型]查找指定服务类型的服务
rosservice uri [服务名称]显示ROSRPC URI服务
rosservice args [服务名称]显示服务参数
rosservice call [服务名称] [参数]用输入的参数请求服务
参考博客:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值