3. Publisher & Subscriber C++
Writing your first ROS 2 publisher and subscriber in C++ with rclcpp
What will you learn in this unit?
- How to create a publisher
- How to create a subscriber
- What are topic messages and how they work
A publisher sends messages of a fixed type onto a named topic. Any number of subscribers can listen on that topic and run a callback whenever a new message arrives. The publisher and subscribers never talk to each other directly — they only know the topic name. In ROS 2 there is no central master: DDS (Data Distribution Service) takes over matchmaking. Each node advertises what it publishes or subscribes to via multicast, and once peers discover each other, message data flows directly peer-to-peer.
Creating a ROS 2 Package
Let's create a package called cpp_tutorial. Execute the following commands in the terminal:
First, navigate to the src folder in your workspace:
cd ~/ros2_ws/srcCreate the package with the ros2 pkg create command and set the dependencies to rclcpp and std_msgs:
ros2 pkg create --build-type ament_cmake --dependencies rclcpp std_msgs cpp_tutorialNavigate back to the ros2_ws:
cd ~/ros2_wsNow since we are using C++ we need to build the package:
colcon build --packages-select cpp_tutorial --symlink-installSource the workspace:
source install/setup.bashHere you can check if the package was created successfully:
ros2 pkg list | grep cpp_tutorialNow navigate to the package:
cd ~/ros2_ws/src/cpp_tutorialCreate a file for the publisher and subscriber in the src folder:
touch src/publisher.cpptouch src/subscriber.cppPublisher
- Now let's create the publisher. Copy the following code to the
publisher.cppfile:
#include <chrono> // For the 500ms literal below
#include <memory> // For std::make_shared
#include "rclcpp/rclcpp.hpp" // The ROS 2 C++ client library
#include "std_msgs/msg/int32.hpp" // The Int32 message from std_msgs
using namespace std::chrono_literals;
class CounterPublisher : public rclcpp::Node
{
public:
CounterPublisher() : Node("counter_publisher"), count_(0)
{
// Create a Publisher on the /counter topic with depth 10
publisher_ = this->create_publisher<std_msgs::msg::Int32>("counter", 10);
// Timer fires every 500 ms → publish rate of 2 Hz
timer_ = this->create_wall_timer(500ms, std::bind(&CounterPublisher::on_tick, this));
}
private:
void on_tick()
{
auto msg = std_msgs::msg::Int32();
msg.data = count_++;
publisher_->publish(msg);
}
rclcpp::Publisher<std_msgs::msg::Int32>::SharedPtr publisher_;
rclcpp::TimerBase::SharedPtr timer_;
int count_;
};
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<CounterPublisher>()); // Keep the node alive
rclcpp::shutdown();
return 0;
}- Since C++ is a compiled language, we need to modify the
CMakeLists.txtin thecpp_tutorialpackage to build the code. For more about CMakeLists in ROS 2 see: https://docs.ros.org/en/humble/How-To-Guides/Ament-CMake-Documentation.html
Here's how the CMakeLists.txt should look:
cmake_minimum_required(VERSION 3.8)
project(cpp_tutorial)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find_package() is how ament tells CMake where the ROS 2 libraries live.
# Every build dependency listed in package.xml needs a find_package() line here.
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
## Build ##
# add_executable() defines one binary called "publisher" built from the source below.
add_executable(publisher src/publisher.cpp)
# ament_target_dependencies() links and adds include paths for each ROS 2 dep in one call.
ament_target_dependencies(publisher rclcpp std_msgs)
## Install ##
# The install(TARGETS …) block is what makes `ros2 run cpp_tutorial publisher` work.
# It copies the binary into install/cpp_tutorial/lib/cpp_tutorial/ where the launcher looks.
install(TARGETS
publisher
DESTINATION lib/${PROJECT_NAME})
ament_package()To run the node first build it and then execute the following commands in different terminals:
Build the Node
To build the node execute the following commands in the terminal:
Navigate to the ros2_ws directory:
cd ~/ros2_wsBuild the workspace:
colcon build --packages-select cpp_tutorial --symlink-installSource the workspace:
source ./install/setup.bashCheck if the package was loaded successfully:
ros2 pkg list | grep cpp_tutorialRun the Publisher
- Unlike ROS 1, there is no
roscoreto start. You can run the publisher directly:
ros2 run cpp_tutorial publisherNothing happening? Well... that's not actually true! We just created a topic named /counter, and published through it an integer that increases indefinitely. Let's check this out.
A topic is like a pipe. Nodes use topics to publish information for other nodes so that they can communicate. You can find out, at any time, the number of topics in the system by doing a ros2 topic list. You can also check for a specific topic.
- Open a new terminal (
[ctrl + b]and[Shift + 2]), source your workspace (source ~/ros2_ws/install/setup.bash) and, typeros2 topic listand check for a topic named/counter.
ros2 topic list/counter
/parameter_events
/rosoutAnd there is our topic /counter! And some other topics that ROS 2 creates by default for logging and parameter events.
- To request information about a topic type the following command in a terminal:
ros2 topic info /counter
ros2 topic info /counterType: std_msgs/msg/Int32
Publisher count: 1
Subscription count: 0- The output indicates the type of the message (
std_msgs/msg/Int32), how many publishers are on the topic, and how many subscribers. Use the-vflag to see QoS profiles and node names. Now, typeros2 topic echo /counterand check the output of the topic in realtime. The output will look something like this:
ros2 topic echo /counterdata: 95
---
data: 96
---
data: 97
---
data: 98
---To stop any process in the terminal, press [ctrl + c] and to close the terminal press [ctrl + d].
Subscriber
You've learned that a topic is a channel where nodes can either write or read information. You've also seen that you can write into a topic using a publisher. A subscriber is a node that reads information from a topic.
#include <memory> // For std::make_shared and std::placeholders
#include "rclcpp/rclcpp.hpp" // The ROS 2 C++ client library
#include "std_msgs/msg/int32.hpp" // The Int32 message from std_msgs
class CounterSubscriber : public rclcpp::Node
{
public:
CounterSubscriber() : Node("counter_subscriber")
{
// Subscribe to /counter with QoS depth 10 and our callback
subscription_ = this->create_subscription<std_msgs::msg::Int32>(
"counter", 10,
std::bind(&CounterSubscriber::on_message, this, std::placeholders::_1));
}
private:
void on_message(const std_msgs::msg::Int32 & msg) const
{
// RCLCPP_INFO is the ROS 2 equivalent of ROS_INFO_STREAM
RCLCPP_INFO(this->get_logger(), "data: %d", msg.data);
}
rclcpp::Subscription<std_msgs::msg::Int32>::SharedPtr subscription_;
};
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<CounterSubscriber>()); // Keep running until Ctrl+C
rclcpp::shutdown();
return 0;
}Modify the CMakeLists.txt to include the subscriber:
cmake_minimum_required(VERSION 3.8)
project(cpp_tutorial)
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)
## Build ##
add_executable(publisher src/publisher.cpp)
ament_target_dependencies(publisher rclcpp std_msgs)
add_executable(subscriber src/subscriber.cpp)
ament_target_dependencies(subscriber rclcpp std_msgs)
## Install ##
install(TARGETS
publisher
subscriber
DESTINATION lib/${PROJECT_NAME})
ament_package()Build the Node
Navigate to the ros2_ws directory:
cd ~/ros2_wsBuild the workspace:
colcon build --packages-select cpp_tutorial --symlink-installSource the workspace:
source ./install/setup.bashCheck if the package was loaded successfully:
ros2 pkg list | grep cpp_tutorialRun the Subscriber
-
In ROS 2 you no longer need to start a
roscore. Just run each node in its own terminal. -
In
different terminals(sourceinstall/setup.bashin each one), run the following commands:
ros2 run cpp_tutorial publisherros2 run cpp_tutorial subscriberYou should see output similar to this:
[INFO] [counter_subscriber]: data: 188
[INFO] [counter_subscriber]: data: 189
[INFO] [counter_subscriber]: data: 190
[INFO] [counter_subscriber]: data: 191You made it! You created a publisher and a subscriber in C++! Now you can create your own nodes and make them communicate with each other.
Ordinary Wizarding Level
6 questions · 5 pts first try · 2 pts on a later try · +15 bonus for a clean sweep
Question 1 of 6
How often does this node publish?
timer_ = this->create_wall_timer(500ms, std::bind(&CounterPublisher::on_tick, this));