1261.29 서비스 서버(Service Server)의 구조

1. 서비스 서버의 정의와 역할

ROS2에서 서비스 서버(Service Server)는 요청-응답 통신 패턴의 응답 측 엔드포인트로서, 클라이언트(Client)로부터 수신된 요청(Request)을 처리하고 응답(Response)을 생성하여 반환하는 통신 개체이다. 서비스 서버는 ROS2 노드(Node) 내부에 생성되며, 특정 서비스 이름과 서비스 타입에 대한 요청 처리 콜백 함수를 등록함으로써 외부 클라이언트로부터의 요청을 수용한다.

서비스 서버는 로봇 시스템에서 매개변수 조회, 구성 변경, 좌표 변환 연산, 모드 전환 등 명확한 입출력 관계를 가진 작업의 제공자(Provider) 역할을 수행한다.

2. rclcpp 기반 서비스 서버 생성

2.1 기본 생성 구조

rclcpp에서 서비스 서버는 rclcpp::Node 클래스의 create_service<>() 템플릿 메서드를 통해 생성된다. 이 메서드는 다음의 매개변수를 요구한다.

  • 서비스 이름(Service Name): 서비스를 식별하는 문자열. 네임스페이스 규칙을 따른다.
  • 콜백 함수(Callback Function): 요청 수신 시 호출되는 처리 함수
  • QoS 프로파일(선택적): 서비스 통신의 QoS 설정
  • 콜백 그룹(선택적): 콜백의 동시 실행 정책을 결정하는 그룹
auto server = this->create_service<example_interfaces::srv::AddTwoInts>(
    "add_two_ints",
    std::bind(&ServerNode::handle_request, this,
              std::placeholders::_1, std::placeholders::_2)
);

생성된 서비스 서버 객체는 rclcpp::Service<ServiceT> 타입의 공유 포인터(Shared Pointer)로 반환되며, 이 포인터가 유지되는 동안 서비스가 활성 상태로 존재한다.

2.2 콜백 함수의 시그니처

서비스 서버의 콜백 함수는 요청 객체와 응답 객체를 매개변수로 전달받는다. rclcpp에서 콜백 함수의 표준 시그니처는 다음과 같다.

void handle_request(
    const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
    std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> response)
{
    response->sum = request->a + request->b;
    RCLCPP_INFO(this->get_logger(), "Request: %ld + %ld = %ld",
                request->a, request->b, response->sum);
}

콜백 함수의 첫 번째 매개변수는 클라이언트로부터 수신된 요청 데이터를 포함하는 상수 공유 포인터이다. 두 번째 매개변수는 응답 데이터를 채워 넣을 가변 공유 포인터이며, 콜백 함수가 반환된 후 이 응답 객체의 내용이 클라이언트에게 전송된다.

2.3 요청 헤더 포함 콜백

ROS2 Humble 이후 버전에서는 요청 헤더(Request Header)를 포함하는 확장 콜백 시그니처를 지원한다.

void handle_request_with_header(
    const std::shared_ptr<rmw_request_id_t> request_header,
    const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
    std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> response)
{
    (void)request_header;
    response->sum = request->a + request->b;
}

요청 헤더에는 클라이언트의 GUID(Globally Unique Identifier)와 시퀀스 번호(Sequence Number)가 포함되어 있으며, 이를 통해 요청의 출처를 식별하거나 로깅 목적으로 활용할 수 있다.

3. rclpy 기반 서비스 서버 생성

Python 클라이언트 라이브러리인 rclpy에서는 Node 클래스의 create_service() 메서드를 사용하여 서비스 서버를 생성한다.

self.srv = self.create_service(
    AddTwoInts,
    'add_two_ints',
    self.handle_request
)

def handle_request(self, request, response):
    response.sum = request.a + request.b
    self.get_logger().info(f'Request: {request.a} + {request.b} = {response.sum}')
    return response

rclpy에서 콜백 함수는 응답 객체를 명시적으로 반환(return)하여야 한다. 이는 rclcpp에서 응답 포인터를 통해 직접 값을 설정하는 방식과의 차이점이다.

4. DDS 계층에서의 서비스 서버 매핑

4.1 내부 토픽 구조

서비스 서버는 DDS 미들웨어 계층에서 두 개의 DDS 엔드포인트로 매핑된다.

  1. 요청 수신용 DataReader: <서비스_이름>/Request 토픽에서 클라이언트의 요청 메시지를 수신한다.
  2. 응답 발행용 DataWriter: <서비스_이름>/Response 토픽을 통해 처리된 응답 메시지를 클라이언트에 전달한다.

이 두 DDS 엔드포인트는 RELIABLE 신뢰성 정책을 기본으로 적용하여 요청과 응답의 전달을 보장한다.

4.2 요청-응답 대응 관리

서비스 서버는 각 요청에 포함된 클라이언트 GUID와 시퀀스 번호를 응답 메시지에 동일하게 포함시킴으로써, 클라이언트 측에서 자신이 보낸 요청에 대한 정확한 응답을 식별할 수 있도록 한다. 이 대응 관리 메커니즘은 ROS2 미들웨어 계층(rmw)에서 자동으로 처리되며, 개발자가 직접 관리할 필요는 없다.

5. 서비스 서버의 콜백 실행 모델

5.1 실행자에 의한 콜백 스케줄링

서비스 서버의 콜백 함수는 토픽 구독자와 마찬가지로 실행자(Executor)에 의해 스케줄링된다. 실행자가 spin() 메서드를 통해 이벤트 루프를 구동하면, DDS DataReader에 도착한 요청 메시지가 감지되어 등록된 콜백 함수가 호출된다.

5.2 콜백 그룹과 동시성

단일 스레드 실행자(SingleThreadedExecutor)를 사용하는 경우, 서비스 콜백은 다른 콜백(토픽 구독자, 타이머 등)과 순차적으로 실행된다. 서비스 콜백의 처리 시간이 길어지면 다른 콜백의 실행이 지연되므로, 계산 집약적인 서비스 처리는 별도의 스레드로 위임하거나 멀티 스레드 실행자를 사용하여야 한다.

멀티 스레드 실행자(MultiThreadedExecutor)를 사용하는 경우, 서비스 콜백을 재진입 가능 콜백 그룹(Reentrant Callback Group)에 할당하면 복수의 요청을 동시에 처리할 수 있다. 다만, 이 경우 공유 자원에 대한 동기화 처리가 필수적이다.

6. 서비스 서버의 생명주기

6.1 서비스 등록과 검색

서비스 서버가 생성되면 DDS 검색(Discovery) 프로토콜을 통해 해당 서비스의 존재가 네트워크의 참여자에게 공지된다. ros2 service list 명령을 통해 현재 활성화된 서비스 목록을 확인할 수 있으며, ros2 service type <서비스_이름> 명령으로 특정 서비스의 인터페이스 타입을 조회할 수 있다.

6.2 서비스 서버의 소멸

서비스 서버 객체가 소멸하면 DDS 계층의 DataReaderDataWriter가 함께 해제되며, 해당 서비스에 대한 요청 수용이 중단된다. 서비스 서버 소멸 시점에 처리 중인 요청이 존재하면, 해당 요청에 대한 응답이 전달되지 않을 수 있다.

6.3 관리형 노드에서의 서비스 서버

관리형 노드(Lifecycle Node)에서는 노드의 상태 전이에 따라 서비스 서버의 생성과 소멸을 제어할 수 있다. on_configure() 단계에서 서비스 서버를 생성하고, on_cleanup() 단계에서 소멸시키는 패턴이 일반적이다. 이를 통해 노드가 활성(Active) 상태에 있을 때만 서비스 요청을 수용하는 구조를 구현할 수 있다.

7. 서비스 서버 구현 시 고려사항

7.1 콜백 처리 시간의 제한

서비스 콜백 내에서의 처리 시간이 과도하게 길어지면 클라이언트 측에서 타임아웃이 발생할 수 있으며, 단일 스레드 실행자 환경에서는 다른 콜백의 실행을 차단한다. 장시간 처리가 필요한 작업에는 서비스 대신 액션(Action) 통신의 사용을 고려하여야 한다.

7.2 예외 처리와 오류 반환

서비스 콜백 내에서 예외가 발생하면 해당 요청에 대한 응답이 정상적으로 반환되지 않을 수 있다. 따라서, 콜백 내부에서 적절한 예외 처리를 수행하고, 오류 상황을 응답 메시지의 필드를 통해 클라이언트에게 전달하는 설계 패턴이 권장된다. std_srvs/srv/Trigger 서비스 타입은 success(부울)와 message(문자열) 필드를 통해 이러한 오류 보고 패턴을 표준화한 예시이다.

7.3 서비스 이름의 명명 규칙

서비스 이름은 ROS2의 명명 규칙을 따르며, 소문자와 밑줄(Underscore)을 사용하여 서비스의 기능을 명확히 표현하여야 한다. 네임스페이스를 활용하면 동일 기능의 서비스를 다중 로봇 환경에서 구분할 수 있다. 예를 들어, /robot1/get_pose/robot2/get_pose는 각각 별개의 로봇에 대한 위치 조회 서비스로 동작한다.

8. 참고 문헌

  • Open Robotics, “ROS 2 Documentation: Humble Hawksbill,” https://docs.ros.org/en/humble/, 2022.
  • OMG, “Data Distribution Service (DDS) Specification, Version 1.4,” Object Management Group, 2015.
  • Open Robotics, “rclcpp API Reference,” https://docs.ros2.org/humble/api/rclcpp/, 2022.
  • Maruyama, Y., Kato, S., Azumi, T., “Exploring the Performance of ROS2,” Proceedings of the 13th International Conference on Embedded Software (EMSOFT), 2016.