Comprehensive overview of unilink's architecture and design principles.
Table of Contents
- Overview
- Layered Architecture
- Core Components
- Design Patterns
- Threading Model
- Memory Management
- Error Handling
- Development & Tooling
Overview
Unilink is designed as a layered, modular communication library with clear separation of concerns. The architecture follows SOLID principles and employs modern C++ design patterns.
Design Goals
- Simplicity: Easy-to-use Builder API
- Safety: Memory-safe, thread-safe operations
- Performance: Asynchronous I/O, efficient resource management
- Flexibility: Modular design, optional features
- Reliability: Comprehensive error handling and recovery
Layered Architecture
graph TD
User[User Application Layer]
Builder[Builder API Layer<br/>TcpClientBuilder, TcpServerBuilder, etc.]
Wrapper[Wrapper API Layer<br/>TcpClient, TcpServer, Serial]
Transport[Transport Layer<br/>TcpTransport, SerialTransport]
Common[Common Utilities Layer<br/>Thread Safety, Memory Management, Logging]
Platform[Platform Layer<br/>Boost.Asio]
User --> Builder
Builder --> Wrapper
Wrapper --> Transport
Transport --> Common
Common --> Platform
Layer Responsibilities
1. Builder API Layer
- Purpose: Provide fluent, chainable interface
- Components:
TcpClientBuilder, TcpServerBuilder, SerialBuilder
- Responsibilities:
- Configuration validation
- Object construction
- Callback registration
2. Wrapper API Layer
- Purpose: High-level, easy-to-use interfaces
- Components:
TcpClient, TcpServer, Serial
- Responsibilities:
- Connection management
- Data transmission
- Lifecycle management
- Event callbacks
3. Transport Layer
- Purpose: Low-level communication implementation
- Components: Protocol-specific transports
- Responsibilities:
- Actual I/O operations
- Protocol handling
- Connection state management
4. Common Utilities Layer
- Purpose: Shared functionality
- Components: Thread safety, memory management, logging
- Responsibilities:
- Thread-safe operations
- Memory tracking
- Error handling
- Logging system
Core Components
1. Builder System
template<typename T>
class BuilderInterface { ... };
class TcpClientBuilder : public BuilderInterface<wrapper::TcpClient>;
class TcpServerBuilder : public BuilderInterface<wrapper::TcpServer>;
class SerialBuilder : public BuilderInterface<wrapper::Serial>;
}
Key Features:
- Method chaining
- Type-safe configuration
- Compile-time validation
- Auto-initialization support
2. Wrapper System
class ChannelInterface {
virtual void send(
const std::string& data) = 0;
virtual void start() = 0;
};
class
Serial :
public ChannelInterface;
}
virtual std::future< bool > start()=0
virtual void send(std::string_view data)=0
wrapper::TcpServer TcpServer
wrapper::TcpClient TcpClient
Key Features:
- Unified interface
- Automatic resource management
- Callback-based events
- Thread-safe operations
3. Transport System
class TcpServerSession { ... };
}
Key Features:
- Boost.Asio based
- Asynchronous I/O
- Retry logic (3s default interval, first retry at 100ms)
- Backpressure guard: callback at high watermark (default 1 MiB), hard cap triggers socket close + Error state
- Connection pooling (planned)
4. Common Utilities
template<typename T>
class ThreadSafeState;
template<typename T>
class AtomicState;
class MemoryPool;
class MemoryTracker;
class SafeDataBuffer;
class Logger;
class LogRotation;
class ErrorHandler;
}
Design Patterns
1. Builder Pattern
Purpose: Simplify object creation with many parameters
TcpClient(host, port, retry_interval, callbacks...);
client->start();
std::unique_ptr< wrapper::TcpClient > build() override
Build and return the configured product.
TcpClientBuilder & retry_interval(uint32_t milliseconds)
Set connection retry interval.
TcpClientBuilder & on_data(std::function< void(const wrapper::MessageContext &)> handler) override
Set data handler callback.
builder::TcpClientBuilder tcp_client(const std::string &host, uint16_t port)
Create a TCP client builder.
Benefits:
- Readable configuration
- Optional parameters
- Compile-time validation
- Method chaining
2. Dependency Injection
Purpose: Enable testability and flexibility
class ISerialPort {
virtual void open() = 0;
virtual void close() = 0;
virtual void async_read(...) = 0;
};
class RealSerialPort : public ISerialPort { ... };
class MockSerialPort : public ISerialPort { ... };
3. Observer Pattern
Purpose: Event notification system
std::function<void()> on_connect_;
std::function<void(const std::string&)> on_data_;
std::function<void()> on_disconnect_;
void notify_connect() {
if (on_connect_) on_connect_();
}
void notify_data(const std::string& data) {
if (on_data_) on_data_(data);
}
};
4. Singleton Pattern
Purpose: Global access to shared resources
class Logger {
public:
static Logger& instance() {
static Logger instance;
return instance;
}
private:
Logger() = default;
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
};
5. RAII (Resource Acquisition Is Initialization)
Purpose: Automatic resource management
stop();
}
std::unique_ptr<IoContext> io_context_;
std::shared_ptr<Connection> connection_;
};
6. Template Method Pattern
Purpose: Define algorithm structure with customizable steps
class ChannelInterface {
void start() {
before_start();
do_start();
after_start();
}
protected:
virtual void do_start() = 0;
virtual void before_start() {}
virtual void after_start() {}
};
Threading Model
Note: For a detailed execution model, see Runtime Behavior.
Overview
graph TD
User[User Thread Application<br/>- Calls API methods<br/>- Receives callbacks]
Queue[Thread-Safe Queue]
IO[I/O Thread Boost.Asio<br/>- Handles network I/O<br/>- Manages connections<br/>- Executes async operations]
User --> Queue
Queue --> IO
Thread Safety Guarantees
1. API Methods
All public API methods are thread-safe:
client->send("data1");
client->send("data2");
client->stop();
2. Callbacks
Callbacks are executed in the I/O thread context:
.on_data([](const std::string& data) {
});
3. Shared State
All shared state is protected:
ThreadSafeState<State> state_;
std::atomic<bool> connected_;
std::mutex write_mutex_;
};
IO Context Management
Shared Context (Default)
Pros:
- Efficient resource usage
- Single I/O thread handles all connections
Cons:
- All connections share same thread
Independent Context
TcpClientBuilder & use_independent_context(bool use_independent=true)
Use independent IoContext for this client.
Pros:
- Isolation between connections
- Useful for testing
Cons:
- More resource usage
- More threads
Memory Management
1. Smart Pointers
std::unique_ptr<TcpClient> client_;
std::shared_ptr<Session> session_;
std::weak_ptr<Connection> conn_;
2. Memory Pool
For high-performance scenarios, transports use a bucketed pool and an RAII buffer wrapper.
auto buf = GlobalMemoryPool::instance().acquire(4096);
GlobalMemoryPool::instance().release(std::move(buf), 4096);
common::PooledBuffer pooled(common::MemoryPool::BufferSize::MEDIUM);
if (pooled.valid()) {
}
void safe_memcpy(uint8_t *dest, const uint8_t *src, size_t size)
Safely copy memory with bounds checking.
Notes:
- Pool applies to moderate sizes (<=64KB); larger buffers fall back to regular allocations.
- PooledBuffer enforces bounds in debug paths;
.data() is validated before writes.
3. Memory Tracking
Debug memory leaks:
#ifdef UNILINK_ENABLE_MEMORY_TRACKING
class MemoryTracker {
void track_allocation(void* ptr, size_t size);
void track_deallocation(void* ptr);
void report_leaks();
};
#endif
4. Safe Data Buffer
Bounds-checked buffer:
class SafeDataBuffer {
std::vector<uint8_t> data_;
public:
void append(const std::string& str);
void append_bytes(const std::vector<uint8_t>& bytes);
};
std::string to_string(ErrorCode code)
Convert ErrorCode to human-readable string.
Error Handling
Error Propagation Flow
graph TD
Transport[Transport Layer Error<br/>Boost.Asio error_code]
Handler[Error Handler<br/>- Categorize<br/>- Log<br/>- Notify callbacks]
Callback[User Callback<br/>on_error]
Retry[Retry Logic<br/>if applicable]
Transport --> Handler
Handler --> Callback
Handler --> Retry
Error Categories
NETWORK,
VALIDATION
};
};
ErrorCategory
Error categories for classification.
ErrorLevel
Error severity levels.
Error Recovery Strategies
1. Automatic Retry
class RetryPolicy {
unsigned max_attempts{5};
unsigned interval_ms{3000};
bool exponential_backoff{false};
bool should_retry(unsigned attempt);
unsigned get_delay(unsigned attempt);
};
2. Circuit Breaker (Planned)
class CircuitBreaker {
enum class State { CLOSED, OPEN, HALF_OPEN };
State state_{State::CLOSED};
unsigned failure_threshold_{5};
std::chrono::seconds timeout_{30};
bool allow_request();
void record_success();
void record_failure();
};
Configuration System
Compile-Time Configuration
#ifdef UNILINK_ENABLE_CONFIG
namespace config {
class ConfigManager { ... };
}
#endif
#ifdef UNILINK_ENABLE_MEMORY_TRACKING
class MemoryTracker { ... };
#endif
Runtime Configuration
struct TcpClientConfig {
std::string host;
uint16_t port;
unsigned retry_interval_ms{3000};
};
auto config = ConfigManager::load_from_file("config.json");
auto client_config = config->get_tcp_client_config("my_client");
Performance Considerations
1. Asynchronous I/O
- Non-blocking operations
- Efficient event loop (Boost.Asio)
- Minimal context switching
2. Zero-Copy Operations
void send(std::string&& data);
void send(std::string_view data);
3. Connection Pooling (Planned)
class ConnectionPool {
std::queue<Connection*> available_;
std::vector<std::unique_ptr<Connection>> all_;
Connection* acquire();
void release(Connection* conn);
};
4. Memory Pooling
MemoryPool buffer_pool_(1024, 100);
auto buffer = buffer_pool_.allocate();
Extension Points
1. Custom Transports
class MyCustomTransport : public transport::TransportInterface {
void connect() override { }
void send(const std::string& data) override { }
};
2. Custom Builders
class MyCustomBuilder : public BuilderInterface<MyWrapper> {
std::unique_ptr<MyWrapper> build() override {
return std::make_unique<MyWrapper>();
}
};
3. Custom Error Handlers
ErrorHandler::instance().register_callback([](const ErrorInfo& error) {
send_to_monitoring_system(error);
if (error.level == ErrorLevel::CRITICAL) {
trigger_alert();
}
});
Development & Tooling
1. Docker-based Environment
Unilink provides a Docker Compose-based environment to maintain a consistent development environment across various platforms.
| Service | Purpose | Key Features |
dev | Development | Debug build, interactive shell provided |
test | Testing | Sanitizers (ASan/UBSan) and memory tracking enabled |
perf | Performance | Release build-based performance benchmarks |
docs | Documentation | Doxygen documentation generation and HTTP server (port 8000) |
prod | Production | Optimized production image build |
Usage Examples:
docker-compose up test # Run all tests and sanitizers
docker-compose up docs # Generate API docs and start server
2. Documentation Generation
You can generate API documentation directly from the source code using Doxygen.
# Direct generation via CMake
make docs
# Generation via Docker
docker-compose up docs
Testing Architecture
1. Dependency Injection
Serial(std::shared_ptr<ISerialPort> port) : port_(port) {}
private:
std::shared_ptr<ISerialPort> port_;
};
auto mock_port = std::make_shared<MockSerialPort>();
builder::SerialBuilder serial(const std::string &device, uint32_t baud_rate)
Create a Serial port builder.
2. Independent Contexts
3. State Verification
ASSERT_EQ(client->get_state(), State::CONNECTED);
ASSERT_TRUE(server->is_listening());
Summary
Unilink's architecture emphasizes:
✅ Modularity - Clear separation of concerns
✅ Safety - Memory-safe, thread-safe operations
✅ Performance - Asynchronous I/O, efficient resource management
✅ Testability - Dependency injection, mockable interfaces
✅ Extensibility - Easy to add new transports and features
✅ Maintainability - Clean code, well-documented
See Also:
- Design Patterns
- Threading Model
- ../guides/performance_tuning.md "Performance Guide"