rumqtt

repository·main·Indexed 24 days ago

https://github.com/bytebeamio/rumqtt

A high-performance set of Rust libraries for the MQTT standard, featuring rumqttc (a high-level MQTT client supporting MQTT 3.1.1 and 5) and rumqttd (an embeddable MQTT broker supporting MQTT 3.1.1, QoS 0, 1, and 2, and TLS connections). The libraries provide synchronous and asynchronous communication options, automatic reconnection, and flow control via an event loop architecture.

Tokens
20K
Snippets
45
Records
100
Agent score
80%

What's inside rumqtt

  1. What is rumqtt?

    main

    rumqtt is an open-source set of Rust libraries designed to implement the MQTT standard. It focuses on being simple, robust, and performant. The project consists of two primary crates:

    • rumqttc: A high-level, easy-to-use MQTT client.
    • rumqttd: A high-performance, embeddable MQTT broker.
  2. Features of rumqttc

    main

    The rumqttc library provides the following core features for robust MQTT communication:

    • Eventloop orchestration of incoming/outgoing packets
    • Automatic broker pinging and half-open connection detection
    • Queue size based flow control on outgoing packets
    • Automatic reconnections via continuous polling
    • Natural backpressure to client APIs
    • Support for WebSockets
    • Secure transport using TLS
  3. How the Router processes events and data

    main

    The Router operates via a main loop (Router::run()) that manages two primary states:

    1. Waiting for Events: If Router::readyqueue is empty, the router blocks on the events receiver to avoid CPU spinning.
    2. Processing Ready Queue: If Router::readyqueue is not empty, the router parses a batch of events (0-500) from the receiver. This allows the router to move from parsing events to sending notifications to RemoteLinks.

    To push data to a specific client, the router uses Router::consume(id: ConnectionId). This method:

    • Sends all pending acknowledgments (acks).
    • Sends all pending data (messages from subscribed topics) for that specific ConnectionId.
  4. How connection lifecycle and events are handled

    main

    The Router responds to several key event types to manage client state:

    New Connection (Event::Connect)

    When a new connection is formed:

    • The router validates if the total connection limit has been reached.
    • If a clean session is NOT requested, the router retrieves any pending data and acks from the previous session.
    • A connack is sent back to the client immediately (bypassing the readyqueue for efficiency).

    Device Data (Event::DeviceData)

    When a RemoteLink reads bytes from the Network, it notifies the router. The router parses these as MQTT packets:

    • Publish Packets: Appended to the CommitLog, a puback is added to the ackslog, and the connection is added to Router::readyqueue to flush the ack.
    • Subscribe Packets: The router validates the subscription and calls Router::prepare_consumption(), which adds the connection to the readyqueue.
    • PubAck Packets: Registered with the QoS1 buffer in the Connection. Out-of-order or unsolicited acks trigger a disconnection.

    Disconnect Event

    When a client disconnects, Router::handle_disconnection() is called. The connection and its ackslog entries are removed. If a clean session was not requested, the Tracker is saved to a 'graveyard' to preserve state.

  5. How the rumqttc event loop works

    main

    The rumqttc event loop (accessed via connection.iter() in sync or eventloop.poll() in async) is the core engine that orchestrates outgoing and incoming packets.

    Key behaviors:

    • Connection Management: It handles pings, detects half-open connections, and performs automatic reconnections as long as you continue polling the loop.
    • Flow Control: It provides queue size based flow control on outgoing packets and natural backpressure to client APIs during network instability.
    • Concurrency: It manages the state and handles packets concurrently.
    • Customization: Because the loop is externally polled, users can intercept notifications to distribute messages by topic, stop the loop for graceful shutdown, or access internal state to modify options before a reconnection.

    Critical Requirement: You must loop on connection.iter() or eventloop.poll(). If you stop polling or block inside this loop, the connection will not progress and will eventually fail.

  6. How rumqttc handles reconnections

    main

    The rumqttc event loop manages reconnections based on configurable options. You can choose between automatic reconnection or manual control. If you choose manual control, you can capture the MqttState returned by the event loop and pass it to a new connection to maintain state.

    Available reconnection options include:

    • Reconnect::Never: Disables automatic reconnection. Use this if you want to handle reconnection logic manually using the returned MqttState.
    • Reconnect::Automatic: Enables the event loop to handle reconnections automatically.
    // create an eventloop after the initial mqtt connection is successful
    let eventloop = connect(mqttoptions) -> Result<EventLoop, Error>
    
    // during intermittent reconnetions due to bad network, eventloop will
    // behave as per configured reconnection options to the eventloop
    let stream = eventloop.assemble(reconnection_options, inputs);
  7. Understand the rumqttd architecture

    main

    The rumqttd architecture is centered around a Router and RemoteLink model:

    • Router: Acts as the central hub for data storage and routing logic. It manages connections, handles MQTT packet parsing, and maintains the state of subscriptions and data logs.
    • RemoteLink: Acts as the bridge between the Router and the network. It is responsible for forwarding Notifications from the Router to the client and sending Events from the client to the Router.
    • Network: A spawned task that handles the actual TCP connection stream for a specific client.

    Data flows between the RemoteLink and the Router via shared buffers (Arc<Mutex<..>>) and mpsc channels used for signaling presence.

  8. How data flows through rumqttd via I/O Buffers

    main

    The rumqttd datapath relies on two primary pre-allocated Slab datatypes (using VecDequeue) to manage data movement:

    • ibufs (Input Buffers): Stores all incoming data (represented as Packet objects) sent to rumqttd.
    • obufs (Output Buffers): Stores all outgoing data (represented as Notification objects).

    obufs also maintains an in-flight queue to track packets that have been transmitted over the network but have not yet been acknowledged by the recipient.

  9. How rumqttc client architecture works

    main

    The rumqttc client library is designed around two primary components: the EventLoop and the AsyncClient.

    • EventLoop: This is the core component that manages network interaction. It handles TCP/WebSockets connections (with or without TLS) and makes critical decisions such as acknowledging QoS 1/2 requests and receiving publishes. To keep the client operational, you must continuously poll the .poll() method on the EventLoop.
    • AsyncClient: This is the interface used by your user application to send requests to the EventLoop. It communicates with the EventLoop via an internal channel.

    Critical Requirement: You must ensure .poll() is called regularly. If the EventLoop is blocked or not polled frequently enough, requests from both the user and the broker will fail to process, which can lead to severe connection issues requiring a hard reset.

          ┌──────┐
          │Broker│
          └──┬▲──┘
             ││
          sub││pub (network)
             ││
         ┌───▼┴────┐                ┌───────────┐
         │EventLoop◄────────────────┤AsyncClient│
         └───┬─────┘                └─────▲─────┘
             └──────────────┐┌────────────┘
                 .poll()    ││  - .ack()
                            ││  - .publish()
                            ││  - .subscribe()
                            ││  - .unsubscribe()
                    ┌───────▼┴───────┐
                    │User Application│
                    └────────────────┘
  10. Understand the core entities in rumqttd

    main

    To work with rumqttd, you should understand its three primary architectural abstractions: the Broker, the Router, and Links.

    • Broker: The top-level entity that manages the lifecycle of the system. When you create a Broker, it automatically creates a Router. Starting a Broker spawns several background threads for the Router, metrics server, MQTT (v4/v5) servers, WebSocket servers (if enabled), Prometheus listener, and the ConsoleLink.
    • Router: The central engine that controls data flow. It uses a reactor pattern to react to events in the input buffers (ibufs). It manages authorization, scheduling, and dispatching data between components by writing to output buffers (obufs).
    • Link: Asynchronous, typically network-facing entities that facilitate communication between the Router and the outside world (e.g., devices, metrics consumers, or management consoles).
  11. Install rumqttd via Cargo

    main

    To install the rumqttd broker directly from the git repository using Cargo:

    cargo install --git https://github.com/bytebeamio/rumqtt rumqttd

    After installation, you can download a demo configuration file:

    curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/bytebeamio/rumqtt/main/rumqttd/rumqttd.toml > rumqttd.toml

    Run the broker using the downloaded configuration:

    rumqttd --config rumqttd.toml

    Note: Ensure the rumqttd.toml file is correctly configured for your specific version of rumqttd.

    cargo install --git https://github.com/bytebeamio/rumqtt rumqttd
    
    curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/bytebeamio/rumqtt/main/rumqttd/rumqttd.toml > rumqttd.toml
    
    rumqttd --config rumqttd.toml