DEV Community

Cover image for From TCP Sockets to Thread Pools - Building a Production Grade C++ Web Framework
Prakash Dass
Prakash Dass

Posted on

From TCP Sockets to Thread Pools - Building a Production Grade C++ Web Framework

From TCP Sockets to Thread Pools: Building a Production-Grade C++ Web Framework

Note: This is an advanced technical deep-dive into systems programming concepts. We'll explore TCP/IP networking, multithreading primitives, asynchronous request handling, and production-grade architectural patterns in C++.

Table of Contents

  1. Introduction
  2. Architecture Overview
  3. TCP/IP Socket Programming
  4. POSIX Thread Pool Implementation
  5. HTTP Protocol Layer
  6. Routing Engine Architecture
  7. Design Patterns & Architectural Decisions
  8. Asynchronous Request Processing Flow
  9. CLI Configuration & Extensibility
  10. Conclusion

Introduction

Modern web servers are complex systems that orchestrate multiple OS-level concepts: socket programming, multithreading, synchronization primitives, and protocol handling. In this article, we'll dissect NanoHost, a lightweight yet powerful C++ web framework that demonstrates these concepts in production-quality code.

What makes this interesting?

  • Pure C++23 with POSIX compliance
  • Thread pool pattern for the C10K problem
  • Non-blocking I/O for high concurrency
  • Zero external dependencies for core functionality
  • Express.js-inspired API design

Source Code: github.com/rprakashdass/nanohost

Architecture

Core Components

Component Responsibility OS Concepts Used
Socket Layer TCP/IP networking socket(), bind(), listen(), accept()
ThreadPool Concurrent request handling POSIX threads, mutexes, condition variables
HTTPRequest Protocol parsing String processing, state machines
Router Request dispatching Hash maps, function pointers
AppDispatcher Strategy orchestration Strategy pattern, dependency injection
StaticServer File serving File I/O, MIME type detection

TCP/IP Socket Programming

Server Socket Initialization

The foundation of any web server is the socket - an OS-level abstraction for network communication. Let's break down the initialization process:

// 1. Create a TCP socket
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1) {
    std::cerr << "[Error] Couldn't create socket" << std::endl;
    return 1;
}
Enter fullscreen mode Exit fullscreen mode

What's happening here?

  • AF_INET: Address Family - Internet (IPv4)
  • SOCK_STREAM: Type - TCP (reliable, connection-oriented)
  • 0: Protocol - Default TCP protocol

Socket Options for Production

// Allow immediate socket reuse (important for rapid restarts)
int opt = 1;
setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
Enter fullscreen mode Exit fullscreen mode

Why SO_REUSEADDR?
Without this, after stopping your server, the OS keeps the port in TIME_WAIT state for ~60 seconds. This prevents immediate restarts - critical in development and deployment scenarios.

Non-Blocking I/O Configuration

// Configure socket for non-blocking operation
int flags = fcntl(server_socket, F_GETFL, 0);
fcntl(server_socket, F_SETFL, flags | O_NONBLOCK);
Enter fullscreen mode Exit fullscreen mode

Non-blocking vs Blocking I/O:

Blocking I/O Non-Blocking I/O
Thread waits for data Returns immediately with EAGAIN
Simple programming model Requires polling/event loops
Limited concurrency High concurrency support
One thread per connection Thousands of connections per thread

Binding and Listening

// Bind socket to address and port
sockaddr_in sockAddr{};
sockAddr.sin_family = AF_INET;
sockAddr.sin_port = htons(port);              // Convert to network byte order
sockAddr.sin_addr.s_addr = INADDR_ANY;        // Listen on all interfaces

bind(server_socket, (sockaddr*)&sockAddr, sizeof(sockAddr));

// Start listening with backlog of 10 pending connections
listen(server_socket, 10);
Enter fullscreen mode Exit fullscreen mode

Network Byte Order:
Different CPU architectures store multi-byte values differently (endianness). htons() (Host TO Network Short) ensures consistent byte ordering across network.

Accepting Connections

while (keepRunning) {
    sockaddr_in client_addr{};
    socklen_t client_len = sizeof(client_addr);

    int client_socket = accept(server_socket, 
                               (sockaddr*)&client_addr, 
                               &client_len);

    if (client_socket == -1) {
        if(errno == EAGAIN || errno == EWOULDBLOCK) {
            // No connection available, sleep briefly to avoid busy-waiting
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
            continue;
        }
        // Handle other errors
    }

    // Enqueue connection to thread pool
    pool.enqueueTask([client_socket, &app]() {
        handleConnection(client_socket, app);
    });
}
Enter fullscreen mode Exit fullscreen mode

Key Design Decision:
The main thread only accepts connections and delegates work to the thread pool. This prevents blocking on slow client operations.


POSIX Thread Pool Implementation

The C10K Problem

Traditional thread-per-connection models fail at scale:

  • Memory overhead: Each thread consumes ~2-8MB of stack space
  • Context switching: OS overhead switching between thousands of threads
  • Thread creation cost: Creating/destroying threads is expensive

Solution: Thread Pool Pattern

Pre-create a fixed number of worker threads that process tasks from a queue.

ThreadPool Design

class ThreadPool {
public:
    ThreadPool(size_t numberOfThreads);
    ~ThreadPool();

    void enqueueTask(std::function<void()> task);
    void waitAll();

private:
    std::atomic<bool> stop;
    std::mutex taskQueueMtx;
    std::condition_variable condition;
    std::queue<std::function<void()>> tasksQueue;
    std::vector<std::thread> workerThreads;

    std::atomic<int> activeTasks;
    std::mutex waitMtx;
    std::condition_variable tasksDoneCondition;

    void workerTask();
};
Enter fullscreen mode Exit fullscreen mode

Initialization: Creating Worker Threads

ThreadPool::ThreadPool(size_t numberOfThreads) 
    : stop(false), activeTasks(0) {

    for(size_t i = 0; i < numberOfThreads; i++) {
        workerThreads.emplace_back([this]() {
            this->workerTask();
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Lambda Capture [this]:
Captures the thread pool object pointer, allowing worker threads to access member variables safely.

Producer-Consumer Pattern with Condition Variables

void ThreadPool::enqueueTask(std::function<void()> task) {
    {
        std::unique_lock<std::mutex> lock(taskQueueMtx);
        tasksQueue.push(task);
    }  // Lock released here

    condition.notify_one();  // Wake up one sleeping worker
}
Enter fullscreen mode Exit fullscreen mode

Why notify outside the lock?
Reduces contention - the woken thread can immediately acquire the lock without competing with the enqueueing thread.

Worker Thread Event Loop

void ThreadPool::workerTask() {
    while(true) {
        std::function<void()> task;
        {
            std::unique_lock<std::mutex> lock(taskQueueMtx);

            // Wait until: task available OR shutdown requested
            condition.wait(lock, [this]() {
                return stop || !tasksQueue.empty();
            });

            if(stop && tasksQueue.empty()) return;  // Graceful shutdown

            task = std::move(tasksQueue.front());
            tasksQueue.pop();
        }  // Release lock before executing task

        activeTasks++;
        task();  // Execute outside lock - allows concurrent execution
        activeTasks--;

        // Notify waiters if all tasks complete
        if(tasksQueue.empty() && activeTasks == 0) {
            tasksDoneCondition.notify_all();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Synchronization Primitives Explained

Mutex (Mutual Exclusion):

std::mutex taskQueueMtx;  // Protects shared queue
Enter fullscreen mode Exit fullscreen mode

Ensures only one thread accesses the queue at a time.

Condition Variable:

std::condition_variable condition;
Enter fullscreen mode Exit fullscreen mode

Efficient thread sleeping/waking mechanism. Threads sleep until signaled, avoiding busy-waiting.

Atomic Variables:

std::atomic<bool> stop;
std::atomic<int> activeTasks;
Enter fullscreen mode Exit fullscreen mode

Lock-free synchronization for simple counters/flags. Hardware-supported atomic operations.

Graceful Shutdown

ThreadPool::~ThreadPool() {
    {
        std::lock_guard<std::mutex> lock(taskQueueMtx);
        stop = true;
    }

    condition.notify_all();  // Wake all workers

    for(auto &worker: workerThreads) {
        if(worker.joinable())
            worker.join();  // Wait for thread to finish
    }
}
Enter fullscreen mode Exit fullscreen mode

RAII (Resource Acquisition Is Initialization):
Destructor automatically cleans up threads - no manual cleanup needed.


HTTP Protocol Layer

HTTP Request Structure

GET /api/users HTTP/1.1          ← Request Line
Host: localhost:11111            ← Headers
Content-Type: application/json
Content-Length: 42

{"user": "john"}                 ← Body
Enter fullscreen mode Exit fullscreen mode

Parsing Implementation

HTTPRequest HTTPRequest::parse(const std::string& rawRequest) {
    HTTPRequest req;
    std::istringstream iss(rawRequest);
    std::string line;

    // 1. Parse Request Line: "GET /path HTTP/1.1"
    std::getline(iss, line);
    std::istringstream firstLine(line);
    firstLine >> req.method >> req.path >> req.version;

    // 2. Parse Headers: "Key: Value"
    while(std::getline(iss, line) && line != "\r") {
        size_t colonPos = line.find(":");
        if(colonPos != std::string::npos) {
            std::string header = line.substr(0, colonPos);
            std::string value = line.substr(colonPos+1);

            // Clean whitespace and carriage returns
            value.erase(std::remove(value.begin(), value.end(), '\r'), 
                       value.end());
            value.erase(0, value.find_first_not_of(" "));

            req.headers[header] = value;
        }
    }

    // 3. Parse Body
    std::string bodyLine, bodyData;
    while(std::getline(iss, bodyLine)) {
        if(!bodyLine.empty() && bodyLine.back() == '\r') {
            bodyLine.pop_back();
        }
        bodyData.append(bodyLine + "\n");
    }
    req.body = bodyData;

    return req;
}
Enter fullscreen mode Exit fullscreen mode

State Machine Approach:
The parser operates in three states:

  1. Request Line → Extract method, path, version
  2. Headers → Parse until empty line
  3. Body → Read remaining data

HTTP Response Generation

class HTTPResponse {
    int statusCode;
    std::string statusMessage;
    std::unordered_map<std::string, std::string> headers;
    std::string body;

public:
    std::string to_string() const {
        std::ostringstream oss;

        // Status line
        oss << "HTTP/1.1 " << statusCode << " " 
            << statusMessage << "\r\n";

        // Headers
        for (const auto& [key, value] : headers) {
            oss << key << ": " << value << "\r\n";
        }

        // Empty line separator
        oss << "\r\n";

        // Body
        oss << body;

        return oss.str();
    }

    static HTTPResponse ok(int code, const std::string& body) {
        return HTTPResponse(code, body);
    }

    static HTTPResponse error(int code, const std::string& message) {
        return HTTPResponse(code, message);
    }
};
Enter fullscreen mode Exit fullscreen mode

Factory Pattern:
Static factory methods provide clean API:

return HTTPResponse::ok(200, jsonData);
return HTTPResponse::error(404, "Not Found");
Enter fullscreen mode Exit fullscreen mode

MIME Type Detection

static const std::unordered_map<std::string, std::string> mimeTypes = {
    { ".html", "text/html" },
    { ".css",  "text/css" },
    { ".js",   "application/javascript" },
    { ".json", "application/json" },
    { ".png",  "image/png" },
    { ".jpg",  "image/jpeg" },
    { ".pdf",  "application/pdf" },
    // ... comprehensive list
};

std::string getMimeType(const std::string& filePath) {
    size_t dotPos = filePath.rfind('.');
    if (dotPos != std::string::npos) {
        std::string ext = filePath.substr(dotPos);
        auto it = mimeTypes.find(ext);
        if (it != mimeTypes.end()) {
            return it->second;
        }
    }
    return "application/octet-stream";  // Default for unknown
}
Enter fullscreen mode Exit fullscreen mode

Performance Note:
Static hash map provides O(1) lookup time. Initialized once, shared across all requests.


Routing Engine Architecture

Dual Routing Strategy

NanoHost supports two routing paradigms:

  1. REST-style routes: Path-based (/api/users)
  2. RPC-style actions: JSON-based ({"action": "getUser"})

Router Implementation

class Router {
public:
    using ActionHandler = std::function<std::string(const std::string& body)>;
    using RouteHandler = std::function<std::string(const std::string& body)>;

    void registerRoute(const std::string& path, RouteHandler handler);
    void registerAction(const std::string& action, ActionHandler handler);

    HTTPResponse route(const std::string& path, 
                      const std::string& body) const;
    HTTPResponse dispatchAction(const std::string& path, 
                               const std::string& body) const;

private:
    std::unordered_map<std::string, ActionHandler> handlers;
    std::unordered_map<std::string, RouteHandler> routes;
};
Enter fullscreen mode Exit fullscreen mode

Route Registration

// REST endpoints
router.registerRoute("/health", [](const std::string&) {
    return R"({"status": "ok", "uptime": "24h"})";
});

router.registerRoute("/users/:id", [](const std::string& body) {
    auto json = nlohmann::json::parse(body);
    return fetchUser(json["id"]);
});

// RPC actions
router.registerAction("greet", [](const std::string& body) {
    auto json = nlohmann::json::parse(body);
    std::string name = json.value("name", "Guest");
    return R"({"message": "Hello, )" + name + R"(!"})";
});
Enter fullscreen mode Exit fullscreen mode

Function Pointers vs Virtual Functions:
We use std::function (type-erased callable) instead of inheritance:

  • More flexible - accepts lambdas, function pointers, functors
  • Zero overhead abstraction
  • No virtual function call overhead

Route Resolution

HTTPResponse Router::route(const std::string& path, 
                           const std::string& body) const {
    auto it = routes.find(path);
    if (it != routes.end()) {
        return HTTPResponse::ok(200, it->second(body));
    }
    return HTTPResponse::error(404, "Route not found");
}
Enter fullscreen mode Exit fullscreen mode

Time Complexity:

  • find() on unordered_map: O(1) average case
  • Much faster than regex matching or tree traversal

AppDispatcher: Strategy Orchestration

class AppDispatcher {
    Router& router;
    ActionDispatcher actionDispatcher;

public:
    HTTPResponse HandleRequest(const HTTPRequest& request) {
        const std::string& path = request.path;
        const std::string& method = request.method;

        // Strategy 1: JSON RPC actions
        auto contentTypeIt = request.headers.find("Content-Type");
        if(method == "POST" && 
           contentTypeIt != request.headers.end() && 
           contentTypeIt->second == "application/json") {
            return actionDispatcher.dispatch(request);
        }

        // Strategy 2: REST routes
        auto routeHandler = router.getRoute(path);
        if (routeHandler) {
            return router.route(request.path, request.body);
        }

        // Strategy 3: Static file serving
        return StaticServer::serve(path);
    }
};
Enter fullscreen mode Exit fullscreen mode

Strategy Pattern:
Different handling strategies selected at runtime based on request characteristics.

ActionDispatcher: RPC Handler

HTTPResponse ActionDispatcher::dispatch(const HTTPRequest& request) {
    try {
        json bodyJson = json::parse(request.body);

        if (!bodyJson.contains("action")) {
            return HTTPResponse::error(400, 
                "Missing 'action' in request body");
        }

        std::string action = bodyJson["action"];
        return router.dispatchAction(action, request.body);

    } catch (const json::parse_error& e) {
        return HTTPResponse::error(400, "Invalid JSON");
    } catch (const std::exception& e) {
        return HTTPResponse::error(500, "Internal Server Error");
    }
}
Enter fullscreen mode Exit fullscreen mode

Error Handling:
Comprehensive exception handling prevents server crashes from malformed requests.


Design Patterns & Architectural Decisions

1. Strategy Pattern

Purpose: Select algorithm/behavior at runtime

Implementation:

// Different strategies for different request types
if (isJSON) return actionDispatcher.dispatch(request);
if (hasRoute) return router.route(path, body);
return StaticServer::serve(path);
Enter fullscreen mode Exit fullscreen mode

2. Factory Pattern

Purpose: Encapsulate object creation

Implementation:

HTTPResponse::ok(200, data);     // Success factory
HTTPResponse::error(404, msg);   // Error factory
Enter fullscreen mode Exit fullscreen mode

3. Dependency Injection

Purpose: Decouple components, enable testing

Implementation:

class AppDispatcher {
    Router& router;  // Injected dependency

public:
    AppDispatcher(Router& router) 
        : router(router), 
          actionDispatcher(router) {}
};
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Test with mock router
  • Swap implementations without changing code
  • Clear dependencies

4. RAII (Resource Acquisition Is Initialization)

Purpose: Automatic resource management

Implementation:

{
    std::unique_lock<std::mutex> lock(taskQueueMtx);
    // Critical section
}  // Lock automatically released
Enter fullscreen mode Exit fullscreen mode

Prevents:

  • Memory leaks
  • Resource leaks
  • Exception-related bugs

5. Producer-Consumer Pattern

Purpose: Decouple work generation from work execution

Implementation:

// Producer (main thread)
pool.enqueueTask([client_socket]() { 
    handleRequest(client_socket); 
});

// Consumer (worker threads)
while(true) {
    wait_for_task();
    execute_task();
}
Enter fullscreen mode Exit fullscreen mode

6. Observer Pattern (Condition Variables)

Purpose: Efficient thread notification

Implementation:

condition.notify_one();  // Wake one waiting thread
condition.notify_all();  // Wake all waiting threads
Enter fullscreen mode Exit fullscreen mode

Asynchronous Request Processing Flow

Complete Request Lifecycle

1. CLIENT CONNECTS
   └─> accept() returns client_socket

2. ENQUEUE TO THREAD POOL
   └─> pool.enqueueTask([client_socket, &app]() { ... })
   └─> Returns immediately (non-blocking)

3. WORKER THREAD PICKS UP TASK
   └─> Waits on condition variable
   └─> Wakes up when task available

4. RECEIVE HTTP DATA
   └─> recv(client_socket, buffer, 4096, 0)
   └─> Reads raw bytes from network

5. PARSE HTTP REQUEST
   └─> HTTPRequest::parse(rawRequest)
   └─> Structured HTTPRequest object

6. ROUTE THROUGH DISPATCHER
   └─> app.HandleRequest(request)
   └─> Selects appropriate strategy

7. EXECUTE HANDLER
   └─> Lambda/function executes business logic
   └─> Returns response string

8. BUILD HTTP RESPONSE
   └─> HTTPResponse::to_string()
   └─> Formats as HTTP protocol

9. SEND TO CLIENT
   └─> send(client_socket, response, size, 0)
   └─> Transmits over network

10. CLEANUP
    └─> close(client_socket)
    └─> Worker thread returns to pool
Enter fullscreen mode Exit fullscreen mode

Concurrency Analysis

Key Points:

  1. Main thread never blocks on request processing
  2. Worker threads execute independently - no coordination needed
  3. Mutex only held during queue operations - minimal contention
  4. Task execution outside locks - maximum parallelism

Performance Characteristics:

// Theoretical maximum throughput
max_throughput = worker_threads * (1 / avg_request_time)

// Example: 15 threads, 10ms avg request time
max_throughput = 15 * (1 / 0.01) = 1,500 requests/second
Enter fullscreen mode Exit fullscreen mode

CLI Configuration & Extensibility

Command-Line Interface

// Default configuration
int port = 11111;
int threadCount = 15;
std::string staticDir = "../public";

// Parse arguments
for (int i = 1; i < argc; ++i) {
    std::string arg = argv[i];
    if (arg == "--port" && i + 1 < argc) {
        port = std::stoi(argv[++i]);
    } else if (arg == "--threads" && i + 1 < argc) {
        threadCount = std::stoi(argv[++i]);
    } else if (arg == "--static" && i + 1 < argc) {
        staticDir = argv[++i];
    }
}
Enter fullscreen mode Exit fullscreen mode

Usage Examples:

# Default settings
./NanoHostApp

# Custom configuration
./NanoHostApp --port 8080 --threads 20 --static ./public

# Production deployment
./NanoHostApp --port 80 --threads 50 --static /var/www/html
Enter fullscreen mode Exit fullscreen mode

Input Validation

// Validate port range
if (port <= 0 || port > 65535) {
    std::cerr << "[Error] Invalid port number: " << port << std::endl;
    return 1;
}

// Validate thread count
if (threadCount <= 0) {
    std::cerr << "[Error] Thread count must be > 0" << std::endl;
    return 1;
}
Enter fullscreen mode Exit fullscreen mode

Extensibility Points

1. Custom Route Handlers

// Simple handler
router.registerRoute("/api/status", [](const std::string&) {
    return R"({"status": "online"})";
});

// Database integration
router.registerRoute("/api/users", [&db](const std::string& body) {
    auto json = nlohmann::json::parse(body);
    return db.query("SELECT * FROM users WHERE id = ?", json["id"]);
});

// Async operations
router.registerRoute("/api/slow", [](const std::string&) {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return "Completed slow operation";
});
Enter fullscreen mode Exit fullscreen mode

2. Middleware Pattern

// Authentication middleware
auto authMiddleware = [](HTTPRequest& req, HTTPResponse& res) -> bool {
    auto authHeader = req.headers.find("Authorization");
    if (authHeader == req.headers.end()) {
        res = HTTPResponse::error(401, "Unauthorized");
        return false;
    }
    return validateToken(authHeader->second);
};

// Logging middleware
auto loggingMiddleware = [](const HTTPRequest& req) {
    std::cout << "[" << getCurrentTime() << "] " 
              << req.method << " " << req.path << std::endl;
};

// CORS middleware
auto corsMiddleware = [](HTTPResponse& res) {
    res.setHeader("Access-Control-Allow-Origin", "*");
    res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
};
Enter fullscreen mode Exit fullscreen mode

3. Custom Dispatchers

// WebSocket dispatcher
class WebSocketDispatcher {
public:
    void handleUpgrade(const HTTPRequest& request, int socket);
    void handleMessage(const std::string& message);
};

// GraphQL dispatcher
class GraphQLDispatcher {
    GraphQLSchema schema;
public:
    HTTPResponse execute(const std::string& query);
};
Enter fullscreen mode Exit fullscreen mode

4. Plugin System

// Plugin interface
class Plugin {
public:
    virtual void onServerStart() = 0;
    virtual void onRequest(HTTPRequest& req) = 0;
    virtual void onResponse(HTTPResponse& res) = 0;
    virtual void onServerStop() = 0;
};

// Plugin manager
class PluginManager {
    std::vector<std::unique_ptr<Plugin>> plugins;
public:
    void registerPlugin(std::unique_ptr<Plugin> plugin);
    void notifyServerStart();
    void notifyRequest(HTTPRequest& req);
};
Enter fullscreen mode Exit fullscreen mode

Conclusion

We've explored the implementation of a production-grade web server, demonstrating:

OS Concepts Applied

  • TCP/IP Socket Programming: socket(), bind(), listen(), accept()
  • POSIX Threading: Thread creation, synchronization, condition variables
  • Non-blocking I/O: fcntl(), O_NONBLOCK, handling EAGAIN
  • Signal Handling: Graceful shutdown with SIGINT/SIGTERM
  • File Descriptors: Managing socket and file descriptors
  • Byte Order Conversion: Network vs host byte order

Design Patterns Used

  • Strategy Pattern: Request routing strategies
  • Factory Pattern: Response object creation
  • Producer-Consumer: Thread pool task queue
  • Dependency Injection: Loose coupling between components
  • RAII: Automatic resource management
  • Observer Pattern: Condition variable notifications

Key Takeaways

  1. Thread pools solve the C10K problem - Pre-allocated threads eliminate creation overhead
  2. Non-blocking I/O enables high concurrency - Don't block the accept loop
  3. Lock-free primitives where possible - Use atomics for simple counters
  4. Design patterns matter - They provide proven solutions to common problems
  5. RAII prevents leaks - Let C++ manage resources automatically

Further Exploration

Next Steps:

  • Implement epoll/kqueue for true asynchronous I/O
  • Add TLS/SSL support with OpenSSL
  • Implement HTTP/2 with multiplexing
  • Add connection pooling for databases
  • Build comprehensive middleware system
  • Implement rate limiting and DDoS protection

Resources:

GitHub Repository:
github.com/rprakashdass/nanohost


About the Author

Prakash Dass R is a systems programmer with a strong foundation in low-level programming, operating system internals, and building high-performance systems. Passionate about artificial intelligence development, bringing expertise in both core systems engineering and modern AI technologies to create efficient, scalable software solutions

Connect:


Did you find this article helpful? Consider starring the NanoHost repository on GitHub and sharing this article with your network!


Top comments (0)