1261.25 ROS2 토픽의 구독자(Subscriber) 구조
1. 구독자의 정의와 역할
ROS2(Robot Operating System 2)에서 구독자(Subscriber)는 발행-구독(Publish-Subscribe) 통신 패러다임의 수신 측 엔드포인트로서, 특정 토픽(Topic)에 발행된 메시지를 수신하고 처리하는 통신 개체이다. 구독자는 ROS2 노드(Node) 내부에 생성되며, 지정된 토픽 이름과 메시지 타입에 부합하는 데이터가 발행될 때 등록된 콜백 함수(Callback Function)를 통해 해당 데이터를 비동기적으로 수신한다.
발행-구독 패턴에서 구독자는 발행자(Publisher)와 직접적인 연결을 맺지 않으며, 토픽이라는 논리적 채널을 매개로 간접적으로 데이터를 전달받는다. 이러한 구조적 분리(Decoupling)는 시스템의 모듈성과 확장성을 보장하며, 발행자와 구독자 간의 공간적·시간적 독립성을 확보하는 핵심 메커니즘이다.
2. 구독자의 내부 아키텍처
2.1 rclcpp 기반 구독자 생성 구조
ROS2의 C++ 클라이언트 라이브러리인 rclcpp에서 구독자는 rclcpp::Node 클래스의 create_subscription<>() 템플릿 메서드를 통해 생성된다. 이 메서드는 다음의 주요 매개변수를 요구한다.
- 토픽 이름(Topic Name): 구독 대상이 되는 토픽의 문자열 식별자
- QoS 프로파일(Quality of Service Profile): 통신 신뢰성, 내구성(Durability), 이력(History) 등의 정책을 규정하는 설정 객체
- 콜백 함수(Callback Function): 메시지 수신 시 호출되는 처리 함수
생성된 구독자 객체는 rclcpp::Subscription<MessageT> 타입의 공유 포인터(Shared Pointer)로 반환되며, 노드의 생명주기 동안 유지되어야 한다. 구독자 객체가 소멸하면 해당 토픽에 대한 구독이 자동으로 해제된다.
auto subscription = this->create_subscription<std_msgs::msg::String>(
"topic_name",
rclcpp::QoS(10),
std::bind(&ClassName::callback, this, std::placeholders::_1)
);
2.2 rclpy 기반 구독자 생성 구조
Python 클라이언트 라이브러리인 rclpy에서는 Node 클래스의 create_subscription() 메서드를 사용하여 구독자를 생성한다. 기본적인 매개변수 구조는 rclcpp와 동일하나, Python의 동적 타이핑 특성에 따라 템플릿 매개변수 대신 메시지 클래스 객체를 직접 전달한다.
self.subscription = self.create_subscription(
String,
'topic_name',
self.callback,
10
)
3. 콜백 메커니즘과 메시지 수신 처리
3.1 콜백 함수의 구조
구독자의 콜백 함수는 수신된 메시지 객체를 매개변수로 전달받으며, 해당 메시지에 대한 처리 로직을 포함한다. rclcpp에서 콜백 함수의 시그니처는 다음과 같은 형태를 취한다.
void callback(const std_msgs::msg::String::SharedPtr msg);
콜백 함수는 메시지의 불변 참조(Const Reference) 또는 공유 포인터(SharedPtr)를 통해 데이터를 수신한다. ROS2 Humble 이후 버전에서는 const MessageT & 형태의 상수 참조를 통한 수신도 지원하여, 불필요한 포인터 간접 참조를 제거할 수 있다.
3.2 콜백 호출 시점과 실행자의 관계
구독자의 콜백 함수는 자동으로 호출되지 않으며, 실행자(Executor)가 spin() 또는 spin_some() 등의 메서드를 통해 이벤트 루프를 구동할 때 비로소 실행된다. 실행자는 DDS(Data Distribution Service) 미들웨어로부터 메시지 수신 이벤트를 감지하고, 해당 구독자의 콜백을 스케줄링한다.
단일 스레드 실행자(SingleThreadedExecutor)를 사용하는 경우, 콜백은 순차적으로 실행되며 동시에 두 개 이상의 콜백이 병렬로 수행되지 않는다. 멀티 스레드 실행자(MultiThreadedExecutor)를 사용하면 복수의 콜백을 병렬로 실행할 수 있으나, 이 경우 공유 자원에 대한 동기화 문제를 별도로 처리하여야 한다.
3.3 콜백 그룹과 동시성 제어
ROS2는 콜백 그룹(Callback Group)을 통해 콜백의 동시 실행 여부를 세밀하게 제어한다. 콜백 그룹은 두 가지 유형으로 구분된다.
- 상호 배타적 콜백 그룹(Mutually Exclusive Callback Group): 동일 그룹 내의 콜백은 동시에 실행되지 않으며, 하나의 콜백이 완료된 후에 다음 콜백이 실행된다.
- 재진입 가능 콜백 그룹(Reentrant Callback Group): 동일 그룹 내의 콜백이 동시에 실행될 수 있으며, 멀티 스레드 실행자와 결합하여 병렬 처리를 구현한다.
구독자 생성 시 create_subscription() 메서드의 선택적 매개변수로 콜백 그룹을 지정할 수 있으며, 지정하지 않은 경우 노드의 기본 콜백 그룹에 할당된다.
4. 메시지 수신 큐와 QoS 정책
4.1 수신 큐의 구조
구독자는 내부적으로 수신 큐(Receive Queue)를 유지하며, DDS 미들웨어를 통해 도착한 메시지를 임시 저장한다. 이 큐의 크기와 동작 방식은 QoS 이력(History) 정책에 의해 결정된다.
| QoS 이력 정책 | 동작 방식 |
|---|---|
KEEP_LAST | 지정된 깊이(Depth)만큼의 최근 메시지를 유지하며, 큐가 가득 찬 경우 가장 오래된 메시지를 폐기한다 |
KEEP_ALL | 수신된 모든 메시지를 유지하며, 시스템 자원이 허용하는 범위 내에서 큐를 확장한다 |
4.2 QoS 호환성과 구독자 설정
구독자의 QoS 프로파일은 발행자의 QoS 프로파일과 호환되어야 메시지 수신이 가능하다. 주요 QoS 정책별 호환성 규칙은 다음과 같다.
- 신뢰성(Reliability): 발행자가
BEST_EFFORT로 설정된 경우, 구독자는BEST_EFFORT로 설정하여야 한다. 발행자가RELIABLE로 설정된 경우, 구독자는RELIABLE또는BEST_EFFORT중 어느 것이든 사용할 수 있다. - 내구성(Durability): 발행자가
TRANSIENT_LOCAL로 설정된 경우, 구독자도TRANSIENT_LOCAL로 설정하면 구독 이전에 발행된 메시지를 수신할 수 있다. 발행자가VOLATILE로 설정된 경우, 구독자의TRANSIENT_LOCAL설정은 효과가 없다.
QoS 불일치가 발생하면 ROS2는 경고 로그를 출력하며, 해당 발행자-구독자 쌍 간의 통신이 성립하지 않는다.
5. DDS 계층에서의 구독자 매핑
5.1 DataReader와의 대응 관계
ROS2 구독자는 DDS 미들웨어 계층에서 DataReader 개체에 대응된다. DDS의 DataReader는 Subscriber 엔티티 내에 포함되며, 특정 Topic과 연결되어 해당 토픽의 데이터 샘플을 수신한다. ROS2는 이 DDS DataReader를 추상화하여 사용자에게 단순화된 인터페이스를 제공한다.
DDS 계층에서의 구독자 생성 과정은 다음과 같은 단계를 거친다.
DomainParticipant가 생성되거나 기존 참여자가 재사용된다.Subscriber엔티티가 생성된다.Topic이 등록되거나 검색된다.- QoS 정책이 적용된
DataReader가 생성된다. DataReader에 리스너(Listener) 또는 조건(Condition)이 등록되어 데이터 수신을 감지한다.
5.2 토픽 검색과 발행자 발견
DDS 미들웨어는 RTPS(Real-Time Publish-Subscribe) 프로토콜의 검색(Discovery) 메커니즘을 활용하여 동일 토픽을 발행하는 노드를 자동으로 탐지한다. 이 과정은 SPDP(Simple Participant Discovery Protocol)와 SEDP(Simple Endpoint Discovery Protocol)를 통해 수행되며, 구독자는 별도의 명시적 연결 설정 없이 발행자를 발견하고 데이터 수신을 개시한다.
6. 구독자의 생명주기 관리
6.1 구독 활성화와 비활성화
ROS2의 관리형 노드(Managed Node, Lifecycle Node)에서는 노드의 상태 전이에 따라 구독자의 활성화 상태를 제어할 수 있다. on_activate() 콜백에서 구독자를 활성화하고, on_deactivate() 콜백에서 구독자를 비활성화하는 방식으로, 노드의 운용 상태에 따른 선택적 메시지 수신이 가능하다.
6.2 구독자 소멸과 자원 해제
구독자 객체가 소멸하면 DDS 계층의 DataReader가 함께 소멸되며, 해당 토픽에 대한 구독이 해제된다. rclcpp에서는 공유 포인터의 참조 카운트가 0이 되는 시점에 자동으로 소멸이 수행되며, rclpy에서는 Python의 가비지 컬렉터(Garbage Collector)에 의해 자원이 회수된다.
7. 다중 구독자와 토픽 다중화
하나의 노드 내에 동일 토픽에 대한 복수의 구독자를 생성할 수 있으며, 각 구독자는 독립적인 콜백 함수를 보유한다. 또한, 하나의 구독자가 하나의 토픽만을 구독할 수 있으므로, 복수의 토픽을 수신하려면 토픽 수만큼의 구독자를 생성하여야 한다.
동일 토픽에 대해 복수의 노드가 각각 구독자를 생성하는 경우, 발행된 메시지는 모든 구독자에게 독립적으로 전달된다. 이는 DDS의 일대다(One-to-Many) 데이터 분배 모델에 기반한 동작이다.
8. 구독자 구현 시 고려사항
8.1 콜백 처리 시간과 시스템 응답성
콜백 함수 내부의 처리 시간이 과도하게 길어지면 후속 메시지의 수신 및 처리가 지연되어 시스템 전체의 응답성이 저하된다. 따라서, 계산 집약적인 작업은 별도의 스레드로 분리하거나, 콜백 내에서는 데이터의 버퍼링만 수행하고 실제 처리는 비동기적으로 수행하는 설계 패턴이 권장된다.
8.2 메시지 필터링과 선택적 수신
ROS2는 message_filters 패키지를 통해 복수의 토픽으로부터 수신된 메시지를 시간 동기화(Time Synchronizer)하거나, 특정 조건에 부합하는 메시지만을 선별하여 콜백에 전달하는 필터링 메커니즘을 제공한다. 이는 다중 센서 데이터의 융합 처리에서 필수적인 기능이다.
8.3 직렬화 성능과 제로 복사 전송
ROS2 Iron 이후 버전에서는 동일 프로세스 내의 발행자-구독자 간에 제로 복사(Zero-Copy) 전송을 지원하며, 이를 통해 대용량 메시지(예: 이미지, 포인트 클라우드)의 전송 시 직렬화·역직렬화 오버헤드를 제거할 수 있다. 이 기능을 활용하기 위해서는 rclcpp::LoanedMessage와 호환 DDS 구현체의 공유 메모리(Shared Memory) 전송 기능이 요구된다.
9. 참고 문헌
- Open Robotics, “ROS 2 Documentation: Humble Hawksbill,” https://docs.ros.org/en/humble/, 2022.
- Maruyama, Y., Kato, S., Azumi, T., “Exploring the Performance of ROS2,” Proceedings of the 13th International Conference on Embedded Software (EMSOFT), 2016.
- OMG, “Data Distribution Service (DDS) Specification, Version 1.4,” Object Management Group, 2015.
- Pardo-Castellote, G., “OMG Data Distribution Service: Architectural Overview,” Proceedings of the 23rd International Conference on Distributed Computing Systems Workshops, 2003.