Transport
Transport lets a Softadastra C++ SDK client start an async TCP network runtime and connect to peers. Transport is optional. A client can use the local store, persistence, sync state, and manual ticks without enabling transport. Use transport when your application needs to connect one Softadastra node to another.
Include
#include <softadastra/sdk.hpp>What transport provides
Transport gives the SDK a network layer for peer communication.
With transport enabled, an application can:
start the transport runtime
check if transport is running
connect to a peer
disconnect from a peer
process queued transport events
advance transport work during tick()The public API stays simple:
client.start_transport();
client.transport_running();
client.connect(peer);
client.disconnect(peer);
client.process_transport_events();
client.stop_transport();Transport is optional
A client created without transport can still write, read, persist, recover, inspect sync state, and run manual ticks.
Client client{
ClientOptions::persistent(
"my-app",
"data/my-app.wal"
)
};Transport methods require transport to be enabled in ClientOptions.
If transport is not enabled, transport methods return an error.
transport_error: transport is disabledEnable transport
Use with_local_transport() for local development.
ClientOptions options = ClientOptions::memory_only("node-1").with_local_transport(9100);This enables transport on:
127.0.0.1:9100Then create and open the client.
Client client{options};
const auto opened = client.open();
if (opened.is_err())
{
return 1;
}Start transport
After opening the client, call start_transport().
const auto started = client.start_transport();
if (started.is_err())
{
std::cerr << started.error().code_string() << ": "
<< started.error().message() << "\n";
return 1;
}Check the running state:
if (client.transport_running())
{
std::cout << "transport is running\n";
}Stop transport
Use stop_transport() to stop the transport service.
client.stop_transport();You can check the state again:
std::cout << "transport running: " << (client.transport_running() ? "yes" : "no") << "\n";Peer
A peer represents another Softadastra node.
const Peer peer = Peer::local("node-2", 9101);A valid peer must have:
non-empty node id
non-empty host
non-zero portFor localhost development, use:
Peer::local("node-2", 9101);This creates a peer pointing to:
127.0.0.1:9101Connect to a peer
Use connect() to connect to another peer.
const Peer peer = Peer::local("node-2", 9101);
const auto connected = client.connect(peer);
if (connected.is_err())
{
std::cerr << connected.error().code_string() << ": " << connected.error().message();
if (connected.error().has_context())
{
std::cerr << " (" << connected.error().context() << ")";
}
std::cerr << "\n";
}If the peer is invalid, the SDK returns:
invalid_argument: invalid peerIf the connection fails, the SDK returns a transport error.
Disconnect from a peer
Use disconnect() to disconnect from a peer.
const auto disconnected = client.disconnect(peer);
if (disconnected.is_err())
{
std::cerr << disconnected.error().code_string() << ": " << disconnected.error().message() << "\n";
}Process transport events
The async transport backend can queue events.
Use process_transport_events() to let the SDK process them explicitly.
const auto processed = client.process_transport_events(64);
if (processed.is_ok())
{
std::cout << "processed events: " << processed.value() << "\n";
}This is useful after:
start_transport()
connect()
disconnect()
tick()
a wait loop
a manual event loopTransport and tick
When transport is enabled, tick() also processes a small number of queued transport events.
const auto tick = client.tick();
if (tick.is_err())
{
return 1;
}This makes transport usable in applications that manually drive their runtime loop.
Basic transport example
#include <softadastra/sdk.hpp>
#include <iostream>
int main()
{
using namespace softadastra::sdk;
ClientOptions options = ClientOptions::memory_only("example-transport").with_local_transport(9100);
Client client{options};
const auto opened = client.open();
if (opened.is_err())
{
std::cerr << "open failed: " << opened.error().code_string() << ": "
<< opened.error().message() << "\n";
return 1;
}
const auto started = client.start_transport();
if (started.is_err())
{
std::cerr << "start_transport failed: " << started.error().code_string() << ": "
<< started.error().message() << "\n";
client.close();
return 1;
}
std::cout << "transport_running : " << (client.transport_running() ? "yes" : "no") << "\n";
const Peer peer = Peer::local("example-peer", 9101);
const auto connected = client.connect(peer);
if (connected.is_err())
{
std::cerr << "connect failed: " << connected.error().code_string() << ": " << connected.error().message();
if (connected.error().has_context())
{
std::cerr << " (" << connected.error().context() << ")";
}
std::cerr << "\n";
}
else
{
std::cout << "connected peer: " << peer.node_id() << " " << peer.host() << ":" << peer.port() << "\n";
}
client.stop_transport();
std::cout << "transport_running : " << (client.transport_running() ? "yes" : "no") << "\n";
client.close();
return 0;
}Two-node local example
Transport is easiest to understand with two local nodes.
Node 1 listens on port 9100.
ClientOptions node1_options = ClientOptions::memory_only("node-1").with_local_transport(9100);
Client node1{node1_options};
node1.open();
node1.start_transport();Node 2 listens on port 9101.
ClientOptions node2_options = ClientOptions::memory_only("node-2").with_local_transport(9101);
Client node2{node2_options};
node2.open();
node2.start_transport();Node 1 can connect to node 2.
const Peer node2_peer = Peer::local("node-2", 9101);
const auto connected = node1.connect(node2_peer);After connect, process transport events.
node1.process_transport_events();
node2.process_transport_events();Complete two-node example
#include <softadastra/sdk.hpp>
#include <iostream>
int main()
{
using namespace softadastra::sdk;
Client node1{
ClientOptions::memory_only("node-1")
.with_local_transport(9100)
};
Client node2{
ClientOptions::memory_only("node-2")
.with_local_transport(9101)
};
const auto opened1 = node1.open();
if (opened1.is_err())
{
std::cerr << "node1 open failed: " << opened1.error().message() << "\n";
return 1;
}
const auto opened2 = node2.open();
if (opened2.is_err())
{
std::cerr << "node2 open failed: " << opened2.error().message() << "\n";
node1.close();
return 1;
}
const auto started1 = node1.start_transport();
if (started1.is_err())
{
std::cerr << "node1 transport failed: " << started1.error().message() << "\n";
node2.close();
node1.close();
return 1;
}
const auto started2 = node2.start_transport();
if (started2.is_err())
{
std::cerr << "node2 transport failed: " << started2.error().message() << "\n";
node1.stop_transport();
node2.close();
node1.close();
return 1;
}
const Peer node2_peer = Peer::local("node-2", 9101);
const auto connected = node1.connect(node2_peer);
if (connected.is_err())
{
std::cerr << "connect failed: " << connected.error().code_string() << ": "
<< connected.error().message() << "\n";
}
else
{
std::cout << "node-1 connected to node-2\n";
}
node1.process_transport_events();
node2.process_transport_events();
node2.stop_transport();
node1.stop_transport();
node2.close();
node1.close();
return 0;
}Transport with persistent storage
Transport can be used with persistent storage.
ClientOptions options = ClientOptions::persistent(
"node-1",
"data/node-1.wal"
).with_local_transport(9100);This gives the node:
WAL-backed local storage
sync pipeline
async TCP transport
node metadataTransport with metadata
You can give the node a human-readable display name.
ClientOptions options = ClientOptions::memory_only("node-1")
.with_local_transport(9100)
.with_metadata("Local Node 1", "0.1.0");Then read it later:
const auto info = client.node_info();
if (info.is_ok())
{
std::cout << info.value().label() << "\n";
}Common errors
Transport is disabled
This happens when a transport method is called without enabling transport.
transport_error: transport is disabledFix:
ClientOptions options = ClientOptions::memory_only("node-1").with_local_transport(9100);Transport is not initialized
This means transport was enabled but the runtime was not built correctly.
transport_error: transport is not initializedCheck that client.open() succeeded.
Client is not open
Transport requires an open client.
invalid_state: SDK client is not openFix:
const auto opened = client.open();
if (opened.is_err())
{
return 1;
}Invalid peer
This happens when the peer has an empty node id, empty host, or zero port.
invalid_argument: invalid peerFix:
const Peer peer =
Peer::local("node-2", 9101);When to use transport
Use transport when your application needs network communication between Softadastra nodes.
Good use cases:
local peer-to-peer experiments
node-to-node synchronization
LAN-first applications
manual sync runtimes
tools that need explicit peer connectionsDo not enable transport if your application only needs local storage and restart recovery.
Summary
Transport is optional.
Enable it with:
ClientOptions::memory_only("node-1").with_local_transport(9100);Start it with:
client.start_transport();Connect to peers with:
client.connect(Peer::local("node-2", 9101));Process events with:
client.process_transport_events();Stop it with:
client.stop_transport();Next, continue with Discovery.