概述
之前提到的发布-订阅通信机制是端到端的通信,发布者不知道订阅者,订阅者也不知道发布者;服务-客户端通信机制是点到点的通信,服务器知道客户端,客户端也知道服务器,这样就对发布-订阅通信机制有了一种改进,但是这种方法也有一种限制,就是客户端在等待服务器响应的时候,不能做任何事情。因此就有了第三种通信机制:动作服务器-动作客户端,这种通信机制使得客户端在执行对服务器的请求的时候,还可以继续运行其他任务。
定义动作服务器消息
动作服务器消息也叫操作消息,和服务-客户端的服务消息类似。
注意三个区域分别是goal,result,feedback,顺序是固定的,不可以更改,各区域之间由三个短横线分割。
动作服务器
#include <ros/ros.h>
//这是导入了simple_action_server包中的头文件
#include <actionlib/server/simple_action_server.h>
//这是自己定义的操作消息的描述
#include <example_action_server/demoAction.h>
int g_count=0;
bool g_count_failure=false;
class ExampleActionServer{
private:
ros::NodeHandle nh_;
//定义了SimpleActionServer类的对象as_,相当于构建了一个动作服务器
actionlib::SimpleActionServer<example_action_server::demoAction> as_;
//动作服务器将使用这些消息来进行通信
example_action_server::demoGoal goal_;
example_action_server::demoResult result_;//将结果传回给动作客户端
example_action_server::demoFeedback feedback_;
public:
ExampleActionServer();//构造函数
~ExampleActionServer(void){}//析构函数
//这是回调函数,参数是指向目标消息的指针
void executeCB(const actionlib::SimpleActionServer<example_action_server::demoAction>::GoalConstPtr& goal);
};
//相当于初始化动作服务器,取名为“example_action”,并指定回调函数为executeCB(使得回调函数与动作服务器相关联)
ExampleActionServer::ExampleActionServer():as_(nh_,"example_action",boost::bind(&ExampleActionServer::executeCB,this,_1),false)
{
ROS_INFO("in constructor of exampleActionServer...");
as_.start();
}
//这是回调函数的实现,一旦执行了回调函数,必须通过setAborted或者setSucceeded来将result消息传回客户端
void ExampleActionServer::executeCB(const actionlib::SimpleActionServer<example_action_server::demoAction>::GoalConstPtr& goal){
g_count++;
result_.output=g_count;
result_.goal_stamp=goal->input;
if(g_count!=goal->input){
ROS_WARN("hey--mismatch!");
ROS_INFO("g_count=%d;goal_stamp=%d",g_count,result_.goal_stamp);
g_count_failure=true;
ROS_WARN("informing client of aborted goal");
as_.setAborted(result_);
}
else{
as_.setSucceeded(result_);
}
}
int main(int argc,char** argv){
ros::init(argc,argv,"demo_action_server_node");
ROS_INFO("instantiating the demo action server:");
ExampleActionServer as_object;
ROS_INFO("going into spin");
while(!g_count_failure){
ros::spinOnce();
}
return 0;
}
动作客户端
#include <ros/ros.h>
#include <actionlib/client/simple_action_client.h>
#include <example_action_server/demoAction.h>
//这是回调函数
void doneCb(const actionlib::SimpleClientGoalState& state,const example_action_server::demoResultConstPtr& result){
ROS_INFO("doneCB:server responded with state [%s]",state.toString().c_str());
int diff=result->output-result->goal_stamp;
ROS_INFO("got result output=%d;goal_stamp=%d;diff=%d",result->output,result->goal_stamp,diff);
}
int main(int argc,char** argv){
ros::init(argc,argv,"demo_action_client_node");
int g_count=0;
//这是目标消息,用来将目标传给动作服务器
example_action_server::demoGoal goal;
//这是在构建动作客户端action_client,指明要连接的动作服务器
actionlib::SimpleActionClient<example_action_server::demoAction> action_client("example_action",true);
//用来判断是否连接服务器成功
ROS_INFO("waiting for server: ");
bool server_exists=action_client.waitForServer(ros::Duration(5.0));
if(!server_exists){
ROS_INFO("could not connect to server;halting");
return 0;
}
//到这儿说明连接成功了
ROS_INFO("connected to action server");
while(true){
g_count++;
goal.input=g_count;
//发送目标消息
action_client.sendGoal(goal,&doneCb);
bool finished_before_timeout=action_client.waitForResult(ros::Duration(5.0));
if(!finished_before_timeout){
ROS_WARN("giving up waiting on result for goal number %d",g_count);
return 0;
}else
{
}
}
return 0;
}
编写CMakeLists.txt文件
类似话题消息和服务消息,在添加了新文件以后也要修改下面三个地方。
这里是在添加了动作服务端和动作客户端以后需要修改的地方。
运行截图
这实现的其实就是一个简单的比较,如果客户端输入goal->input的与服务器期望g_count的不一样,那么服务器就异常退出;如果相同,那么服务器就成功完成。
为数不多的代码写了注释的文章,老是抱怨别人不写注释,原来写注释这么麻烦。嗯…看来以后还是不能写注释,让别人写。