Sending Messages Between NUClear Nodes¶
How to send typed messages between NUClear instances on different machines.
Overview¶
NUClear's built-in networking layer handles node discovery, serialization, and type-safe delivery.
You emit a configuration to join the network, then send and receive messages with familiar emit and on patterns.
sequenceDiagram
participant A as Node "alice"
participant M as Multicast Group
participant B as Node "bob"
A->>M: Announce presence
B->>M: Announce presence
M-->>A: Discover "bob"
M-->>B: Discover "alice"
Note over A,B: Nodes connected
A->>B: emit<Scope::NETWORK>(SensorData)
B->>B: on<Network<SensorData>> fires
B->>A: emit<Scope::NETWORK>(Command)
A->>A: on<Network<Command>> fires
1. Configure Networking¶
Emit a NetworkConfiguration message to start the networking subsystem:
#include "nuclear"
class NetworkSetup : public NUClear::Reactor {
public:
NetworkSetup(std::unique_ptr<NUClear::Environment> environment)
: Reactor(std::move(environment)) {
on<Startup>().then([this] {
emit(std::make_unique<NUClear::message::NetworkConfiguration>(
"alice", // Node name (unique on the network)
"239.226.152.162", // Multicast announce address
7447 // Announce port
));
});
}
};
NetworkConfiguration Fields¶
| Field | Type | Default | Description |
|---|---|---|---|
name |
string |
— | Unique name for this node on the network |
announce_address |
string |
— | Address for node discovery announcements |
announce_port |
uint16_t |
— | Port for announce messages |
bind_address |
string |
"" (all) |
Local interface to bind to |
mtu |
uint16_t |
1500 |
Maximum transmission unit (fragments if larger) |
Network Modes¶
NUClearNet supports several discovery modes depending on the announce_address you configure:
| Mode | Address Type | Example | Use Case |
|---|---|---|---|
| Multicast IPv4 | 239.x.x.x |
239.226.152.162 |
LAN discovery, multiple nodes |
| Multicast IPv6 | ff02::x |
ff02::1 |
IPv6 LAN discovery |
| Broadcast IPv4 | x.x.x.255 |
192.168.1.255 |
Simple LAN, all nodes on subnet |
| Unicast IPv4/IPv6 | Specific IP | 192.168.1.50 |
Point-to-point, two nodes |
Multicast (Default)¶
Multicast is the most common mode. All nodes join a multicast group and discover each other automatically:
emit(std::make_unique<NUClear::message::NetworkConfiguration>(
"my-node", "239.226.152.162", 7447));
The default multicast address 239.226.152.162 with port 7447 is a conventional choice.
All nodes that share the same announce address and port will discover each other.
Broadcast¶
For simpler networks, use a broadcast address. All nodes on the subnet will receive announce messages:
Unicast (Point-to-Point)¶
For direct connections between exactly two nodes, use unicast. Each node sets its announce address to the other node's IP:
// On Node A (IP: 192.168.1.10)
emit(std::make_unique<NUClear::message::NetworkConfiguration>(
"node-a", "192.168.1.20", 7447)); // Point to Node B
// On Node B (IP: 192.168.1.20)
emit(std::make_unique<NUClear::message::NetworkConfiguration>(
"node-b", "192.168.1.10", 7447)); // Point to Node A
In unicast mode, each peer announces directly to the other. This is useful when multicast/broadcast is unavailable (e.g., across subnets or VPNs).
2. Send Messages¶
Use emit<Scope::NETWORK> to broadcast a message to all connected nodes:
// Send to all nodes (unreliable)
emit<Scope::NETWORK>(std::make_unique<SensorData>(reading));
// Send to a specific node by name (unreliable)
emit<Scope::NETWORK>(std::make_unique<SensorData>(reading), "bob");
// Send reliably (retransmits until acknowledged)
emit<Scope::NETWORK>(std::make_unique<SensorData>(reading), "bob", true);
// Send reliably to all nodes
emit<Scope::NETWORK>(std::make_unique<SensorData>(reading), true);
3. Receive Messages¶
Use on<Network<T>> to react to messages arriving from the network:
on<Network<SensorData>>().then(
[this](const std::shared_ptr<NetworkSource>& src, const SensorData& data) {
// src->name - name of the sending node
// src->reliable - whether this was sent reliably
log("Received sensor data from", src->name);
});
The callback receives:
std::shared_ptr<NetworkSource>— metadata about the sender (name, address, reliability flag)const T&— the deserialized message
4. Handle Join/Leave Events¶
React to nodes joining or leaving the network:
on<Trigger<NUClear::message::NetworkJoin>>().then([this](const NUClear::message::NetworkJoin& join) {
log("Node joined:", join.name);
});
on<Trigger<NUClear::message::NetworkLeave>>().then([this](const NUClear::message::NetworkLeave& leave) {
log("Node left:", leave.name);
});
5. Complete Two-Node Example¶
Shared message type¶
Node A — Sender¶
#include "nuclear"
#include "Messages.hpp"
class SensorNode : public NUClear::Reactor {
public:
SensorNode(std::unique_ptr<NUClear::Environment> environment)
: Reactor(std::move(environment)) {
on<Startup>().then([this] {
emit(std::make_unique<NUClear::message::NetworkConfiguration>(
"sensor-node", "239.226.152.162", 7447));
});
on<Every<1, std::chrono::seconds>>().then([this] {
auto msg = std::make_unique<SensorData>();
msg->temperature = read_sensor();
msg->humidity = read_humidity();
emit<Scope::NETWORK>(msg);
});
}
};
Node B — Receiver¶
#include "nuclear"
#include "Messages.hpp"
class DashboardNode : public NUClear::Reactor {
public:
DashboardNode(std::unique_ptr<NUClear::Environment> environment)
: Reactor(std::move(environment)) {
on<Startup>().then([this] {
emit(std::make_unique<NUClear::message::NetworkConfiguration>(
"dashboard", "239.226.152.162", 7447));
});
on<Network<SensorData>>().then(
[this](const std::shared_ptr<NetworkSource>& src, const SensorData& data) {
log("From", src->name, "- temp:", data.temperature, "humidity:", data.humidity);
});
on<Trigger<NUClear::message::NetworkJoin>>().then([this](const auto& join) {
log("Sensor connected:", join.name);
});
}
};
Reliable vs Unreliable Delivery¶
| Mode | Behavior | Use when |
|---|---|---|
| Unreliable | Fire-and-forget. No retransmission. Lowest latency. | Streaming data, periodic updates |
| Reliable | Retransmits until acknowledged. Delivery guaranteed. | Commands, configuration, events |
Pass true as the reliability argument to emit<Scope::NETWORK>:
// Unreliable (default)
emit<Scope::NETWORK>(std::make_unique<Command>(cmd));
// Reliable
emit<Scope::NETWORK>(std::make_unique<Command>(cmd), true);
Serialization Requirements¶
Types sent over the network must be serializable. NUClear handles this automatically for trivially copyable types (POD structs with no pointers or dynamic memory).
For complex types, specialize NUClear::util::serialise::Serialise<T> to provide custom serialise(), deserialise(), and hash() methods.
Type safety across nodes is ensured by hash matching — if a type's hash doesn't match between sender and receiver, the message is silently discarded.