이번 실습에서는 C++로 ROS2에서 제공하는 인터페이스를 이용하여 토픽 통신하는 예제를 만들어 보겠습니다.
예제 내용은 간단합니다.
temperature_publisher 노드는 1초마다 임의의 온도 값을 만들어 /temperature 토픽으로 보냅니다.
temperature_subscriber 노드는 /temperature 토픽을 구독하고, 받은 온도 값을 터미널에 출력합니다.
전체 구조는 다음과 같습니다.
temperature_publisher
|
| std_msgs/msg/Float32
v
/temperature
|
v
temperature_subscriber
이번 예제에서는 ROS 2에서 기본 제공하는 std_msgs/msg/Float32 메시지를 사용합니다.
1. 퍼블리셔 노드 만들기
먼저 온도 데이터를 보내는 퍼블리셔 노드를 작성하겠습니다.
cd ~/ros2_study/src/my-cpp_package/src
touch temperature_publisher.cpp

code를 실행하여 temperature_publisher.cpp 파일에 아래 코드를 작성합니다.
#include <chrono>
#include <memory>
#include <random>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/float32.hpp"
using namespace std::chrono_literals;
class TemperaturePublisher : public rclcpp::Node
{
public:
TemperaturePublisher()
: Node("temperature_publisher")
{
publisher_ = this->create_publisher<std_msgs::msg::Float32>(
"temperature",
rclcpp::QoS(10)
);
timer_ = this->create_wall_timer(
1s,
std::bind(&TemperaturePublisher::publish_temperature, this)
);
RCLCPP_INFO(this->get_logger(), "Temperature publisher node has started.");
}
private:
void publish_temperature()
{
std_msgs::msg::Float32 msg;
msg.data = generate_temperature();
publisher_->publish(msg);
RCLCPP_INFO(
this->get_logger(),
"Published temperature: %.2f C",
msg.data
);
}
float generate_temperature()
{
static std::random_device rd;
static std::mt19937 generator(rd());
static std::uniform_real_distribution<float> distribution(20.0, 35.0);
return distribution(generator);
}
rclcpp::Publisher<std_msgs::msg::Float32>::SharedPtr publisher_;
rclcpp::TimerBase::SharedPtr timer_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
auto node = std::make_shared<TemperaturePublisher>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}

2. 퍼블리셔 코드 분석
먼저 필요한 헤더를 포함합니다.
#include <chrono>
#include <memory>
#include <random>
각 헤더의 역할은 다음과 같습니다.
chrono : 시간 처리를 위해 사용
memory : std::shared_ptr 같은 스마트 포인터 사용
random : 임의의 온도 값 생성
다음은 ROS 2 관련 헤더입니다.
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/float32.hpp"
rclcpp는 ROS 2 C++ 프로그래밍에서 가장 기본이 되는 라이브러리입니다.
std_msgs/msg/float32.hpp는 32비트 실수 하나를 담는 메시지 타입입니다.
온도, 거리, 속도, 전압처럼 숫자 하나를 보낼 때 사용하기 좋습니다.
3. Node 클래스 상속하기
class TemperaturePublisher : public rclcpp::Node
ROS 2 C++에서는 보통 rclcpp::Node를 상속해서 노드 클래스를 만듭니다.
이렇게 작성하면 퍼블리셔, 서브스크라이버, 타이머 같은 ROS 2 기능을 클래스 안에서 깔끔하게 관리할 수 있습니다.
생성자에서는 노드 이름을 지정합니다.
TemperaturePublisher()
: Node("temperature_publisher")
이 노드는 실행되면 ROS 2 시스템 안에서 temperature_publisher라는 이름으로 등록됩니다.
4. 퍼블리셔 생성하기
publisher_ = this->create_publisher<std_msgs::msg::Float32>(
"temperature",
rclcpp::QoS(10)
);
이 코드는 temperature 토픽으로 std_msgs::msg::Float32 타입 메시지를 발행하는 퍼블리셔를 만듭니다.
여기서 중요한 값은 두 가지입니다.
"temperature" : 토픽 이름
rclcpp::QoS(10) : 메시지 큐 크기
코드에서는 "temperature"라고 작성했지만, ROS 2 명령어로 확인하면 보통 /temperature처럼 보입니다.
rclcpp::QoS(10)은 메시지를 최대 10개까지 큐에 저장할 수 있다는 의미입니다.
퍼블리셔가 데이터를 보내는 속도와 서브스크라이버가 받는 속도가 항상 같지는 않기 때문에, 어느 정도 메시지를 보관할지 정하는 설정이 필요합니다. 처음 공부할 때는 일단 rclcpp::QoS(10)으로 시작하면 충분합니다.
5. 타이머 생성하기
timer_ = this->create_wall_timer(
1s,
std::bind(&TemperaturePublisher::publish_temperature, this)
);
이 코드는 1초마다 publish_temperature() 함수를 실행합니다.
create_wall_timer()는 ROS 2 노드에서 일정 시간마다 콜백 함수를 반복 실행하는 타이머를 생성하는 함수입니다.
예:
timer_ = this->create_wall_timer(
1s,
std::bind(&TemperaturePublisher::publish_temperature, this)
);
의미:
create_wall_timer(주기, 실행할_함수)
즉,
1초마다 publish_temperature() 함수를 실행한다
여기서 wall timer는 시스템의 실제 시간, 즉 벽시계 시간을 기준으로 동작하는 타이머입니다.
6. 메시지 발행 함수
void publish_temperature()
{
std_msgs::msg::Float32 msg;
msg.data = generate_temperature();
publisher_->publish(msg);
RCLCPP_INFO(
this->get_logger(),
"Published temperature: %.2f C",
msg.data
);
}
중요한 소스는 아래입니다.
std_msgs::msg::Float32 msg;
msg.data = generate_temperature();
publisher_->publish(msg);
먼저 std_msgs::msg::Float32 타입의 메시지 객체를 만듭니다.
std_msgs::msg::Float32 msg;
그리고 메시지의 data 필드에 온도 값을 넣습니다.
msg.data = generate_temperature();
마지막으로 publish() 함수를 호출해서 토픽으로 보냅니다.
publisher_->publish(msg);
퍼블리셔는 특정 노드에게 직접 데이터를 보내는 것이 아닙니다.
정해진 토픽으로 데이터를 발행하고, 그 토픽을 구독하는 노드가 데이터를 받습니다.
7. 임의의 온도 값 만들기
float generate_temperature()
{
static std::random_device rd;
static std::mt19937 generator(rd());
static std::uniform_real_distribution<float> distribution(20.0, 35.0);
return distribution(generator);
}
이 함수는 20.0도에서 35.0도 사이의 임의 온도 값을 생성합니다.
static std::random_device rd;
난수 생성기의 초기값을 만들기 위한 객체입니다.
static std::mt19937 generator(rd());
실제 난수를 생성하는 객체입니다.
static std::uniform_real_distribution<float> distribution(20.0, 35.0);
20.0부터 35.0 사이의 실수를 비슷한 확률로 생성하도록 설정합니다.
return distribution(generator);
설정된 범위 안에서 온도 값 하나를 만들어 반환합니다.
여기서는 실제 온도 센서가 없기 때문에 랜덤 값으로 센서 데이터를 흉내 내는 것입니다.
8. 서브스크라이버 노드 만들기
이제 온도 데이터를 받는 서브스크라이버 노드를 작성하겠습니다.
cd ~/ros2_study/src/my_cpp_package
touch temperature_subscriber.cpp

temperature_subscriber.cpp 파일에 아래 코드를 작성합니다.
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/float32.hpp"
class TemperatureSubscriber : public rclcpp::Node
{
public:
TemperatureSubscriber()
: Node("temperature_subscriber")
{
subscription_ = this->create_subscription<std_msgs::msg::Float32>(
"temperature",
rclcpp::QoS(10),
std::bind(
&TemperatureSubscriber::temperature_callback,
this,
std::placeholders::_1
)
);
RCLCPP_INFO(this->get_logger(), "Temperature subscriber node has started.");
}
private:
void temperature_callback(const std_msgs::msg::Float32::SharedPtr msg)
{
RCLCPP_INFO(
this->get_logger(),
"Received temperature: %.2f C",
msg->data
);
if (msg->data >= 30.0) {
RCLCPP_WARN(
this->get_logger(),
"Temperature is high. Cooling check is recommended."
);
}
}
rclcpp::Subscription<std_msgs::msg::Float32>::SharedPtr subscription_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
auto node = std::make_shared<TemperatureSubscriber>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}

9. 서브스크라이버 코드 이해하기
1) 사용한 헤더
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/float32.hpp"
memory는 std::shared_ptr를 사용하기 위한 헤더입니다.
rclcpp/rclcpp.hpp는 ROS 2 C++ 기능을 사용하기 위한 기본 헤더입니다. 노드 생성, Subscriber 생성, 로그 출력, spin() 실행 등에 사용됩니다.
std_msgs/msg/float32.hpp는 Float32 메시지 타입을 사용하기 위한 헤더입니다. 이 메시지는 float data 하나를 가지고 있으며, 여기서는 온도 값을 저장하는 데 사용됩니다.
2) 노드 클래스
class TemperatureSubscriber : public rclcpp::Node
TemperatureSubscriber 클래스는 rclcpp::Node를 상속받습니다. 따라서 이 클래스는 ROS 2 노드로 동작합니다.
3) 생성자
TemperatureSubscriber()
: Node("temperature_subscriber")
노드 이름을 temperature_subscriber로 설정합니다.
즉, 실행 후 노드 목록을 확인하면 다음과 같이 보입니다.
ros2 node list
/temperature_subscriber
4) Subscriber 생성
subscription_ = this->create_subscription<std_msgs::msg::Float32>(
"temperature",
rclcpp::QoS(10),
std::bind(
&TemperatureSubscriber::temperature_callback,
this,
std::placeholders::_1
)
);
temperature라는 토픽을 구독합니다.
메시지 타입은 std_msgs::msg::Float32입니다.
rclcpp::QoS(10)
은 메시지를 최대 10개까지 큐에 저장할 수 있다는 의미입니다.
std::bind(...)
은 메시지가 들어왔을 때 실행할 콜백 함수를 연결합니다. 즉, temperature 토픽으로 메시지가 들어오면 아래 함수가 자동 실행됩니다.
temperature_callback()
5) 콜백 함수
void temperature_callback(const std_msgs::msg::Float32::SharedPtr msg)
이 함수는 온도 메시지를 받을 때마다 실행됩니다. 수신한 온도 값은 다음처럼 읽습니다.
msg->data
그리고 로그로 출력합니다.
RCLCPP_INFO(
this->get_logger(),
"Received temperature: %.2f C",
msg->data
);
예를 들어 온도 값이 25.3이면 다음처럼 출력됩니다.
Received temperature: 25.30 C
6) 온도 경고 처리
if (msg->data >= 30.0) {
RCLCPP_WARN(
this->get_logger(),
"Temperature is high. Cooling check is recommended."
);
}
온도 값이 30.0 이상이면 경고 로그를 출력합니다.
즉,
30도 미만 → 온도만 출력
30도 이상 → 온도 출력 + 경고 출력
7) Subscriber 멤버 변수
rclcpp::Subscription<std_msgs::msg::Float32>::SharedPtr subscription_;
생성한 Subscriber를 저장하는 변수입니다.
이 변수를 클래스 멤버로 가지고 있어야 구독 기능이 계속 유지됩니다.
8) main 함수
rclcpp::init(argc, argv);
auto node = std::make_shared<TemperatureSubscriber>();
rclcpp::spin(node);
rclcpp::shutdown();
동작 순서는 다음과 같습니다.
ROS 2 초기화
→ TemperatureSubscriber 노드 생성
→ spin()으로 노드 실행 유지
→ 메시지가 들어오면 콜백 함수 실행
→ 종료 시 ROS 2 shutdown
rclcpp::spin(node)가 실행 중이어야 Subscriber가 메시지를 받을 수 있습니다.
10. CMakeLists.txt 수정하기
이제 두 C++ 파일을 실행 파일로 등록해야 합니다.
아래 파일을 엽니다.
cd ~/ros2_study/src/my_cpp_package
code .
find_package 아래쪽, ament_package() 위쪽에 다음 내용을 추가합니다.
add_executable(temperature_publisher src/temperature_publisher.cpp)
ament_target_dependencies(temperature_publisher rclcpp std_msgs)
add_executable(temperature_subscriber src/temperature_subscriber.cpp)
ament_target_dependencies(temperature_subscriber rclcpp std_msgs)
install(TARGETS
temperature_publisher
temperature_subscriber
DESTINATION lib/${PROJECT_NAME}
)

중요한 부분은 세 가지입니다.
add_executable(temperature_publisher src/temperature_publisher.cpp)
C++ 소스 파일을 실행 가능한 프로그램으로 만듭니다.
ament_target_dependencies(temperature_publisher rclcpp std_msgs)
해당 실행 파일이 사용하는 ROS 2 패키지 의존성을 연결합니다.
install(TARGETS
temperature_publisher
temperature_subscriber
DESTINATION lib/${PROJECT_NAME}
)
ros2 run 명령어로 실행할 수 있도록 설치 경로를 지정합니다.
이 install() 설정을 빼먹으면 빌드가 성공해도 ros2 run에서 실행 파일을 찾지 못할 수 있습니다.
11. package.xml 확인하기
패키지를 만들 때 아래 옵션을 사용했습니다.
--dependencies rclcpp std_msgs
그래서 package.xml에는 보통 다음 내용이 자동으로 들어갑니다.
<depend>rclcpp</depend>
<depend>std_msgs</depend>
그래도 한 번 확인하는 습관을 들이는 것이 좋습니다.
package.xml를 열러 아래 항목이 있는지 확인합니다.
<depend>rclcpp</depend>
<depend>std_msgs</depend>

ROS 2에서 package.xml은 이 패키지가 어떤 라이브러리와 메시지 패키지에 의존하는지 알려주는 파일입니다.
빌드 오류가 날 때는 보통 아래 두 파일을 먼저 확인합니다.
CMakeLists.txt
package.xml
이건 기본 중의 기본입니다.
12. 빌드하기
워크스페이스 최상위 폴더로 이동합니다.
cd ~/ros2_study
빌드합니다.
colcon build --packages-select temperature_topic_cpp
빌드가 끝나면 환경 설정을 적용합니다.
source install/setup.bash

13. 퍼블리셔 실행하기
터미널을 하나 열고 아래 명령어를 실행합니다.
cd ~/ros2_study
source install/setup.bash
ros2 run temperature_topic_cpp temperature_publisher
정상적으로 실행되면 다음과 비슷한 출력이 나옵니다.
[INFO] [temperature_publisher]: Temperature publisher node has started.
[INFO] [temperature_publisher]: Published temperature: 24.73 C
[INFO] [temperature_publisher]: Published temperature: 31.42 C
[INFO] [temperature_publisher]: Published temperature: 28.15 C
이제 퍼블리셔 노드는 1초마다 온도 값을 발행하고 있습니다.

14. 서브스크라이버 실행하기
새 터미널을 하나 더 엽니다.
cd ~/ros2_study
source install/setup.bash
ros2 run temperature_topic_cpp temperature_subscriber
정상적으로 실행되면 다음과 비슷한 출력이 나옵니다.
[INFO] [temperature_subscriber]: Temperature subscriber node has started.
[INFO] [temperature_subscriber]: Received temperature: 24.73 C
[INFO] [temperature_subscriber]: Received temperature: 31.42 C
[WARN] [temperature_subscriber]: Temperature is high. Cooling check is recommended.
[INFO] [temperature_subscriber]: Received temperature: 28.15 C
온도가 30도 이상이면 경고 로그도 함께 출력됩니다.
[WARN] [temperature_subscriber]: Temperature is high. Cooling check is recommended.
이렇게 보이면 퍼블리셔와 서브스크라이버가 정상적으로 통신하고 있는 것입니다.

15. 토픽 목록 확인하기
ROS 2에서는 CLI 명령어로 현재 사용 중인 토픽을 확인할 수 있습니다.
새 터미널을 열고 아래 명령어를 실행합니다.
cd ~/ros2_study
source install/setup.bash
ros2 topic list
예상 출력은 다음과 같습니다.
/parameter_events
/rosout
/temperature

여기서 /temperature가 보이면 토픽이 정상적으로 생성된 것입니다.
16. 토픽 타입 확인하기
/temperature 토픽의 메시지 타입을 확인해 보겠습니다.
ros2 topic type /temperature
예상 출력은 다음과 같습니다.
std_msgs/msg/Float32
퍼블리셔와 서브스크라이버는 토픽 이름만 같아서는 안 됩니다.
메시지 타입도 같아야 합니다.
이번 예제에서는 둘 다 std_msgs/msg/Float32를 사용합니다.

17. 토픽 데이터 직접 확인하기
서브스크라이버를 실행하지 않아도 CLI 명령어로 토픽 데이터를 직접 볼 수 있습니다.
ros2 topic echo /temperature
예상 출력은 다음과 같습니다.
data: 26.374
---
data: 30.812
---
data: 22.691
---
이 명령어는 디버깅할 때 매우 자주 사용합니다.
노드는 실행 중인데 데이터가 제대로 오는지 모르겠다면, 바로 ros2 topic echo로 확인하면 됩니다.
18. 노드 목록 확인하기
현재 실행 중인 노드 목록도 확인할 수 있습니다.
ros2 node list
퍼블리셔와 서브스크라이버가 모두 실행 중이라면 다음과 비슷하게 나옵니다.
/temperature_publisher
/temperature_subscriber
노드가 보이지 않는다면 실행이 안 되었거나, 터미널에서 source install/setup.bash를 하지 않았을 가능성이 있습니다.

19. 실습 정리
이번 글에서는 ROS 2 C++에서 가장 기본적인 토픽 통신 예제를 만들었습니다.
만든 노드는 두 개입니다.
temperature_publisher
temperature_subscriber
사용한 토픽은 다음과 같습니다.
/temperature
사용한 메시지 타입은 다음과 같습니다.
std_msgs/msg/Float32
전체 흐름은 다음과 같습니다.
1. temperature_publisher 노드 실행
2. 1초마다 랜덤 온도 값 생성
3. /temperature 토픽으로 Float32 메시지 발행
4. temperature_subscriber 노드가 /temperature 구독
5. 수신한 온도 값을 터미널에 출력
6. 온도가 30도 이상이면 경고 출력
'강좌 > ROS2' 카테고리의 다른 글
| ROS 2 C++ Action Server / Client 실습 #2 (0) | 2026.05.15 |
|---|---|
| ROS 2 C++ 서비스 프로그래밍 이해하기 (0) | 2026.05.15 |
| ROS2 launch 작성 (0) | 2026.05.10 |
| 소스에서 파라미터 사용하기 (0) | 2026.05.10 |
| ROS 2 MultiThreadedExecutor와 Action Server로 Turtlesim 거리 이동 구현하기 (0) | 2026.05.10 |