2.3 服務(wù)通信
場景
服務(wù)通信也是ROS中一種極其常用的通信模式,服務(wù)通信是基于請求響應(yīng)模式的,是一種應(yīng)答機制。也即:一個節(jié)點A向另一個節(jié)點B發(fā)送請求,B接收處理請求并產(chǎn)生響應(yīng)結(jié)果返回給A。比如如下場景:
機器人巡邏過程中,控制系統(tǒng)分析傳感器數(shù)據(jù)發(fā)現(xiàn)可疑物體或人... 此時需要拍攝照片并留存。
在上述場景中,就使用到了服務(wù)通信。
數(shù)據(jù)分析節(jié)點A需要向相機相關(guān)節(jié)點B發(fā)送圖片存儲請求,節(jié)點B處理請求,并返回處理結(jié)果。
與上述應(yīng)用類似的,服務(wù)通信更適用于對實時性有要求、具有一定邏輯處理的應(yīng)用場景。
概念
服務(wù)通信是以請求響應(yīng)的方式實現(xiàn)不同節(jié)點之間數(shù)據(jù)傳輸?shù)耐ㄐ拍J?。發(fā)送請求數(shù)據(jù)的對象稱為客戶端,接收請求并發(fā)送響應(yīng)的對象稱之為服務(wù)端,同話題通信一樣,客戶端和服務(wù)端也通過話題相關(guān)聯(lián),不同的是服務(wù)通信的數(shù)據(jù)傳輸是雙向交互式的。

服務(wù)通信中,服務(wù)端與客戶端是一對多的關(guān)系,也即,同一服務(wù)話題下,存在多個客戶端,每個客戶端都可以向服務(wù)端發(fā)送請求。

作用
用于偶然的、對實時性有要求、有一定邏輯處理需求的數(shù)據(jù)傳輸場景。
2.3.1 案例以及案例分析
1.案例需求
需求:編寫服務(wù)通信,客戶端可以提交兩個整數(shù)到服務(wù)端,服務(wù)端接收請求并解析兩個整數(shù)求和,然后將結(jié)果響應(yīng)回客戶端。

2.案例分析
在上述案例中,需要關(guān)注的要素有三個:
客戶端;
服務(wù)端;
消息載體。
3.流程簡介
案例實現(xiàn)前需要先自定義服務(wù)接口,接口準備完畢后,服務(wù)實現(xiàn)主要步驟如下:
編寫服務(wù)端實現(xiàn);
編寫客戶端實現(xiàn);
編輯配置文件;
編譯;
執(zhí)行。
案例我們會采用C++和Python分別實現(xiàn),二者都遵循上述實現(xiàn)流程。
4.準備工作
終端下進入工作空間的src目錄,調(diào)用如下兩條命令分別創(chuàng)建C++功能包和Python功能包。
ros2 pkg create cpp02_service --build-type ament_cmake --dependencies rclcpp base_interfaces_demo?
ros2 pkg create py02_service --build-type ament_python --dependencies rclpy base_interfaces_demo
2.3.2 服務(wù)通信接口消息
定義服務(wù)接口消息與定義話題接口消息流程類似,主要步驟如下:
創(chuàng)建并編輯?
.srv
文件;編輯配置文件;
編譯;
測試。
接下來,我們可以參考案例編寫一個srv文件,該文件中包含請求數(shù)據(jù)(兩個整型字段)與響應(yīng)數(shù)據(jù)(一個整型字段)。
1.創(chuàng)建并編輯 .srv 文件
功能包base_interfaces_demo下新建srv文件夾,srv文件夾下新建AddInts.srv文件,文件中輸入如下內(nèi)容:
int32 num1?
int32 num2
---?
int32 sum
2.編輯配置文件
1.package.xml 文件
srv文件與msg文件的包依賴一致,如果你是新建的功能包添加srv文件,那么直接參考定義msg文件時package.xml 配置即可。由于我們使用的是base_interfaces_demo該包已經(jīng)為msg文件配置過了依賴包,所以package.xml不需要做修改。
2.CMakeLists.txt 文件
如果是新建的功能包,與之前定義msg文件同理,為了將.srv
文件轉(zhuǎn)換成對應(yīng)的C++和Python代碼,還需要在CMakeLists.txt中添加如下配置:
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}?
?"srv/AddInts.srv"
)
不過,我們當前使用的base_interfaces_demo包,那么你只需要修改rosidl_generate_interfaces函數(shù)即可,修改后的內(nèi)容如下:
rosidl_generate_interfaces(${PROJECT_NAME} ?
"msg/Student.msg" ?
"srv/AddInts.srv"?
)
3.編譯
終端中進入當前工作空間,編譯功能包:
colcon build --packages-select base_interfaces_demo
4.測試
編譯完成之后,在工作空間下的 install 目錄下將生成AddInts.srv
文件對應(yīng)的C++和Python文件,我們也可以在終端下進入工作空間,通過如下命令查看文件定義以及編譯是否正常:
. install/setup.bash?
ros2 interface show base_interfaces_demo/srv/AddInts
正常情況下,終端將會輸出與AddInts.srv
文件一致的內(nèi)容。
2.3.3 服務(wù)通信(C++)
1.服務(wù)端實現(xiàn)
功能包cpp02_service的src目錄下,新建C++文件demo01_server.cpp,并編輯文件,輸入如下內(nèi)容:
/* ?
?
? 需求:編寫服務(wù)端,接收客戶端發(fā)送請求,提取其中兩個整型數(shù)據(jù),相加后將結(jié)果響應(yīng)回客戶端。
?
? 步驟:
? ?
? ? ? 1.包含頭文件;
? ?
? ? ? 2.初始化 ROS2 客戶端;
? ?
? ? ? 3.定義節(jié)點類;
? ? ?
? ? ? ? 3-1.創(chuàng)建服務(wù)端;
? ? ?
? ? ? ? 3-2.處理請求數(shù)據(jù)并響應(yīng)結(jié)果。
? ? ? ? 3-3.組織請求數(shù)據(jù)并發(fā)送;
? ?
? ? ? 4.調(diào)用spin函數(shù),并傳入節(jié)點對象指針;
? ?
? ? ? 5.釋放資源。
*/
// 1.包含頭文件
;
using base_interfaces_demo::srv::AddInts;
using std::placeholders::_1;
using std::placeholders::_2;
// 3.定義節(jié)點類;
class MinimalService: public rclcpp::Node{?
?public:
?
?? MinimalService():Node("minimal_service"){ ? ??
? ? ?// 3-1.創(chuàng)建服務(wù)端;
? ??
?? server = this->create_service<AddInts("add_ints",std::bind(&MinimalService::add, this, _1, _2));
? ?RCLCPP_INFO(this->get_logger(),"add_ints 服務(wù)端啟動完畢,等待請求提交...");
? ?} ?
?private:
? ?
? ?rclcpp::Service<AddInts>::SharedPtr server; ? ?
? ?// 3-2.處理請求數(shù)據(jù)并響應(yīng)結(jié)果。
??
??void add(const AddInts::Request::SharedPtr req,const AddInts::Response::SharedPtr res){
? ? ?
? ?res->sum = req->num1 + req->num2;
? ? ?
? ?RCLCPP_INFO(this->get_logger(),"請求數(shù)據(jù):(%d,%d),響應(yīng)結(jié)果:%d", req->num1, req->num2, res->sum);
? ?
? ?}?
};
int main(int argc, char const *argv[])
{ ?
? // 2.初始化 ROS2 客戶端;?
?? rclcpp::init(argc,argv); ?
? // 4.調(diào)用spin函數(shù),并傳入節(jié)點對象指針;?
??auto server = std::make_shared<MinimalService>();?
? ?rclcpp::spin(server); ?
? // 5.釋放資源。
?
? rclcpp::shutdown(); ?
? return 0;?
}
2.客戶端實現(xiàn)
功能包cpp02_service的src目錄下,新建C++文件demo02_client.cpp,并編輯文件,輸入如下內(nèi)容:

3.編輯配置文件
1.packages.xml
在創(chuàng)建功能包時,所依賴的功能包已經(jīng)自動配置了,配置內(nèi)容如下:
<depend>rclcpp</depend>
<depend>base_interfaces_demo</depend>
2.CMakeLists.txt
CMakeLists.txt 中服務(wù)端和客戶端程序核心配置如下:
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(base_interfaces_demo REQUIRED)
add_executable(demo01_server src/demo01_server.cpp)
ament_target_dependencies(
?demo01_server
?"rclcpp"
?"base_interfaces_demo"
)
add_executable(demo02_client src/demo02_client.cpp)
ament_target_dependencies(
?demo02_client
?"rclcpp"
?"base_interfaces_demo"
)
install(TARGETS
?demo01_server
?demo02_client
?DESTINATION lib/${PROJECT_NAME})
4.編譯
終端中進入當前工作空間,編譯功能包:
colcon build --packages-select cpp02_service
5.執(zhí)行
當前工作空間下,啟動兩個終端,終端1執(zhí)行服務(wù)端程序,終端2執(zhí)行客戶端程序。
終端1輸入如下指令:
. install/setup.bash?
ros2 run cpp02_service demo01_server
終端2輸入如下指令:
. install/setup.bash?
ros2 run cpp02_service demo02_client 100 200
最終運行結(jié)果與案例類似。
2.3.4 服務(wù)通信(Python)
1.服務(wù)端實現(xiàn)
功能包py02_service的py02_service目錄下,新建Python文件demo01_server_py.py,并編輯文件,輸入如下內(nèi)容:
""" ?
? ?
? ?需求:編寫服務(wù)端,接收客戶端發(fā)送請求,提取其中兩個整型數(shù)據(jù),相加后將結(jié)果響應(yīng)回客戶端。
? ?
? ?步驟:
? ? ? ?
? ? ? ?1.導(dǎo)包;
? ? ? ?
? ? ? ?2.初始化 ROS2 客戶端;
? ? ? ?
? ? ? ?3.定義節(jié)點類;
? ? ? ? ? ?
? ? ? ? ?3-1.創(chuàng)建服務(wù)端;
? ? ? ? ? ?
? ? ? ? ?3-2.處理請求數(shù)據(jù)并響應(yīng)結(jié)果。
? ? ? ?
? ? ? ? 4.調(diào)用spin函數(shù),并傳入節(jié)點對象;
? ? ? ?
? ? ? ? 5.釋放資源。?
"""
# 1.導(dǎo)包;
import rclpy
from rclpy.node import Node
from base_interfaces_demo.srv import AddInts
# 3.定義節(jié)點類;
class MinimalService(Node):
??
? ?def __init__(self):
? ?
? ? ? super().__init__('minimal_service_py') ? ? ? ?
? ? ? # 3-1.創(chuàng)建服務(wù)端;? ? ? ? ?
? ? ?self.srv = self.create_service(AddInts, 'add_ints', self.add_two_ints_callback)
? ? ? ?
? ? ?self.get_logger().info("服務(wù)端啟動!") ? ?
? ? ?# 3-2.處理請求數(shù)據(jù)并響應(yīng)結(jié)果。
? ?
? ? ?def add_two_ints_callback(self, request, response):? ? ? ? ? ? ?
? ? ?response.sum = request.num1 + request.num2? ? ? ? ? ?
? ? ?self.get_logger().info('請求數(shù)據(jù):(%d,%d),響應(yīng)結(jié)果:%d' % (request.num1, request.num2, response.sum)) ? ? ? ?
? ? ?return response
def main():
? ?
? ? ? # 2.初始化 ROS2 客戶端;
? ?
? ? ? rclpy.init() ? ?
? ? ? # 4.調(diào)用spin函數(shù),并傳入節(jié)點對象;
? ?
? ? ? minimal_service = MinimalService()
? ?
? ? ? rclpy.spin(minimal_service) ? ?
? ? ? # 5.釋放資源。
? ?
? ? ? rclpy.shutdown()
if __name__ == '__main__':
? ?
? main()
2.客戶端實現(xiàn)
功能包py02_service的py02_service目錄下,新建Python文件demo02_client_py.py,并編輯文件,輸入如下內(nèi)容

3.編輯配置文件
1.package.xml
在創(chuàng)建功能包時,所依賴的功能包已經(jīng)自動配置了,配置內(nèi)容如下:
<depend>rclpy</depend>
<depend>base_interfaces_demo</depend>
2.setup.py
entry_points
字段的console_scripts
中添加如下內(nèi)容:
entry_points={?
? ?'console_scripts': [
? ? ? ?
? ? ? 'demo01_server_py = py02_service.'demo01_server_py:main',? ? ? ? ??
? ? ? 'demo02_client_py = py02_service.demo02_client_py:main'
? ?
? ? ],?
},
4.編譯
終端中進入當前工作空間,編譯功能包:
colcon build --packages-select py02_service
5.執(zhí)行
當前工作空間下,啟動兩個終端,終端1執(zhí)行服務(wù)端程序,終端2執(zhí)行客戶端程序。
終端1輸入如下指令:
. install/setup.bash?
ros2 run py02_service demo01_server_py
終端2輸入如下指令:
. install/setup.bash?
ros2 run py02_service demo02_client_py 100 200
最終運行結(jié)果與案例類似。


B站有完整的ros系列教程視頻,可以觀看完整內(nèi)容ros課程ROS2理論與實踐
更多內(nèi)容將在猛獅知識星球社區(qū)更新最新課程,后續(xù)將推出更多優(yōu)質(zhì)內(nèi)容——詳情可關(guān)注猛獅集訓營公眾號和猛獅集訓營官方網(wǎng)站。