ROS 환경에서 분산 노드 간 데이터 교환은 일반적으로 토픽(Topic)을 사용하지만, 실시간성이 요구되거나 명확한 논리적 처리 흐름이 필요한 경우 Service 통신이 적합합니다. Service는 클라이언트가 요청(Request)을 전송하면 서버가 해당 작업을 처리한 후 응답(Response)을 반환하는 동기식 패턴을 따릅니다.
서버(Server) 구현
서버 측에서는 서비스 타입을 정의한 메시지 파일(.srv)을 기반으로 콜백 함수를 등록하고, 요청을 수신할 대기 구조를 구축해야 합니다. 구현 단계는 메시지 패키지 임포트, 노드 핸들 초기화, 서비스 객체 등록, 요청 처리 함수 정의로 나뉩니다.
C++ 기반 서버 구현
#include <ros/ros.h>
#include <basic_math/AddValues.h>
bool handle_calculation(basic_math::AddValues::Request &req,
basic_math::AddValues::Response &res) {
int val_a = req.first_val;
int val_b = req.second_val;
ROS_INFO("Received inputs: A=%d, B=%d", val_a, val_b);
res.total = val_a + val_b;
ROS_INFO("Computed result: %d", res.total);
return true;
}
int main(int argc, char **argv) {
setlocale(LC_ALL, "");
ros::init(argc, argv, "calc_server");
ros::NodeHandle nh;
ros::ServiceServer srv_handle = nh.advertiseService("add_values", handle_calculation);
ROS_INFO("Calculation service is active and waiting for requests...");
ros::spin();
return 0;
}
Python 기반 서버 구현
#!/usr/bin/env python3
import rospy
from basic_math.srv import AddValues, AddValuesResponse
def process_addition(req):
first = req.first_val
second = req.second_val
total = first + second
rospy.loginfo("Processing inputs: %d + %d", first, second)
return AddValuesResponse(total)
if __name__ == "__main__":
rospy.init_node("math_service_provider")
service = rospy.Service("add_values", AddValues, process_addition)
rospy.loginfo("Service node initialized.")
rospy.spin()
클라이언트(Client) 구현
클라이언트는 노드를 초기화한 후 서비스 프록시 객체를 생성하고, 요청 데이터를 설정하여 호출(Call)합니다. 실행 인자를 통해 매개변수를 동적으로 전달하도록 구조를 최적화하면 재사용성이 크게 향상됩니다.
C++ 기반 클라이언트 구현
#include <ros/ros.h>
#include <basic_math/AddValues.h>
#include <cstdlib>
int main(int argc, char **argv) {
setlocale(LC_ALL, "");
if (argc != 3) {
ROS_ERROR("Usage: rosrun pkg_name node_name <val1> <val2>");
return -1;
}
ros::init(argc, argv, "calc_client");
ros::NodeHandle nh;
ros::ServiceClient proxy = nh.serviceClient<basic_math::AddValues>("add_values");
if (!proxy.exists()) {
ROS_INFO("Waiting for service to become available...");
proxy.waitForExistence();
}
basic_math::AddValues calc_req;
calc_req.request.first_val = std::atoi(argv[1]);
calc_req.request.second_val = std::atoi(argv[2]);
if (proxy.call(calc_req)) {
ROS_INFO("Result received: %d", calc_req.response.total);
} else {
ROS_ERROR("Failed to call service add_values");
}
return 0;
}
Python 기반 클라이언트 구현
#!/usr/bin/env python3
import rospy
import sys
from basic_math.srv import AddValues
if __name__ == "__main__":
if len(sys.argv) != 3:
rospy.logerr("Usage: python script.py <val1> <val2>")
sys.exit(1)
rospy.init_node("math_service_consumer")
proxy = rospy.ServiceProxy("add_values", AddValues)
rospy.loginfo("Waiting for service...")
proxy.wait_for_service()
result = proxy(int(sys.argv[1]), int(sys.argv[2]))
rospy.loginfo("Calculation complete: %d", result.total)
서버 대기 메커니즘
네트워크 환경이나 노드 기동 순서에 따라 서비스 등록이 지연될 수 있습니다. 이 경우 클라이언트 호출 직전에 명시적으로 대기 상태를 설정하면 안정성이 확보됩니다.
- C++:
client.waitForExistence();또는 전역 유틸리티ros::service::waitForService("service_name"); - Python:
client.wait_for_service()또는 전역 유틸리티rospy.wait_for_service("service_name")
명령줄 서비스 도구 (rosservice)
ROS 시스템에서 활성화된 서비스의 상태 확인 및 직접 호출은 rosservice 명령어 시리즈를 통해 효율적으로 수행할 수 있습니다.
| 명령어 | 설명 |
|---|---|
rosservice list |
현재 활성 상태인 모든 서비스 이름 출력 |
rosservice info <service> |
특정 서비스의 노드, URI, 유형 정보 조회 |
rosservice type <service> |
서비스가 사용하는 메시지 타입(.srv) 출력 |
rosservice args <service> |
요청에 필요한 인자 순서 및 구조 확인 |
rosservice call <service> <args> |
터미널에서 직접 인자를 전달하여 서비스 호출 및 응답 수신 |
rosservice find <type> |
특정 메시지 타입을 사용하는 서비스 노드 검색 |