YOUSEFUL / DELPHI / COMPONENTS

TAtlasMqttBroker

A small MQTT 3.1.1 broker that runs inside your program, for tests, simulators, demos and a local message bus.

Non-visual component Palette: Atlas MQTT Unit Atlas.MqttBroker · Inherits TComponent · Product Atlas MQTT

Description

TAtlasMqttBroker lets programs and tests use MQTT without installing a server. It is on the Atlas MQTT palette page. Set Port (0 for any free port), call Start, and point clients at 127.0.0.1:ActualPort.

What it does. Publish and subscribe at qualities 0, 1 and 2, with + and # wildcards and the $-topic rule. It delivers at the lower of the published and the subscribed quality. When several of a client's filters match, it delivers once, at the highest quality among them. It grants every subscription at the requested quality.

It answers a protocol level other than 4 with return code 1, and an empty client identifier without a clean session with return code 2. It closes the connection on malformed data, on a second CONNECT, and on packets larger than 1 MiB (1 048 576 bytes, header included).

What it does not do. Sessions are not persisted: every session is clean, CONNACK always says "no session present", nothing is queued for a disconnected subscriber, and an unacknowledged delivery is not sent again. Retained messages live in memory until Stop. There is no TLS, WebSocket, MQTT 5.0, IPv6, per-topic access control, bridging, $SYS statistics or message expiry.

Threads. One thread accepts connections, and one thread serves each connection. Messages are routed while holding a single lock, which is simple and correct, but one stalled client delays the others. It listens on the loopback address unless LoopbackOnly is False. Do not expose it to the internet.

Members

MemberKindWhat it does
CreateconstructorCreates a stopped broker with Port 1883 and LoopbackOnly True.
DestroydestructorCalls Stop, then frees the broker.
StartmethodOpens the listening socket on Port and starts accepting connections.
StopmethodStops accepting, closes every client connection and waits for the connection threads to end.
DropClientmethodCuts the connection of the client with this identifier without ceremony, as a network failure would.
ActualPortpropertyThe port really bound by Start: the same as Port, or the one the system chose when Port is 0.
ClientCountpropertyClients whose CONNECT was accepted and whose connection is still open.
RetainedCountpropertyTopics with a retained message.
RoutedCountpropertyMessages accepted from publishers since Start, whether or not a subscription matched.
PortpropertyThe TCP port to listen on; 0 lets the system choose a free one (read ActualPort after Start).
LoopbackOnlypropertyTrue listens on 127.0.0.1 only, so only programs on this machine can connect.
ActivepropertyTrue while started.
OnAuthenticateeventCalled for every CONNECT that passes the protocol checks, on the thread of that connection.

Constructors

Create constructor

constructor Create(AOwner: TComponent); override;

Creates a stopped broker with Port 1883 and LoopbackOnly True.

pascal
Broker := TAtlasMqttBroker.Create(nil);

Destructors

Destroy destructor

destructor Destroy; override;

Calls Stop, then frees the broker.

pascal
Broker.Free;

Properties

ActualPort property

property ActualPort: Word read FActualPort;

The port really bound by Start: the same as Port, or the one the system chose when Port is 0. 0 while stopped.

pascal
Client.Port := Broker.ActualPort;

ClientCount property

property ClientCount: Integer read GetClientCount;

Clients whose CONNECT was accepted and whose connection is still open. A socket that has not sent CONNECT yet does not count.

pascal
Count := Broker.ClientCount;

RetainedCount property

property RetainedCount: Integer read GetRetainedCount;

Topics with a retained message. A retained publication with an empty payload removes its topic.

pascal
Count := Broker.RetainedCount;

RoutedCount property

property RoutedCount: Int64 read FRouted;

Messages accepted from publishers since Start, whether or not a subscription matched. Wills count too. A repeated quality-2 message counts once.

pascal
Text := IntToStr(Broker.RoutedCount);

Port property

property Port: Word read FPort write FPort default 1883;

The TCP port to listen on; 0 lets the system choose a free one (read ActualPort after Start). A change takes effect at the next Start.

pascal
Broker.Port := 0;

LoopbackOnly property

property LoopbackOnly: Boolean read FLoopbackOnly write FLoopbackOnly default True;

True listens on 127.0.0.1 only, so only programs on this machine can connect. False listens on every IPv4 interface. The broker has no TLS and no per-topic access control, so do not expose it to the internet. A change takes effect at the next Start.

pascal
Broker.LoopbackOnly := False;

Active property

property Active: Boolean read GetActive write SetActive default False;

True while started. Setting it calls Start or Stop. In the form designer, setting it has no effect, so start the broker at run time. When a form file does contain Active = True, the broker starts when loading finishes, after Port and LoopbackOnly have been read.

pascal
Broker.Active := True;

Events

OnAuthenticate event

property OnAuthenticate: TAtlasMqttAuthenticateEvent read FOnAuthenticate write FOnAuthenticate;

Called for every CONNECT that passes the protocol checks, on the thread of that connection. It is not synchronised with the main thread, so the handler must be thread-safe. Accept is True on entry; set it to False to refuse the client with return code 4 ("Bad user name or password"). Password is the UTF-8 text of the password field, empty when none was sent. Without a handler, every client is accepted.

pascal
Broker.OnAuthenticate := Handler.Authenticate;

Methods

Start method

procedure Start;

Opens the listening socket on Port and starts accepting connections. ActualPort tells which port was bound, and RoutedCount starts from 0 again. Raises EAtlasMqttTransportError when the port cannot be bound, for example "Binding port 1883 failed (Windows Sockets error 10048)" when it is taken. Does nothing when already started.

pascal
Broker.Port := 0;
Broker.Start;

Stop method

procedure Stop;

Stops accepting, closes every client connection and waits for the connection threads to end. No wills are published while stopping. All retained messages are discarded, and ActualPort becomes 0. Clients see the connection close; with AutoReconnect they keep trying and find the broker again after the next Start on the same port. Does nothing when already stopped.

pascal
Broker.Stop;

DropClient method

function DropClient(const ClientId: string): Boolean;

Cuts the connection of the client with this identifier without ceremony, as a network failure would. Its will, if it has one, is published. Returns False when no connected client has that identifier. Meant for tests and demonstrations of reconnect and will handling.

pascal
Ok := Broker.DropClient('pump-3');

Examples

A broker and a client for a test

A broker on a free loopback port with a client connected to it, and a network failure on demand. Free the client before the broker.

pascal
uses
  System.SysUtils, Atlas.MqttClient, Atlas.MqttBroker;

function StartTestBroker(out Client: TAtlasMqttClient): TAtlasMqttBroker;
begin
  Result := TAtlasMqttBroker.Create(nil);
  Result.Port := 0;  // any free port
  Result.Start;
  Client := TAtlasMqttClient.Create(nil);
  Client.Host := '127.0.0.1';
  Client.Port := Result.ActualPort;
  Client.ClientId := 'under-test';
  Client.SynchronizeEvents := False;
  Client.ReconnectDelayMs := 100;
  if not Client.ConnectAndWait(3000) then
  begin
    FreeAndNil(Client);
    Result.Free;
    raise Exception.Create('The test client did not connect');
  end;
end;

// Simulates a network failure: the client's will is published and it reconnects.
procedure CutConnection(Broker: TAtlasMqttBroker);
begin
  if not Broker.DropClient('under-test') then
    raise Exception.Create('under-test is not connected');
end;

Report what a running broker is doing

The counters are safe to read while the broker runs.

pascal
uses
  System.SysUtils, Atlas.MqttBroker;

function BrokerSummary(Broker: TAtlasMqttBroker): string;
begin
  if not Broker.Active then
    Exit('stopped');
  Result := Format('port %d, %d clients, %d retained topics, %d messages routed',
    [Broker.ActualPort, Broker.ClientCount, Broker.RetainedCount, Broker.RoutedCount]);
end;

TAtlasMqttClient · TAtlasMqttAuthenticateEvent · TAtlasTcpListener · EAtlasMqttTransportError

Also in Atlas MQTT

TAtlasMqttClient