1. 개요
이번 실습에서는 ROS 2 Humble 환경에서 C++ 패키지를 생성하고, Publisher 노드와 Subscriber 노드를 작성한 뒤 빌드하고 실행하는 과정을 진행합니다.
이번 실습에서 사용할 패키지 이름은 다음과 같습니다.
my_cpp_package
실습 목표는 다음과 같습니다.
작업공간 생성
→ C++ 패키지 생성
→ Publisher 노드 작성
→ Subscriber 노드 작성
→ CMakeLists.txt 수정
→ colcon build
→ 실행 확인
2. 실습 환경
이 글은 다음 환경을 기준으로 합니다.
OS: Ubuntu 22.04
ROS 2: Humble Hawksbill
언어: C++
빌드 타입: ament_cmake
빌드 도구: colcon
ROS 2 Humble 환경을 불러옵니다.
source /opt/ros/humble/setup.bash
src 폴더로 이동합니다.
cd ~/ros2_study/src
C++ 패키지를 생성합니다.
ros2 pkg create my_cpp_package --build-type ament_cmake --dependencies rclcpp std_msgs
생성 후 구조를 확인합니다.
tree my_cpp_package
예상 구조는 다음과 같습니다.
my_cpp_package/
├── CMakeLists.txt
├── include/
│ └── my_cpp_package/
├── package.xml
└── src/


4. package.xml 확인
src 폴더로 이용하여 code를 실행하고 package.xml을 불러옵니다.
package.xml은 패키지 정보와 의존성을 관리하는 파일입니다.
생성된 package.xml에서 다음 내용이 있는지 확인합니다.
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>rclcpp</depend>
<depend>std_msgs</depend>
그리고 export 태그 안에 다음 내용이 들어가는 것이 좋습니다.
<export>
<build_type>ament_cmake</build_type>
</export>
C++ ROS 2 패키지에서 <build_type>ament_cmake</build_type>가 빠지면 패키지 인식이나 빌드 과정에서 문제가 생길 수 있습니다.
최소 형태는 다음과 비슷합니다.
<?xml version="1.0"?>
<package format="3">
<name>my_cpp_package</name>
<version>0.0.0</version>
<description>ROS 2 Humble C++ practice package for mobile robot examples</description>
<maintainer email="user@example.com">user</maintainer>
<license>Apache-2.0</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>rclcpp</depend>
<depend>std_msgs</depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>

5. CMakeLists.txt 기본 구조 확인
CMakeLists.txt는 C++ 파일을 어떻게 빌드하고 설치할지 정하는 파일입니다.
처음 생성된 파일은 대략 다음 구조를 가집니다.
cmake_minimum_required(VERSION 3.8)
project(my_cpp_package)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
ament_package()
여기에서 나중에 실행 파일 등록 내용을 추가합니다.

6. Publisher 노드 만들기
패키지 폴더로 이동합니다.
cd ~/ros2_study/src/my_cpp_package
Publisher 소스 파일을 만듭니다.
touch src/simple_publisher.cpp

파일을 엽니다.
code .
아래 코드를 작성합니다.
#include <chrono>
#include <functional>
#include <memory>
#include <string>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std::chrono_literals;
class SimplePublisher : public rclcpp::Node
{
public:
SimplePublisher()
: Node("simple_publisher"), count_(0)
{
publisher_ = this->create_publisher<std_msgs::msg::String>("robot_status", 10);
timer_ = this->create_wall_timer(
1s,
std::bind(&SimplePublisher::timer_callback, this)
);
}
private:
void timer_callback()
{
auto message = std_msgs::msg::String();
message.data = "Mobile robot is alive: " + std::to_string(count_++);
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
publisher_->publish(message);
}
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
rclcpp::TimerBase::SharedPtr timer_;
size_t count_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<SimplePublisher>());
rclcpp::shutdown();
return 0;
}

7. Publisher 코드 분석
1) ROS 2와 메시지 헤더 포함
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
rclcpp는 ROS 2 C++ 클라이언트 라이브러리입니다.
std_msgs/msg/string.hpp는 문자열 메시지를 사용하기 위한 헤더입니다.
2) 노드 클래스 선언
class SimplePublisher : public rclcpp::Node
SimplePublisher 클래스는 ROS 2 노드입니다.
rclcpp::Node를 상속받아서 직접 만든 노드 클래스로 사용합니다.
3) 생성자
SimplePublisher()
: Node("simple_publisher"), count_(0)
노드 이름을 simple_publisher로 설정합니다.
count_는 발행 횟수를 세기 위한 변수이며 0부터 시작합니다.
4) Publisher 생성
publisher_ = this->create_publisher<std_msgs::msg::String>("robot_status", 10);
robot_status라는 토픽으로 문자열 메시지를 발행하는 Publisher를 만듭니다.
여기서 10은 QoS 큐 크기입니다.
구독자가 잠시 메시지를 처리하지 못할 때 최대 10개까지 메시지를 버퍼에 저장할 수 있다는 의미입니다.
5) Timer 생성
timer_ = this->create_wall_timer(
1s,
std::bind(&SimplePublisher::timer_callback, this)
);
1초마다 timer_callback() 함수를 실행하는 타이머입니다.
즉, 이 노드는 1초 주기로 메시지를 발행합니다.
using namespace std::chrono_literals;
1s, 500ms 같은 시간 표현을 쓸 수 있게 해줍니다.
6) 콜백 함수
void timer_callback()
{
auto message = std_msgs::msg::String();
message.data = "Mobile robot is alive: " + std::to_string(count_++);
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
publisher_->publish(message);
}
이 함수가 1초마다 실행됩니다.
동작 순서는 다음과 같습니다.
- 문자열 메시지 객체 생성
- "Mobile robot is alive: 숫자" 형태의 데이터 작성
- 터미널에 발행 로그 출력
- robot_status 토픽으로 메시지 발행
7) main 함수
rclcpp::init(argc, argv);
ROS 2 시스템을 초기화합니다.
rclcpp::spin(std::make_shared<SimplePublisher>());
std::make_shared<>는 C++에서 객체를 동적으로 생성하고, 그 객체를 std::shared_ptr로 관리하게 해주는 함수입니다.
ROS 2 코드에서는 보통 노드를 만들 때 많이 사용합니다.
std::make_shared<SimplePublisher>()
풀어서 쓰면 아래 코드와 비슷합니다.
std::shared_ptr<SimplePublisher> node =
std::make_shared<SimplePublisher>();
즉, SimplePublisher 노드 객체를 생성하는 코드입니다.
SimplePublisher node;
이렇게 일반 객체로 만들 수도 있지만, ROS 2의 rclcpp::spin()은 보통 스마트 포인터 형태의 노드 객체를 받습니다.
그래서 아래처럼 사용합니다.
rclcpp::spin(std::make_shared<SimplePublisher>());
shared_ptr는 여러 곳에서 함께 사용할 수 있는 포인터입니다.
객체를 사용하는 곳이 남아 있으면 객체가 유지되고, 더 이상 사용하는 곳이 없으면 자동으로 메모리가 해제됩니다.
즉, 직접 delete를 하지 않아도 됩니다.
SimplePublisher 노드를 실행합니다.
spin()은 타이머 콜백이나 구독 콜백 같은 ROS 이벤트를 계속 처리합니다.
rclcpp::shutdown();
노드 실행이 끝나면 ROS 2를 종료합니다.
8. Subscriber 노드 만들기
Subscriber 소스 파일을 만듭니다.
touch src/simple_subscriber.cpp

code를 실행하여 파일을 엽니다.
아래 코드를 작성합니다.
#include <functional>
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
class SimpleSubscriber : public rclcpp::Node
{
public:
SimpleSubscriber()
: Node("simple_subscriber")
{
subscription_ = this->create_subscription<std_msgs::msg::String>(
"robot_status",
10,
std::bind(&SimpleSubscriber::topic_callback, this, std::placeholders::_1)
);
}
private:
void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
{
RCLCPP_INFO(this->get_logger(), "Received: '%s'", msg->data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<SimpleSubscriber>());
rclcpp::shutdown();
return 0;
}

9. Subscriber 코드 분석
1) 노드 클래스 선언
class SimpleSubscriber : public rclcpp::Node
SimpleSubscriber 클래스는 ROS 2 노드입니다.
rclcpp::Node를 상속받아서 Subscriber 기능을 가진 노드를 만듭니다.
2) 생성자
SimpleSubscriber()
: Node("simple_subscriber")
노드 이름을 simple_subscriber로 설정합니다.
3) Subscriber 생성
subscription_ = this->create_subscription<std_msgs::msg::String>(
"robot_status",
10,
std::bind(&SimpleSubscriber::topic_callback, this, std::placeholders::_1)
);
robot_status 토픽을 구독하는 Subscriber를 생성합니다.
각 인자의 의미는 다음과 같습니다.
| std_msgs::msg::String | 구독할 메시지 타입 |
| "robot_status" | 구독할 토픽 이름 |
| 10 | QoS 큐 크기 |
| topic_callback | 메시지를 받았을 때 실행할 함수 |
4) 콜백 함수
void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
{
RCLCPP_INFO(this->get_logger(), "Received: '%s'", msg->data.c_str());
}
토픽으로 메시지가 들어오면 이 함수가 자동으로 실행됩니다.
msg->data에는 Publisher가 보낸 문자열 데이터가 들어 있습니다. 즉, 받은 메시지를 터미널에 출력하는 역할을 합니다.
5) main 함수
rclcpp::init(argc, argv);
ROS 2를 초기화합니다.
rclcpp::spin(std::make_shared<SimpleSubscriber>());
SimpleSubscriber 노드를 생성하고 계속 실행합니다.
spin()이 실행 중이어야 토픽 메시지를 받을 수 있습니다.
rclcpp::shutdown();
ROS 2를 종료합니다.
10. CMakeLists.txt 수정하기
이제 C++ 파일을 빌드 대상에 추가합니다.
아래처럼 수정합니다.
cmake_minimum_required(VERSION 3.8)
project(my_cpp_package)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
add_executable(simple_publisher src/simple_publisher.cpp)
ament_target_dependencies(simple_publisher rclcpp std_msgs)
add_executable(simple_subscriber src/simple_subscriber.cpp)
ament_target_dependencies(simple_subscriber rclcpp std_msgs)
install(TARGETS
simple_publisher
simple_subscriber
DESTINATION lib/${PROJECT_NAME}
)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()
중요한 부분은 다음입니다.
add_executable(simple_publisher src/simple_publisher.cpp)
ament_target_dependencies(simple_publisher rclcpp std_msgs)
add_executable(simple_subscriber src/simple_subscriber.cpp)
ament_target_dependencies(simple_subscriber rclcpp std_msgs)
그리고 실행하려면 반드시 설치 대상에 추가해야 합니다.
install(TARGETS
simple_publisher
simple_subscriber
DESTINATION lib/${PROJECT_NAME}
)
이 부분이 없으면 빌드는 되더라도 ros2 run에서 실행 파일을 찾지 못할 수 있습니다.

11. 패키지 빌드하기
작업공간 루트로 이동합니다.
cd ~/ros2_study
전체 빌드합니다.
colcon build
특정 패키지만 빌드하려면 다음처럼 합니다.
colcon build --packages-select my_cpp_package
빌드가 성공하면 다음 폴더들이 생성됩니다.
ros2_ws/
├── build/
├── install/
├── log/
└── src/

12. 환경 설정하기
빌드 후에는 반드시 install/setup.bash를 불러옵니다.
source install/setup.bash
절대 빼먹으면 안 됩니다. 이걸 하지 않으면 새로 만든 패키지를 현재 터미널에서 찾지 못합니다.
13. 노드 실행하기
터미널을 2개 엽니다.
터미널 1: Publisher 실행
source /opt/ros/humble/setup.bash
source ~/ros2_study/install/setup.bash
ros2 run my_cpp_package simple_publisher
출력 예시는 다음과 같습니다.
[INFO] [simple_publisher]: Publishing: 'Mobile robot is alive: 0'
[INFO] [simple_publisher]: Publishing: 'Mobile robot is alive: 1'
[INFO] [simple_publisher]: Publishing: 'Mobile robot is alive: 2'
터미널 2: Subscriber 실행
source /opt/ros/humble/setup.bash
source ~/ros2_study/install/setup.bash
ros2 run my_cpp_package simple_subscriber
출력 예시는 다음과 같습니다.
[INFO] [simple_subscriber]: Received: 'Mobile robot is alive: 0'
[INFO] [simple_subscriber]: Received: 'Mobile robot is alive: 1'
[INFO] [simple_subscriber]: Received: 'Mobile robot is alive: 2'
이렇게 나오면 정상입니다.


14. ROS 2 명령어로 확인하기
실행 중인 노드를 확인합니다.
ros2 node list
예상 출력:
/simple_publisher
/simple_subscriber
목록을 확인합니다.
ros2 topic list
예상 출력:
/parameter_events
/robot_status
/rosout

데이터를 직접 확인합니다.
ros2 topic echo /robot_status
예상 출력:
data: 'Mobile robot is alive: 0'
---
data: 'Mobile robot is alive: 1'
---
data: 'Mobile robot is alive: 2'
---

정보를 확인합니다.
ros2 topic info /robot_status
예상 출력:
Type: std_msgs/msg/String
Publisher count: 1
Subscription count: 1

15. 최종 패키지 구조
실습이 끝난 후 패키지 구조는 다음과 같습니다.
my_cpp_package/
├── CMakeLists.txt
├── include/
│ └── my_cpp_package/
├── package.xml
└── src/
├── simple_publisher.cpp
└── simple_subscriber.cpp

작업공간 전체 구조는 다음과 같습니다.
ros2_ws/
├── build/
├── install/
├── log/
└── src/
└── my_cpp_package/
├── CMakeLists.txt
├── include/
│ └── my_cpp_package/
├── package.xml
└── src/
├── simple_publisher.cpp
└── simple_subscriber.cpp
16. 실습 과제
과제 1. 이름 변경하기
현재 이름은 다음입니다.
"robot_status"
이것을 다음 이름으로 바꿔보세요.
"mobile_robot_status"
수정해야 할 파일:
simple_publisher.cpp
simple_subscriber.cpp
빌드:
cd ~/ros2_ws
colcon build --packages-select my_cpp_package
source install/setup.bash
확인:
ros2 topic list
ros2 topic echo /mobile_robot_status

과제 2. 발행 주기 변경하기
현재 Publisher는 1초마다 메시지를 보냅니다.
1s
이것을 0.5초로 바꿔보세요.
500ms
코드 상단에 이미 다음이 있으므로 바로 사용할 수 있습니다.
using namespace std::chrono_literals;
과제 3. 새 노드 추가하기
새 파일을 만듭니다.
touch src/robot_status_publisher.cpp
새 Publisher 노드를 만들고, 실행 파일 이름을 다음으로 등록해보세요.
robot_status_publisher
CMakeLists.txt에 추가할 내용:
add_executable(robot_status_publisher src/robot_status_publisher.cpp)
ament_target_dependencies(robot_status_publisher rclcpp std_msgs)
그리고 install()에도 추가합니다.
install(TARGETS
simple_publisher
simple_subscriber
robot_status_publisher
DESTINATION lib/${PROJECT_NAME}
)
빌드:
cd ~/ros2_ws
colcon build --packages-select my_cpp_package
source install/setup.bash
실행:
ros2 run my_cpp_package robot_status_publisher

'강좌 > ROS2' 카테고리의 다른 글
| ROS 2 Python 패키지의 setup.py 이해하기 (0) | 2026.05.24 |
|---|---|
| ROS 2 package.xml 이해하기 : Python 패키지와 C++ 패키지 기준 (0) | 2026.05.24 |
| ROS 2 개발자를 위한 C/C++ 기초강의 #2 (0) | 2026.05.24 |
| VS Code 원격 개발 환경 (0) | 2026.05.24 |
| ROS 2 Humble rqt Plugins 정리 #3 (0) | 2026.05.24 |