# 【ROS教程】服务通信

@[TOC](https://github.com/UnderTurrets/notes/blob/master/ROS/%E6%96%87%E7%AB%A0%E7%9B%AE%E5%BD%95/README.md)

***

## 1.流程

服务通信也是ROS中一种极其常用的通信模式，服务通信是基于请求响应模式的，是一种应答机制。也即: 一个节点A向另一个节点B发送请求，B接收处理请求并产生响应结果返回给A。在ROS中，实现服务通信只需要如下几步：

1. 确定**客户端发送的请求的数据类型和服务端响应的数据类型**，**需要自定义.srv文件，修改好CMakeLists.txt文件和package.xml文件并重编译**
2. 编写发布方和订阅方的cpp文件，修改好CMakeLists.txt文件并重编译
3. 分别启动发布方节点和订阅方节点，**必须先启动服务端再启动客户端**

## 2.自定义请求和响应的数据

### 2.1 std\_msgs内置类型

* 内置类型与 C++ 和 Python 中的对应关系:

| Primitive Type | C++           | Python         |
| -------------- | ------------- | -------------- |
| bool           | uint8\_t      | bool           |
| int8           | int8\_t       | int            |
| uint8          | uint8\_t      | int            |
| int16          | int16\_t      | int            |
| uint16         | uint16\_t     | int            |
| int32          | uint32\_t     | int            |
| uint64         | uint64\_t     | long int       |
| float32        | float         | float          |
| float64        | double        | float          |
| string         | std::string   | str bytes      |
| time           | ros::Time     | rospy.Time     |
| duration       | ros::Duration | rospy.Duration |

* 内置类型的数组与 C++ 和 Python 中的对应关系:

| Primitive Type  | C++                                  | Python |
| --------------- | ------------------------------------ | ------ |
| variable-length | std::vector                          | tuple  |
| fixed-length    | boost::array\<T, length>或std::vector | tuple  |

### 2.2 编写.srv文件

示例如下：

```shell
#文件名AddInt.srv
# 客户端请求时发送的两个数字
int32 num1
int32 num2
---
# 服务器响应发送的数据
int32 sum
```

* **中间的三横用以区分请求数据和响应数据**

### 2.3 修改package.xml文件

* 查看是否存在如下编译依赖

```xml
<build_depend>message_generation</build_depend>
```

* 查看是否存在如下执行依赖

```xml
<exec_depend>message_generation</exec_depend>
```

### 2.4 修改CMakeLists.txt文件

#### 2.4.1 修改find\_package指令

```cpp
# 需要加入 message_generation,必须有 std_msgs
find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)
```

#### 2.4.2 添加add\_message\_files指令

```cpp
## 配置 srv 源文件
add_service_files(
  FILES
  AddInt.srv
)
```

#### 2.4.3 添加generate\_messages指令

```cpp
generate_messages(
  DEPENDENCIES
  std_msgs
)
```

其中，**add\_service\_files指令必须要在generate\_messages指令的前面**，然后在工作空间目录编译即可。

### 2.5 查看头文件

经过以上几步，在`${workspace}/devel/include/${package}/`目录下应该会出现头文件，如图：

![在这里插入图片描述](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/ROS/assets%E3%80%90ROS%E6%95%99%E7%A8%8B%E3%80%91%E6%9C%8D%E5%8A%A1%E9%80%9A%E4%BF%A1/2fbcafb0c87c4527906a4bf075e1f4ec.png)

* **如果没有出现，是无法进行接下来的步骤的。这时，只需要把`${workspace}/`目录下的build目录和devel目录全部删除，然后重新编译即可。**

```shell
rm -rf build/
rm -rf devel/
catkin_make
```

## 3.编写cpp文件

### 3.1 功能包目录文件树

![在这里插入图片描述](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/ROS/assets%E3%80%90ROS%E6%95%99%E7%A8%8B%E3%80%91%E6%9C%8D%E5%8A%A1%E9%80%9A%E4%BF%A1/3426c515b6f5478ca604c1cbdc7c5675.png)

### 3.2 修改CMakeLists.txt文件

#### 3.2.1 添加add\_executable指令

```cpp
add_executable(server src/server.cpp)
add_executable(client src/client.cpp)
```

#### 3.2.2 添加add\_dependencies指令

```cpp
add_dependencies(server ${PROJECT_NAME}_gencpp)
add_dependencies(client ${PROJECT_NAME}_gencpp)
```

#### 3.2.3 添加target\_link\_libraries指令

```cpp
target_link_libraries(server
        ${catkin_LIBRARIES}
        )
target_link_libraries(client
        ${catkin_LIBRARIES}
        )
```

### 3.3 服务端cpp

示例如下：

```cpp
/*
    需求:
        编写两个节点实现服务通信，客户端节点需要提交两个整数到服务器
        服务器需要解析客户端提交的数据，相加后，将结果响应回客户端，
        客户端再解析

    服务器实现:
        1.包含头文件
        2.初始化 ROS 节点
        3.创建 ROS 句柄
        4.创建 服务 对象
        5.回调函数处理请求并产生响应
        6.由于请求有多个，需要调用 ros::spin()

*/
// 1.包含头文件
#include "ros/ros.h"
#include "serve/AddInt.h"

// bool 返回值由于标志是否处理成功
bool reponse(serve::AddInt::Request& req,
           serve::AddInt::Response& resp){
    int num1 = req.num1;
    int num2 = req.num2;

    ROS_INFO("服务器接收到的请求数据为:num1 = %d, num2 = %d",num1, num2);

    //逻辑处理
    if (num1 < 0 || num2 < 0)
    {
        ROS_ERROR("提交的数据异常:数据不可以为负数");
        return false;
    }

    //如果没有异常，那么相加并将结果赋值给 resp
    resp.sum = num1 + num2;
    return true;


}

int main(int argc, char *argv[])
{
    //设置编码
    setlocale(LC_ALL,"");
    // 2.初始化 ROS 节点
    ros::init(argc,argv,"server");
    // 3.创建 ROS 句柄
    ros::NodeHandle nh;
    // 4.创建 服务 对象，回调函数的返回值必须是布尔类型
    ros::ServiceServer server = nh.advertiseService("AddInt",reponse);
    ROS_INFO("服务已经启动....");
    //     5.回调函数处理请求并产生响应
    //     6.由于请求有多个，需要调用 ros::spin()
    ros::spin();
    return 0;
}
```

* **创建服务对象时的回调函数的返回值必须是布尔类型**

### 3.4 客户端cpp

示例如下：

```cpp
/*
    需求:
        编写两个节点实现服务通信，客户端节点需要提交两个整数到服务器
        服务器需要解析客户端提交的数据，相加后，将结果响应回客户端，
        客户端再解析

    服务器实现:
        1.包含头文件
        2.初始化 ROS 节点
        3.创建 ROS 句柄
        4.创建 客户端 对象
        5.请求服务，接收响应

*/
// 1.包含头文件
#include "ros/ros.h"
#include "serve/AddInt.h"

int main(int argc, char *argv[])
{
    //设置编码
    setlocale(LC_ALL,"");

    // 调用时动态传值,如果通过 launch 的 args 传参，需要传递的参数个数 +3
    if (argc != 3)
        // if (argc != 5)//launch 传参(0-文件路径 1传入的参数 2传入的参数 3节点名称 4日志路径)
    {
        ROS_ERROR("请提交两个整数");
        return 1;
    }


    // 2.初始化 ROS 节点
    ros::init(argc,argv,"client");
    // 3.创建 ROS 句柄
    ros::NodeHandle nh;
    // 4.创建 客户端 对象
    ros::ServiceClient client = nh.serviceClient<serve::AddInt>("AddInt");
    //等待服务启动成功
    //方式1
    ros::service::waitForService("AddInt");
    //方式2
    // client.waitForExistence();
    // 5.组织请求数据
    serve::AddInt ai;
    ai.request.num1 = atoi(argv[1]);
    ai.request.num2 = atoi(argv[2]);
    // 6.发送请求,返回 bool 值，标记是否成功
    bool flag = client.call(ai);
    // 7.处理响应
    if (flag)
    {
        ROS_INFO("请求正常处理,响应结果:%d",ai.response.sum);
    }
    else
    {
        ROS_ERROR("请求处理失败....");
        return 1;
    }

    return 0;
}
```

## 4.效果

![在这里插入图片描述](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/ROS/assets%E3%80%90ROS%E6%95%99%E7%A8%8B%E3%80%91%E6%9C%8D%E5%8A%A1%E9%80%9A%E4%BF%A1/85591902029a453a9ab328e07ffdd2de.png)

![在这里插入图片描述](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/ROS/assets%E3%80%90ROS%E6%95%99%E7%A8%8B%E3%80%91%E6%9C%8D%E5%8A%A1%E9%80%9A%E4%BF%A1/f23861e6a57344c59475ad6ed581cfab.png)

![在这里插入图片描述](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/ROS/assets%E3%80%90ROS%E6%95%99%E7%A8%8B%E3%80%91%E6%9C%8D%E5%8A%A1%E9%80%9A%E4%BF%A1/dce7ac09c6544a8884ac8afd8aba48ea.png)

![在这里插入图片描述](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/ROS/assets%E3%80%90ROS%E6%95%99%E7%A8%8B%E3%80%91%E6%9C%8D%E5%8A%A1%E9%80%9A%E4%BF%A1/ecee987737c241d7aba65655976df55b.png)
