Remote procedure calls require transferring three core data elements: target service identifiers, method signatures, and call parameters. These data are first encoded into a contiguous byte stream before transmission to the remote service host. Upon receiving the byte stream, the server parses it back to the original service, method, and parameter data to execute local invocation, then serializes the execution result back to a byte stream to return to the caller, who deserializes it to get the final response data.
The Serializer.hpp component is responsible for implementing this bidirectional conversion between native data structures and transportable byte streams, alongside managing byte stream read/write operations.
StreamBuffer Class
The StreamBuffer class extends std::vector<char> to act as a dedicated byte buffer for serialization operations. While std::vector<char> natively supports dynamic resizing and contiguous storage of byte data, it lacks built-in utilities for common byte stream operations such as tracking the current read/write pointer, scanning for specific byte sequences, and detecting end-of-buffer conditions. StreamBuffer adds these missing capabilities while retaining all the dynamic storage benefits of the underlying std::vector<char>.
Serializer Class
The Seiralizer is the core class handling both serialization and deserialization workflows. For write operations, it ensures byte order consistency before writing data to the buffer. For read operations, it reads specified byte lengths from the buffer and converts byte order to match the host system before reconstructing native data types.
Private Member Variables
private:
int m_endian_flag; // Specifies the endianness (big-endian or little-endian) used for stored byte data
StreamBuffer m_buf; // Underlying byte stream buffer instance
Core Methods
Serialization Logic
template<typename T>
inline void Serializer::write_val(T val)
{
const size_t data_size = sizeof(T);
char* temp_buf = new char[data_size];
const char* raw_data = reinterpret_cast<const char*>(std::addressof(val));
std::memcpy(temp_buf, raw_data, data_size);
adjust_endianness(temp_buf, data_size); // Convert to preconfigured transmission endianness
m_buf.append(temp_buf, data_size);
delete[] temp_buf;
}
Deserialization Logic
template<typename T>
inline void Serializer::read_val(T& out_val)
{
const size_t data_size = sizeof(T);
char* temp_buf = new char[data_size];
if (!m_buf.reached_end()) {
std::memcpy(temp_buf, m_buf.get_curr_ptr(), data_size);
m_buf.advance_ptr(data_size);
adjust_endianness(temp_buf, data_size); // Convert from transmission endianness to host endianness
out_val = *reinterpret_cast<T*>(temp_buf);
}
delete[] temp_buf;
}
For composite types such as custom structs, the serialization process converts the entire memory layout of the struct instance into a contiguous char array, while deserialization maps the received char array back to the original struct type by interpreting the bytes according to the struct's defined memory layout.