TIKI-MINI로 시작하는 ROS2 실무
목차

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부(개발환경)에서 다룹니다.
디스커버리 순서같은 도메인 ID노드 ADDS 참여자노드 BDDS 참여자1. 발견 메시지 (멀티캐스트)2. 토픽 이름, 타입, QoS 교환3. 호환되면 연결, 메시지 전달노드 C다른 도메인 ID서로 보이지 않음
같은 도메인의 두 노드가 발견 메시지를 알리고 토픽 정보와 QoS를 교환한 뒤 연결되는 순서입니다. 도메인이 다른 노드는 보이지 않습니다.

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이므로 이것을 권장합니다.
안전한 도메인 ID 범위Linux0~101 안전임시 포트와 겹침macOS, Windows0~166 안전겹침세 OS 공통0~101 권장0101166215232도메인 ID발견용 멀티캐스트 포트 = 7400 + 250 × 도메인 ID
도메인 ID 0~232 가운데 운영체제별로 임시 포트와 겹치지 않는 구간을 나타냈습니다. 세 운영체제에서 모두 안전한 구간은 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처럼 가볍게 최선만 다해서도 쓸 수 있습니다. 여러 정책이 모여 하나의 프로파일을 이룹니다.

주요 정책

정책선택지의미
HistoryKeep last / Keep all보관할 메시지를 최근 N개로 할지, 모두 보관할지 (미들웨어의 자원 한도 안에서)
Depth정수Keep last일 때 큐 크기. Keep all이면 쓰이지 않음
ReliabilityReliable / Best effortReliable은 전달을 보장하려고 필요하면 재전송, Best effort는 최선만 다하고 잃어버릴 수 있음
DurabilityVolatile / Transient localTransient local은 퍼블리셔가 메시지를 보관했다가 늦게 참여한 구독자에게 전달, Volatile은 보관 안 함
Deadline시간메시지가 올라와야 하는 최대 간격
Lifespan시간발행 후 이 시간이 지난 메시지는 만료되어 조용히 버려짐
Liveliness, Lease durationAutomatic / Manual by topic, 시간퍼블리셔가 살아 있는지 확인하는 방식과 제한 시간
  • ROS1의 큐 크기와 비슷한 것이 History + Depth입니다.
  • Durability의 Transient local과 어떤 depth의 조합은 ROS1의 "latch"와 비슷한 기능입니다. 지도(/map)처럼 한 번 발행하고 나중에 접속한 노드도 받아야 하는 데이터에 씁니다.

자주 쓰는 프로파일

Humble의 기본 프로파일은 아래와 같습니다 (rmw/qos_profiles.h 기준).

프로파일History / DepthReliabilityDurability용도
기본 (default)Keep last / 10ReliableVolatile일반 토픽. create_publisher(..., 10) 의 10이 depth
센서 데이터 (sensor data)Keep last / 5Best effortVolatile최신 값이 중요하고 일부 손실은 괜찮은 센서 스트림
서비스 (services default)Keep last / 10ReliableVolatile서비스 요청과 응답
파라미터 (parameters)Keep last / 1000ReliableVolatile파라미터 서비스. 큐가 커서 요청이 쉽게 유실되지 않음
시스템 기본 (system default)RMW 구현의 기본값RMW마다 다를 수 있음

기본 프로파일에서 Deadline, Lifespan, Lease duration은 모두 "기본값(제한 없음)"이고 Liveliness는 시스템 기본입니다. 래치 용도의 transient local 조합은 위 목록의 내장 프로파일에 없습니다. 필요할 때 직접 지정하는 설정입니다.

서비스는 특히 Volatile이어야 합니다. 서버가 재시작할 때 오래된 요청을 받아 부작용이 생기는 것을 막기 위해서입니다.

QoS 호환성: 요청과 제공

QoS는 퍼블리셔와 서브스크라이버가 각자 정합니다. 그리고 둘의 설정이 호환되어야만 연결됩니다.

  • 서브스크라이버는 "이 정도 품질은 받아야 한다"는 최소 품질을 요청합니다.
  • 퍼블리셔는 "이 정도 품질까지 줄 수 있다"는 최대 품질을 제공합니다.
  • 모든 정책에서 요청이 제공보다 까다롭지 않아야 연결됩니다. 하나라도 어긋나면 연결되지 않고 메시지가 전달되지 않습니다.

가장 자주 만나는 두 규칙입니다.

퍼블리셔서브스크라이버Reliability
Best effortBest effort호환
Best effortReliable비호환
ReliableBest effort호환 (best effort로 받음)
ReliableReliable호환
퍼블리셔서브스크라이버Durability결과
VolatileVolatile호환새 메시지만
VolatileTransient local비호환통신 없음
Transient localVolatile호환새 메시지만
Transient localTransient local호환새 메시지와 이전 메시지

Deadline과 Lease duration은 퍼블리셔가 지정한 값 x에 대해 서브스크라이버의 값이 x 이상이면 호환입니다. 퍼블리셔는 기본값인데 서브스크라이버만 값을 지정하면 비호환입니다. Liveliness는 퍼블리셔가 Automatic인데 서브스크라이버가 Manual by topic이면 비호환입니다. 이 규칙은 서비스 서버와 클라이언트에도 똑같이 적용됩니다.

해보기: QoS 호환성 검사기

퍼블리셔와 서브스크라이버의 QoS를 고르면 호환 여부와 이유를 알려 줍니다. 처음 설정은 위의 함정(센서 퍼블리셔 + 기본 구독자)입니다.

퍼블리셔가 제공(offer)하는 QoS
서브스크라이버가 요청(request)하는 QoS
호환되지 않습니다 (Reliability). 연결되지 않아 메시지가 전달되지 않습니다.
정책퍼블리셔서브스크라이버결과
ReliabilityBest effortReliable비호환 · 서브스크라이버는 모든 메시지의 전달 보장(reliable)을 요청했지만, 퍼블리셔는 best effort만 제공합니다.
DurabilityVolatileVolatile호환 · 새 메시지만 주고받습니다.
Deadline기본(제한 없음)기본(제한 없음)호환 · 둘 다 기본값이라 deadline 조건이 없습니다.
LivelinessAutomaticAutomatic호환 · 요청한 수준을 제공할 수 있습니다.
History / DepthKeep last 5Keep 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)

한눈에 정리

  1. ROS2의 통신은 DDS가 맡고 RMW가 구현을 바꿔 끼울 수 있게 해 준다. Humble의 기본은 Fast DDS다.
  2. 노드는 중앙 서버 없이 디스커버리로 서로를 찾는다.
  3. ROS_DOMAIN_ID(기본 0)가 같은 노드끼리만 통신한다. 안전한 값은 0~101이다.
  4. QoS 정책의 핵심은 History/Depth, Reliability, Durability이고 Deadline, Lifespan, Liveliness도 있다.
  5. 서브스크라이버의 요청이 퍼블리셔의 제공보다 까다로우면 연결되지 않는다. 특히 Best effort 퍼블리셔와 Reliable 구독자, Volatile 퍼블리셔와 Transient local 구독자는 비호환이다.
  6. 센서 토픽이 안 오면 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를 다룹니다. 좌표계로는 로봇의 여러 부품 위치를 하나의 체계로 표현합니다.