ROS学习之路——第三章:服务通信

一、服务通信:

基于请求响应模式,一个节点A向节点B发送请求,B接受处理请求并产生响应结果返回给A。适用于对时时性有要求、具有一定逻辑处理的应用场景。

二、理论顺序:

1、Serve首先和ROS Master建立联系,注册话题和RPC地址

2、Client再和ROS Master建立联系,注册话题

3、当Serve和Client的话题匹配时,ROS Master向Client发送ROSRPC地址

4、之后,Client可以向Serve发送请求

5、Serve收到请求后,通过TCP地址,响应数据给Client

三、注意事项:

1、保证顺序:需要服务端提前启动,客户端才能发起请求

2、客户端和服务端可以存在多个

四、服务通信自定义srv消息

1、创建srv文件

(1)在新建的功能包里,建立一个srv目录,并在里面新建.srv文件

(2)srv文件内容如下:

#客户端请求时发送的两个数字:Request

int32 num1

int32 num2

---#这里用---来直接区别Request和Response

#服务器响应发送的数据:Response

int32 sum

(注意:int末尾没有;)

2、编辑配置文件

(1)在package.xml文件中第55行添加<build_depend>message_generation</build_depend>

         第65行添加<exec_depend>message_runtime</exec_depend>

(2)在CMakeLists.txt文件中第14行,添加依赖message_generation

(3)在CMakeLists.txt文件中第58行:解除注释并改为Addints.srv

(4)在CMakeLists.txt文件中第71行:解除注释并添加依赖std_msgs

(5)在CMakeLists.txt文件中第108行:解除注释,并添加message_runtime

3、编译生成中间文件

     在devel开发目录下

五、编写程序:

0、vscode配置:

在c_cpp_properties.json文件中"includePath"数组内添加路径:

1、编写服务端实现:

在src目录下添加.cpp文件并编写:

#include "ros/ros.h"
#include "plumbing_server_client/Addints.h"


/*
    服务端实现:解析客户端提交的数据,并运算再产生响应
    1.包含头文件
    2.初始化ROS节点
    3.创建节点句柄
    4.创建服务对象
    5.处理请求并产生响应
    6.spin()
*/

//回调函数是处理请求的
//参数1:plumbing_server_client文件下的Addints文件下的Request,地址给指针request了
//参数2同理
bool doNums(plumbing_server_client::Addints::Request &request,
            plumbing_server_client::Addints::Response &response)
{   
    //1.处理请求
    //根据.srv文件可知,上方的request所指的结构体下有num1和num2两个参数
    int num1=request.num1;
    int num2=request.num2;
    ROS_INFO("收到的请求数据:num1=%d,num2=%d",num1,num2);
    //2.组织响应
    int sum=num1+num2;
    response.sum=sum;   //返回给response的sum=
    ROS_INFO("求和结果:sum=%d",sum);
    return true;
}

int main(int argc, char *argv[])
{
    setlocale(LC_ALL,"");
    
    //2.初始化ROS节点
    ros::init(argc,argv,"Server1"); //节点名称需要保证唯一
    //3.创建节点句柄
    ros::NodeHandle nh;
    //4.创建服务对象(参数一:话题名称;参数二:回调函数,返回布尔值;)
    ros::ServiceServer server =nh.advertiseService("calculate",doNums);
    ROS_INFO("服务器启动");
    //5.处理请求并产生响应
    //6.spin()
    ros::spin();
    return 0;
}

2、编写客户端实现:

#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,"Client1");
    //3.创建节点句柄
    ros::NodeHandle nh;
    //4.创建客户端对象
    //调用了NodeHandle函数serviceClient,首先定下范型:plumbing_server_client::Addints;
    //其次参数写入话题:"calculate"(要和服务端的一样)
    ros::ServiceClient client=nh.serviceClient<plumbing_server_client::Addints>("calculate");
    //5.提交请求并处理响应
    plumbing_server_client::Addints ai; //新建一个ai表示plumbing_server_client::Addints路径
        //(1)组织请求
    ai.request.num1=100;
    ai.request.num2=200;
        //这时.srv文件里的num1和num2被赋值了
        //(2)处理响应
        //调用了ServiceClient里的函数call,返回布尔值,表示是否成功
    bool flag = client.call(ai);
    if(flag)
    {
        ROS_INFO("SUCCEED");
        //记得把sum的路径写清楚ai.response.sum
        ROS_INFO("响应结果为:%d",ai.response.sum);
    }
    else 
    {
        ROS_INFO("FAILED");
    }
    return 0;
}

3、编辑配置文件:

(1)在第137行添加:add_executable(server src/server.cpp)和

add_executable(client src/client.cpp)

(2)在第147行修改成:add_dependencies(server ${PROJECT_NAME}_gencpp)和add_dependencies(client ${PROJECT_NAME}_gencpp)

(3)在第150行添加:target_link_libraries(server${catkin_LIBRARIES})和

target_link_libraries(client${catkin_LIBRARIES})

4、编译并执行:

(1)服务端编写完后测试:

①启动roscore:

②在文件下,进行初始化更新,并且测试是否正常:

 

③测试完后,尝试发送服务和响应服务测试:

在另一个命令窗口重新更新后,启动rosservice call + 话题 + 空格 + Tab补齐 + 修改数字

可以得到sum=11

在上一个命令窗口输出:收到的请求数据:num1=5,num2=6

                                        求和结果:sum=11

(2)客户端编写完后测试:

①启动roscore:

②启动服务端:

③启动客户端并发送请求:

此时,客户端,服务端收到请求数据,并计算出sum,并发送响应。客户端收到响应结果并打印了出来。

六、程序优化

我们上面是将客户端输入的数字固定死了,但正常而言我们需要可编辑的数字,因此我们进行了如下的改进。

1、首先我们需要知道int main(int argc, char *argv[])里的argc表示的是参数个数,argv[ ]里面装的就是参数的字符串类型

(1)判断输入参数的个数是否正确:

//优化实现,获取命令中的参数

if(argc!=3)

{

        ROS_INFO("提交的参数个数不对");

        return 1;

}

(2)将原本的定值换成改变值:

//(1)组织请求

//atoi()将字符串类型的参数转换为int类型的参数:ascill to interger

ai.request.num1=atoi(argv[1]);

ai.request.num2=atoi(argv[2]); 

(3)编译并下发执行命令:

需要注意就是在运行客户端rosrun的时候,我们需要加入两个参数(需要用空格隔开)

2、我们还要优化的是,先启动客户端,但不直接报出异常,而是挂起,等到服务器启动后,再正常请求

(1)解决方案:在ROS中内置了相关函数,这些函数可以让客户端启动后挂起,等到服务器启动后再进行请求

(2)在程序发送请求之前,我们判断一下服务器的状态:

client.waitForExistence(); //只有当服务器启动了,才会执行下面的程序

ros::service::waitForService("calculate"); //同样是判断服务器,这个需要添加话题参数

这样,当我们先启动客户端时,客户端会挂起,等待服务端启动后,客户端会接着发送请求。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值