Skip to content
CXO2 edited this page Jul 17, 2026 · 2 revisions

Overview

The Genode Network module provides client-side networking functionalities on top of the SFML Network module. It consists of two main components: Gx::Packet, which represents a unit of data to exchange with a server, and Gx::TcpNetworkClient, which manages the connection and transfers packets over TCP.

This module requires the sfml-network dependency in addition to the other SFML modules used by Genode.

Packet

The Gx::Packet class is a byte-oriented data container with stream operators for reading and writing primitive types, strings, and user-defined structures. It serves a similar purpose to sf::Packet, but it is designed for interoperability with existing network protocols rather than SFML-to-SFML communication.

Compared to sf::Packet, there are several notable differences:

  • Endianness is configurable. Data is written in little-endian byte order by default, and can be changed via SetEndianness(). sf::Packet always uses network byte order (big-endian).
  • Strings are null-terminated. Narrow strings (std::string, const char*) are written as raw bytes followed by a null terminator, and wide strings (std::wstring, const wchar_t*) are written as 16-bit code units followed by a null terminator. sf::Packet writes a 32-bit length followed by the characters instead.
  • Structures can be serialized automatically. Aggregate types are written and extracted field by field via Boost.PFR reflection, without requiring you to write operator<< / operator>> overloads for each type.
  • A size prefix can be attached. Use UsePrefix<T>() to override the size prefix used when the packet is sent through Gx::TcpNetworkClient. See Message framing for more details.

Unlike sf::Packet, the sf::String overloads are not included. Gx::Packet does not want to assume how a string should be interpreted on the wire (e.g., which encoding to use), so you must decide the representation yourself and convert sf::String to std::string or std::wstring explicitly before inserting it into a packet.

Note

This behavior may change in the future.

Reading and writing data

Use operator<< to append data into the packet and operator>> to extract data from it. The packet evaluates to false once an extraction fails (e.g., reading past the end of the packet).

struct LoginRequest
{
    std::string   Username;
    std::string   Token;
    std::uint32_t Version;
};

// Write: primitives and aggregate structs share the same syntax
auto packet = Gx::Packet();
packet << LoginRequest{"player", "auth-token", 1};

// Read
auto request = LoginRequest();
if (packet >> request)
{
    // All fields extracted successfully
}

Additional functions are available to inspect and manipulate the packet:

  • GetData() / GetDataSize(): Access the raw bytes stored in the packet.
  • Append(data, size) / Read(data, size): Write or read raw bytes directly.
  • GetReadPosition() / EndOfPacket(): Track the extraction state.
  • Clear(): Reset the packet to an empty state.

Similar to sf::Packet, you can derive from Gx::Packet and override the protected OnSend and OnReceive functions to transform the data right before it is sent or right after it is received (e.g., compression or encryption).

TcpNetworkClient

The Gx::TcpNetworkClient class manages a TCP connection and exchanges Gx::Packet objects with a server. The client performs connecting, sending, and receiving on background threads, while exposing a polling-based interface that fits the typical game loop: no callback is invoked from another thread, and all results are consumed from the thread that polls them.

Connecting

Connect() starts a connection attempt in the background and returns immediately. Use GetStatus() to observe the connection state; it returns sf::Socket::Status and rethrows the connection error (such as Gx::ConnectionException or Gx::TimeoutException) if the attempt has failed.

auto client = Gx::TcpNetworkClient();
client.Connect(sf::IpAddress::resolve("example.com").value(), 15010, sf::seconds(10));

// Sometime later (e.g., in the Update loop):
try
{
    switch (client.GetStatus())
    {
        case sf::Socket::Status::Done:     /* connected */         break;
        case sf::Socket::Status::NotReady: /* still connecting */  break;
        default:                           /* not connected */     break;
    }
}
catch (const Gx::NetworkException& ex)
{
    // Connection failed or timed out
}

Call Disconnect() to close the connection and stop the background threads. The destructor disconnects automatically.

Sending packets

Send() enqueues a packet to be transmitted by the background send thread and returns a Gx::Request handle that tracks the delivery:

auto packet = Gx::Packet();
packet << LoginRequest{"player", "auth-token", 1};

Gx::Request request = client.Send(packet);

// Sometime later:
switch (request->GetStatus())
{
    case Gx::RequestStatus::Pending:   /* still in the send queue */          break;
    case Gx::RequestStatus::Completed: /* handed over to the socket */        break;
    case Gx::RequestStatus::Failed:    /* transmission failed */              break;
    case Gx::RequestStatus::Cancelled: /* client stopped before it was sent */ break;
}

RequestTracker::GetStatus() rethrows the stored exception when the request has failed or was cancelled due to an error, allowing you to inspect the failure reason.

Receiving packets

Received packets are buffered internally by the background receive thread. Call Poll() to drain the buffered packets on the calling thread; it also rethrows any error captured by the receive thread (e.g., a malformed size prefix).

// Typically called once per frame
for (auto& packet : client.Poll())
{
    std::uint16_t opcode = 0;
    if (packet >> opcode)
    {
        // Dispatch the packet based on your protocol
    }
}

Tip

Gx::TcpNetworkClient pairs well with Gx::Events::EventDispatcher to dispatch polled packets to interested subscribers, keyed by an opcode or message id.

Message framing

TCP is a stream protocol with no message boundaries, so the client frames each packet with a size prefix: an unsigned integer written before the payload whose value is the total size of the packet, including the prefix itself. The receive thread reads the prefix, then reads the remaining bytes of the packet and surfaces it through Poll().

The default prefix is std::uint16_t. Use UseDefaultPrefix<T>() to change the prefix type for the connection; it must be called while the client is disconnected, otherwise Gx::InvalidOperationException is thrown.

auto client = Gx::TcpNetworkClient();
client.UseDefaultPrefix<std::uint32_t>(); // frame messages with a 32-bit size prefix

A packet can override the prefix used for its own transmission via Gx::Packet::UsePrefix<T>(). This only affects sending; received messages are always framed using the client default prefix.

Gx::ProtocolException is thrown when a packet exceeds the maximum size representable by the prefix type, or when a received prefix contains a malformed size.

Exceptions

All exceptions in this module derive from Gx::NetworkException, which itself derives from Gx::Exception:

  • Gx::ConnectionException: A socket operation failed unexpectedly; GetStatus() exposes the underlying sf::Socket::Status.
  • Gx::TimeoutException: The connection attempt did not complete within the specified timeout.
  • Gx::ProtocolException: The data on the wire does not conform to the framing protocol.

Clone this wiki locally