Skip to content

Mock Socket design

Socket Backend Design

Every socket backend in this project must follow a clear and consistent convention. This ensures that all backends are compatible with the socket manager and can be easily maintained or extended. The convention is based on two main components:

  1. Socket Definition: Specifies how to instantiate a socket, and defines the types for messages and parameters.
  2. Backend Definition: Specifies which socket type is used, the message type, and the parameter structure for socket creation. It also defines how the backend interacts with the serialization system (Datastream).

1. Socket Definition

A socket is defined by a class that must provide: - The type of message it will send and receive. - The type of parameters required to create the socket.

Example:

/// @brief Structure containing parameters needed to create a socket for your backend
struct YourSocketParam{};

/// @brief Example of a socket definition
class YourSocketType {
public:
    /// Type of message used by the PAbstractSocketManager
    typedef DataStreamMsg Message;
    /// Type of extra parameters used to create the socket
    typedef YourSocketParam Param;
};

Explanation: - Message: This typedef defines the format of the data exchanged by the socket. For example, it could be a serialized data stream or a vector of bytes. - Param: This typedef points to a structure containing any additional parameters needed to create the socket. Do not include hostname or port in this structure; these are handled separately.


2. Backend Definition

A backend is defined by a class that must provide: - The socket type it uses. - The message type (should match the socket’s message type). - The parameter type (should match the socket’s parameter type).

Example:

/// @brief Backend and its parameters definition
class YourBackend {
public:
    /// The socket type used by this backend
    typedef YourSocketType Socket;
    /// The message type used by this backend
    typedef DataStreamMsg Message;
    /// The parameter type used to create the socket
    typedef YourSocketParam Param;
};

Explanation: - Socket: Refers to the socket type defined above. - Message: Must be the same as the socket’s message type. - Param: Must be the same as the socket’s parameter type.


Key Points to Respect

  • Socket: Must define at least the Message and Param typedefs.
  • Message: Should match the expected format for the socket manager (often a serialized type like DataStreamMsg).
  • Param: Should only include parameters specific to the socket implementation. Do not include hostname or port fields.

Note: In some backends (e.g., ZMQ), the message type is often a vector of bytes (e.g., std::vector<uint8_t>) wrapped in a type like DataStreamMsg.


Global backend definition

// Parameters specific to the socket
struct YourSocketParam{};

// Socket type definition
class YourSocketType {
public:
    typedef DataStreamMsg Message;
    typedef YourSocketParam Param;
};

// Backend definition
class YourBackend {
public:
    typedef YourSocketType Socket;
    typedef DataStreamMsg Message;
    typedef YourSocketParam Param;
};

By following this convention, every backend remains compatible with the generic socket manager, and it becomes easy to adapt the system to new protocols or requirements.

Generic Socket Design

The core of PhoenixSocket is built around the concept of a generic socket (PGenericSocket). This design allows you to abstract socket operations and easily switch between real and mock backends, making development, testing, and integration much simpler and more robust.

Backend Types

PhoenixSocket supports three main backend types:

  • MockBackend: Simulates socket behavior for testing purposes. The mock backend reads from or writes to mock files depending on whether the socket is in send or receive mode. This enables unit tests without requiring a real network connection.
  • RealBackend: Implements the actual socket logic, handling real network communication and errors (for example, sending messages using ZMQ sockets).
  • GenericSocket: Acts as a bridge between the mock and real backends. It instantiate two sockets (a real one and a mock one) and allows you to switch dynamically between mock and real modes (such as NO_MOCK and MOCK) depending on your use case.

Architecture

Note: A mock backend implementation is already provided in this project, PMockBackend, and can be used directly with the generic socket design.

Declaration of the Generic Socket

The generic socket is declared as follows:

/// @brief Abstract socket with mock mode to avoid heavy socket backend for unit tests
template<typename _TBackend, typename _TMockBackend>
class PGenericSocket
  • _TBackend: The type of the real backend (e.g., a ZMQ backend, Unix socket backend, etc.).
  • _TMockBackend: The type of the mock backend (e.g., a file-based mock backend).

How It Works

PGenericSocket internally manages both a real backend and a mock backend. Depending on the selected mode, it delegates send and receive operations to the appropriate backend:

  • NO_MOCK: Only the real backend is used for all operations.
  • MOCK: Only the mock backend is used, simulating network operations via file I/O.
  • MOCK_RECORD: The real backend is used for communication, but all messages are also recorded using the mock backend for later replay or analysis.

You can switch modes dynamically using the provided API (e.g., setMode).

Benefits

  • Flexibility: Easily add new real or mock backends.
  • Testing: Run unit tests without a real network, using mock data.
  • Recording and Replay: Capture real communication for reproducible tests or debugging.
  • Consistent API: The same interface is used for all backend types, simplifying integration.

By following this generic socket design, PhoenixSocket ensures that your codebase remains maintainable, testable, and adaptable to new protocols or requirements.