1. 이론
1.8DDS, QoS, ROS_DOMAIN_ID
DDS와 RMW: ROS2 통신의 바닥
ROS2의 노드들이 서로를 찾고 메시지를 주고받는 일은 DDS(Data Distribution Service) 라는 산업 표준 통신 미들웨어가 맡습니다. 우리가 create_publisher를 호출하면 그 밑에서 DDS가 네트워크 통신을 처리합니다.
DDS는 한 가지 구현만 있는 것이 아니라 여러 업체와 오픈소스 구현이 있습니다. ROS2는 어떤 구현이든 쓸 수 있도록 중간에 RMW(ROS MiddleWare interface) 라는 추상 계층을 둡니다.
층 구조는 아래와 같습니다.
| 계층 | 역할 |
|---|---|
| 내 노드 코드 | rclpy(Python), rclcpp(C++) 로 작성한 코드 |
| rcl | 언어와 상관없이 공통으로 쓰는 ROS 클라이언트 라이브러리 |
| RMW 인터페이스 | DDS 구현이 달라도 위쪽 코드가 바뀌지 않게 하는 추상 계층 |
| DDS 구현 | 실제로 디스커버리와 네트워크 통신을 수행 |
- Humble의 기본 RMW는 eProsima Fast DDS (
rmw_fastrtps_cpp) 입니다. Cyclone DDS 같은 다른 구현으로 바꿔 쓸 수도 있습니다. - RMW를 바꿀 때는 환경 변수
RMW_IMPLEMENTATION을 씁니다. 예:export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp(해당 RMW 패키지가 설치되어 있어야 합니다). - 서로 통신해야 하는 노드는 보통 같은 RMW를 쓰는 것이 안전합니다. 다른 DDS 구현끼리도 표준 프로토콜로 통신할 수는 있지만 QoS 해석이나 일부 기능 차이로 문제가 생길 수 있습니다.
echo $RMW_IMPLEMENTATION # 비어 있으면 기본값(Humble은 Fast DDS)
ros2 doctor --report # 사용 중인 RMW 등 환경 정보 확인
디스커버리: 노드가 서로를 찾는 방법
ROS2에는 중앙 서버(ROS1의 roscore)가 없습니다. 노드가 시작되면 DDS가 같은 도메인 안의 다른 참여자(participant)를 자동으로 찾아서 연결합니다. 기본 구성에서는 네트워크에 발견용 메시지를 멀티캐스트로 알립니다. 서로를 발견하면 토픽 이름, 타입, QoS 정보를 교환하고 호환되면 연결합니다.
- 같은 네트워크의 PC와 로봇이 별도 설정 없이 서로의 노드를 보는 것은 이 덕분입니다.
- 반대로 같은 네트워크를 쓰는 다른 사람의 노드도 보일 수 있다는 뜻입니다. 이 범위를 나누는 장치가 다음에 볼
ROS_DOMAIN_ID입니다. - 가상머신에서는 네트워크 모드에 따라 멀티캐스트가 전달되지 않아 발견이 안 되기도 합니다. 3부(개발환경)에서 다룹니다.
ROS_DOMAIN_ID: 논리적인 네트워크 나누기
도메인 ID는 같은 물리 네트워크 위에 여러 개의 논리적인 ROS2 네트워크를 만드는 번호입니다.
- 같은 도메인 ID의 노드끼리만 서로 발견하고 통신합니다. 다른 도메인이면 서로 보이지 않습니다.
- 기본값은 0 입니다.
- 교실처럼 한 네트워크에서 여러 조가 동시에 실습하면, 모두 0이라 서로의 토픽이 섞입니다. 조마다 다른 번호를 씁니다.
export ROS_DOMAIN_ID=7 # 현재 터미널에만 적용
echo "export ROS_DOMAIN_ID=7" >> ~/.bashrc # 새 터미널에도 계속 적용
어떤 값을 써야 하나
공식 문서의 결론은 간단합니다. 0부터 101까지(양 끝 포함)의 값을 고르면 안전합니다. 이유는 다음과 같습니다.
- DDS는 도메인 ID로 UDP 포트 번호를 계산합니다. 발견용 멀티캐스트 포트는
7400 + 250 × 도메인ID입니다. - UDP 포트는 16비트(최대 65535)이므로 계산상 도메인 ID의 최댓값은 232입니다. 즉 이론상 범위는 0~232입니다.
- 그런데 운영체제는 다른 프로그램의 임시 연결용으로 임시 포트(ephemeral port) 범위를 따로 씁니다. 리눅스 기본값은 32768~60999여서 이 범위와 겹치지 않는 도메인 ID는 0~101, 215~232입니다. macOS와 Windows는 기본 49152~65535라서 0~166입니다.
- 세 OS에서 모두 안전한 값은 0~101이므로 이것을 권장합니다.
또 하나, 프로세스 하나마다 DDS 참여자가 하나씩 만들어지고 참여자마다 포트를 2개씩 씁니다. 한 컴퓨터에서 ROS2 프로세스를 120개 이상 띄우면 옆 도메인의 포트와 겹칠 수 있습니다. 교육 환경에서는 거의 문제되지 않지만 알아 두세요.
해보기: 누가 누구를 보나
장치마다 도메인 ID를 바꿔 보세요. 예시 설정 버튼으로 조별 분리 상황을 만들어 볼 수 있습니다.
내 PC (VM)
실행 중인 노드: talker
안전한 범위(0~101)입니다. 발견용 멀티캐스트 포트: 7400
옆자리 PC (VM)
실행 중인 노드: listener
안전한 범위(0~101)입니다. 발견용 멀티캐스트 포트: 7400
로봇 컴퓨터
실행 중인 노드: base_driver
안전한 범위(0~101)입니다. 발견용 멀티캐스트 포트: 7400
다른 조 PC (VM)
실행 중인 노드: listener
안전한 범위(0~101)입니다. 발견용 멀티캐스트 포트: 7400
누가 누구를 발견하나
| 내 PC (VM) | 옆자리 PC (VM) | 로봇 컴퓨터 | 다른 조 PC (VM) | |
|---|---|---|---|---|
| 내 PC (VM) | - | 발견 | 발견 | 발견 |
| 옆자리 PC (VM) | 발견 | - | 발견 | 발견 |
| 로봇 컴퓨터 | 발견 | 발견 | - | 발견 |
| 다른 조 PC (VM) | 발견 | 발견 | 발견 | - |
- 도메인 0: 내 PC (VM)(talker), 옆자리 PC (VM)(listener), 로봇 컴퓨터(base_driver), 다른 조 PC (VM)(listener) - 서로 발견하고 통신합니다.
모든 장치가 같은 네트워크에 연결되어 있다고 가정합니다. 가상머신의 네트워크 설정에 따라 실제로는 연결 자체가 안 될 수도 있습니다(3부에서 다룹니다).
직접 해 보기
- 모두 0으로 두고 "다른 조 PC"의
listener가 우리 조talker메시지까지 받는지 확인하세요. - 우리 조는 7, 다른 조는 8로 나누면 표가 어떻게 바뀌나요?
- 도메인 ID에 150, 300을 입력하면 각각 어떤 안내가 나오나요?
ROS_LOCALHOST_ONLY=1은 해당 장치를 같은 컴퓨터 안에서만 통신하게 합니다 (Humble). 도메인이 같아도 다른 장치와 서로 보이지 않는 것을 확인하세요.
QoS: 통신 품질을 정하는 설정
QoS(Quality of Service) 는 퍼블리셔와 서브스크라이버의 통신 방식을 정하는 설정 모음입니다. "얼마나 믿을 수 있게, 얼마나 최신으로" 주고받을지를 여기서 정합니다. ROS2는 설정을 잘 고르면 TCP처럼 신뢰성 있게도, UDP처럼 가볍게 최선만 다해서도 쓸 수 있습니다. 여러 정책이 모여 하나의 프로파일을 이룹니다.
주요 정책
| 정책 | 선택지 | 의미 |
|---|---|---|
| History | Keep last / Keep all | 보관할 메시지를 최근 N개로 할지, 모두 보관할지 (미들웨어의 자원 한도 안에서) |
| Depth | 정수 | Keep last일 때 큐 크기. Keep all이면 쓰이지 않음 |
| Reliability | Reliable / Best effort | Reliable은 전달을 보장하려고 필요하면 재전송, Best effort는 최선만 다하고 잃어버릴 수 있음 |
| Durability | Volatile / Transient local | Transient local은 퍼블리셔가 메시지를 보관했다가 늦게 참여한 구독자에게 전달, Volatile은 보관 안 함 |
| Deadline | 시간 | 메시지가 올라와야 하는 최대 간격 |
| Lifespan | 시간 | 발행 후 이 시간이 지난 메시지는 만료되어 조용히 버려짐 |
| Liveliness, Lease duration | Automatic / Manual by topic, 시간 | 퍼블리셔가 살아 있는지 확인하는 방식과 제한 시간 |
- ROS1의 큐 크기와 비슷한 것이 History + Depth입니다.
- Durability의 Transient local과 어떤 depth의 조합은 ROS1의 "latch"와 비슷한 기능입니다. 지도(
/map)처럼 한 번 발행하고 나중에 접속한 노드도 받아야 하는 데이터에 씁니다.
자주 쓰는 프로파일
Humble의 기본 프로파일은 아래와 같습니다 (rmw/qos_profiles.h 기준).
| 프로파일 | History / Depth | Reliability | Durability | 용도 |
|---|---|---|---|---|
| 기본 (default) | Keep last / 10 | Reliable | Volatile | 일반 토픽. create_publisher(..., 10) 의 10이 depth |
| 센서 데이터 (sensor data) | Keep last / 5 | Best effort | Volatile | 최신 값이 중요하고 일부 손실은 괜찮은 센서 스트림 |
| 서비스 (services default) | Keep last / 10 | Reliable | Volatile | 서비스 요청과 응답 |
| 파라미터 (parameters) | Keep last / 1000 | Reliable | Volatile | 파라미터 서비스. 큐가 커서 요청이 쉽게 유실되지 않음 |
| 시스템 기본 (system default) | RMW 구현의 기본값 | RMW마다 다를 수 있음 |
기본 프로파일에서 Deadline, Lifespan, Lease duration은 모두 "기본값(제한 없음)"이고 Liveliness는 시스템 기본입니다. 래치 용도의 transient local 조합은 위 목록의 내장 프로파일에 없습니다. 필요할 때 직접 지정하는 설정입니다.
서비스는 특히 Volatile이어야 합니다. 서버가 재시작할 때 오래된 요청을 받아 부작용이 생기는 것을 막기 위해서입니다.
QoS 호환성: 요청과 제공
QoS는 퍼블리셔와 서브스크라이버가 각자 정합니다. 그리고 둘의 설정이 호환되어야만 연결됩니다.
- 서브스크라이버는 "이 정도 품질은 받아야 한다"는 최소 품질을 요청합니다.
- 퍼블리셔는 "이 정도 품질까지 줄 수 있다"는 최대 품질을 제공합니다.
- 모든 정책에서 요청이 제공보다 까다롭지 않아야 연결됩니다. 하나라도 어긋나면 연결되지 않고 메시지가 전달되지 않습니다.
가장 자주 만나는 두 규칙입니다.
| 퍼블리셔 | 서브스크라이버 | Reliability |
|---|---|---|
| Best effort | Best effort | 호환 |
| Best effort | Reliable | 비호환 |
| Reliable | Best effort | 호환 (best effort로 받음) |
| Reliable | Reliable | 호환 |
| 퍼블리셔 | 서브스크라이버 | Durability | 결과 |
|---|---|---|---|
| Volatile | Volatile | 호환 | 새 메시지만 |
| Volatile | Transient local | 비호환 | 통신 없음 |
| Transient local | Volatile | 호환 | 새 메시지만 |
| Transient local | Transient local | 호환 | 새 메시지와 이전 메시지 |
Deadline과 Lease duration은 퍼블리셔가 지정한 값 x에 대해 서브스크라이버의 값이 x 이상이면 호환입니다. 퍼블리셔는 기본값인데 서브스크라이버만 값을 지정하면 비호환입니다. Liveliness는 퍼블리셔가 Automatic인데 서브스크라이버가 Manual by topic이면 비호환입니다. 이 규칙은 서비스 서버와 클라이언트에도 똑같이 적용됩니다.
해보기: QoS 호환성 검사기
퍼블리셔와 서브스크라이버의 QoS를 고르면 호환 여부와 이유를 알려 줍니다. 처음 설정은 위의 함정(센서 퍼블리셔 + 기본 구독자)입니다.
| 정책 | 퍼블리셔 | 서브스크라이버 | 결과 |
|---|---|---|---|
| Reliability | Best effort | Reliable | 비호환 · 서브스크라이버는 모든 메시지의 전달 보장(reliable)을 요청했지만, 퍼블리셔는 best effort만 제공합니다. |
| Durability | Volatile | Volatile | 호환 · 새 메시지만 주고받습니다. |
| Deadline | 기본(제한 없음) | 기본(제한 없음) | 호환 · 둘 다 기본값이라 deadline 조건이 없습니다. |
| Liveliness | Automatic | Automatic | 호환 · 요청한 수준을 제공할 수 있습니다. |
| History / Depth | Keep last 5 | Keep last 10 | 호환 · 큐 크기 설정은 호환성에 영향을 주지 않습니다. 각자 자기 쪽 버퍼에만 적용됩니다. |
모든 정책이 호환되어야 연결됩니다. 이 도구는 문서의 규칙을 계산하는 교육용 모형이며, 실제 연결 여부는 사용하는 RMW 구현에서도 확인해야 합니다.
직접 해 보기
- 서브스크라이버의 Reliability를 Best effort로 바꿔 연결되게 만들어 보세요.
- 퍼블리셔 Volatile, 서브스크라이버 Transient local 조합을 만들어 비호환이 되는 이유를 읽어 보세요.
- 지도처럼 늦게 접속한 노드도 마지막 메시지를 받으려면 양쪽 모두 무엇이어야 하나요? ("래치 스타일" 버튼을 양쪽에 눌러 보세요.)
- Depth를 둘이 다르게 바꿔도 결과가 변하지 않는 것을 확인하세요.
터미널과 코드에서의 QoS (참고)
아래는 개발환경 구축 후 사용합니다. 이 페이지에서 실행되지 않습니다.
ros2 topic info /scan --verbose # 퍼블리셔/서브스크라이버별 QoS 확인
ros2 topic echo /scan --qos-reliability best_effort # 구독 QoS 지정
rclpy에서 QoS를 지정하는 방법입니다. 지금까지 쓴 숫자 10은 QoS 프로파일 대신 쓴 depth 단축 표기로, 기본 프로파일에 depth만 바꾼 것입니다.
from rclpy.qos import QoSProfile, ReliabilityPolicy, DurabilityPolicy, qos_profile_sensor_data
# 센서 데이터용 내장 프로파일
sub = self.create_subscription(LaserScan, 'scan', self.on_scan, qos_profile_sensor_data)
# 직접 지정
qos = QoSProfile(
depth=1,
reliability=ReliabilityPolicy.RELIABLE,
durability=DurabilityPolicy.TRANSIENT_LOCAL, # 래치 스타일
)
pub = self.create_publisher(OccupancyGrid, 'map', qos)
한눈에 정리
- ROS2의 통신은 DDS가 맡고 RMW가 구현을 바꿔 끼울 수 있게 해 준다. Humble의 기본은 Fast DDS다.
- 노드는 중앙 서버 없이 디스커버리로 서로를 찾는다.
ROS_DOMAIN_ID(기본 0)가 같은 노드끼리만 통신한다. 안전한 값은 0~101이다.- QoS 정책의 핵심은 History/Depth, Reliability, Durability이고 Deadline, Lifespan, Liveliness도 있다.
- 서브스크라이버의 요청이 퍼블리셔의 제공보다 까다로우면 연결되지 않는다. 특히 Best effort 퍼블리셔와 Reliable 구독자, Volatile 퍼블리셔와 Transient local 구독자는 비호환이다.
- 센서 토픽이 안 오면 QoS와 도메인 ID를 먼저 확인한다.
확인 문제
Q1. 어떤 센서 노드가 센서 데이터 프로파일(Best effort)로 발행합니다. 내 노드가 기본 프로파일(Reliable)로 구독하면 어떻게 되나요? 어떻게 고치나요?
Best effort 퍼블리셔와 Reliable 구독자는 비호환이라 연결되지 않고 메시지가 오지 않습니다. 구독 쪽을 Best effort로 바꾸거나(qos_profile_sensor_data 사용) 퍼블리셔를 Reliable로 바꾸면 됩니다.
Q2. 한 교실에서 여러 조가 같은 Wi-Fi로 실습하는데 서로의 토픽이 섞입니다. 어떻게 해결하나요? 어떤 값을 써야 하나요?
조마다 다른 ROS_DOMAIN_ID를 설정합니다. 기본값이 모두 0이라서 섞입니다. 안전한 범위는 0~101입니다. 도메인 ID로 UDP 포트가 계산되는데 그 값이 운영체제의 임시 포트 범위와 겹치면 안 되기 때문입니다.
Q3. 퍼블리셔가 Transient local, 서브스크라이버가 Volatile입니다. 연결되나요? 늦게 접속한 서브스크라이버는 이전 메시지를 받나요?
연결됩니다 (제공이 요청보다 높은 품질). 하지만 서브스크라이버가 Volatile이라 새 메시지만 받습니다. 이전 메시지를 받으려면 서브스크라이버도 Transient local이어야 합니다.
다음 장
다음 장에서는 좌표계와 TF2, 그리고 로봇 모델 URDF를 다룹니다. 좌표계로는 로봇의 여러 부품 위치를 하나의 체계로 표현합니다.