话题通信以及服务通信实现思路与比较
1.话题:
分两个板块实现publisher和subscriber
topic是一种点对点的单向通信方式,这里的“点”指的是node,是异步通信。
subscriber通过回调(Callback)对接受的消息进行处理。
两个板块的实现,首先初始化节点ros::init(argc,argv,"name");
然后用ros::NodeHandle n;创建句柄
然后分别初始化pub和sub对象,用=n.advertise<package_name::msg_name>("/name",缓冲区大小)给pub初始化(广告),用=n.subscribe("/name",缓冲区大小,poseCallback)给sub初始化。
对于sub,就可以直接ros::pin();结束了,但是得先定义回调函数,一般用ROS_INFO(“”,)输出信息。
对于pub,先设置ros::Rate loop_rate(1);以及int count=0;
然后在循环里反复发布话题消息
while(ros::ok()){
package_name::message_name msg对象;
msg.xx=设置消息内容
用pub对象.publish(msg对象);发布消息
ROS——INFO();
loop_rate.sleep();实现消息发布频率
}
2.服务:
分两个板块实现server和client
是一种双向的通信方式,是同步的,在client发送请求后,直到server回应,一直处于阻塞状态。
server不会一直传输,在一次完成后就结束了服务。
首先还是ros::init(argc,argv,"node_name");
然后创建句柄ros::NodeHandle n;
此时对于server直接创建ros::ServiceServer对象,用n.advertise("/server_name",Callback)初始化
然后ROS_INFO输出等待服务的消息,
ros::spin();就可以结束了,在spin里回调实现主功能。
server的回调一般有两个参数(package_name::srv_name::Request& rep,
package_name::srv_name::Response& res){
ROS_INFO输出显示数据
res.result="ok";设置反馈数据
}
对于client,在初始化ServiceClient前先要等待目标服务,ros::service::waitForService("/server_name");
然后ros::ServiceClient初始化服务客户端对象。
然后请求服务调用ROS_INFO()请求服务调用
开始请求client对象.call(srv对象)
注意最后要显示调用反馈结果 ROS_INFO("Show result : %s", srv对象.response.result.c_str());
在定义一个客户端和订阅者时,其实我们并不是很关心客户端/订阅者的名字,因为这只是用来区分不同客户端/订阅者的工具,但是在用句柄初始化时传入的参数是非常关键的,因为这个参数代表的是服务/话题的名字(由服务者和发布者发出)。由此我们可以大致区分service(srv)/server和message(msg)/publisher这些概念了。
另外,两个talker的初始化是advertise<package_name::message_name>(“topic_name”,1)和ServiceServer=advertiseService(“service_name”,&pkg_name::Callback,this);(this是当Callback是成员函数时才特有的)
两个listener的初始化是subscribe<pkg_name::message>(“topic_name”,1,boost::bind(&pkg_name::Callback,this,_1));注意这也是回调成员函数的特殊写法。
还有ServiceClient=n.serviceClient<pkg_name::service>(“service_name”);用于客户端的实例化。
1.服务通信的逻辑:(上面的代码部分写的不好)
/ 首先是一个srv文件,分为request和response两个部分,客户端在实例化时给出要订阅的服务名称(“service_name”),然后在客户端程序中按照需求给srv.request中的数据赋值,之后再根据需要是否在循环中调用client.call(srv),这也决定了你的服务通信是否是一次性的,用一个if(client.call(srv))来判断是否建立通讯,如果成功则ROS_INFO服务端处理过的res值,如果失败则ROS_INFO(“failed”);
/注意call应该是一个很复杂的函数,他通过服务的名字来选择传参目标,而这个名字我猜测是类似param的静态字典key的一种存储形式,并且在call调用之后客户端程序就停止了,直到服务端处理结束后返回一个结果才继续执行程序。服务端一般是执行至spin();等待客户端调用,客户端一旦执行call函数,服务器在spin()中就会调用Callback函数对srv进行处理,注意spin()函数继续执行,也就是服务端不退出,除非Ctrl+c
/在服务端的回调函数中对call(srv)传递的服务参数srv进行了分解,先对pkg_name::servise::Request&req进行判断(在客户端对req有过赋值处理),具体判断条件由需求决定,如果判断成功,则把res中变量的值赋为正确的需要的值,并且对call函数传递返回值true,客户端自然会继续执行后续操作(比如ROS_INFO(srv.response)信息输出)。
2.话题通信的逻辑:
//首先是一个msg文件,没有srv和action那么复杂。
对消息的修改在publisher中完成,在publisher中定义一个msg对象,然后对该对象进行赋值,赋值后执行pub.publish(msg)传递消息给订阅者,说明发布者掌握主动权,是一种单向通信。然后ros::spin()等待下次发布,loop_rate.sleep()实现频率发布,++count记录发布次数。
/订阅端一般只能在回调函数Callback(const pkg_name::message ConstPtr &msg)中对发布者publish的msg实现相关ROS_INFO,然后ros::spin();等待下一次接收信息。
/这样来看话题通信中订阅者只负责接受消息和输出消息,而在服务通信中服务端一直在spin()处等待客户端call他,而客户端首先便对srv进行了处理(即给request部分赋值),然后才client.call(srv)将srv传给客户端回调函数,回调函数(先对request部分值进行判断)再对response部分处理后将结果传回给客户端,客户端输出response,是一种双向通信。
补充一点实际用法:
1.消息回调函数一定是const类型的,不可以对消息进行修改。
2.spin函数不会出现在循环,且spin后一般不会在有语句。
spinOnce函数可以放在循环里,但要注意spinOnce调用的频率和发布消息的频率是否能够使队列中的消息完整的传递。否则可能出现数据损失和延迟。
3.在一个节点中同时用两种通信方式时一般用spinOnce来避免冲突。