Zum Inhalt springen
Deutsch

UDP

UdpSocketActor umschließt einen UDP-Socket — verbindungslos, keine ACKs, keine Retries, paketorientiert. Nimm ihn, wenn Best-Effort- Zustellung in Ordnung ist und Latenz wichtiger ist als Zuverlässigkeit.

Eingehende Datagramme werden an einen Target-Actor geschoben, den du über den Builder verdrahtest — es gibt kein Subscribe-Kommando. Gesendet wird per { kind: 'send', datagram }-Nachricht mit explizitem Ziel (UDP ist verbindungslos, jedes Paket trägt also seine eigene Adresse).

import { ActorSystem, UdpSocketActor, UdpSocketOptions } from 'actor-ts';
import type { UdpDatagram } from 'actor-ts';
const system = ActorSystem.create('udp-demo');
// Inbound datagrams land here, one message per packet:
class PacketHandler extends Actor<UdpDatagram> {
override onReceive(d: UdpDatagram): void {
// d.payload — Uint8Array
// d.remoteHost — sender's address
// d.remotePort — sender's port
console.log(new TextDecoder().decode(d.payload), 'from', d.remoteHost);
}
}
const target = system.spawnAnonymous(PacketHandler);
const udpSocketOptions = UdpSocketOptions.create()
.withBindPort(41234) // bind for receiving; 0 lets the OS pick
.withTarget(target);
const udp = system.spawnAnonymous(
() => new UdpSocketActor(
udpSocketOptions, // required: where inbound packets go
),
);
// Send to a remote endpoint:
udp.tell({
kind: 'send',
datagram: { payload: 'ping', host: '127.0.0.1', port: 41234 },
});

UdpSocketOptionsType erweitert BrokerCommonOptionsType (das die gemeinsamen Felder für Reconnect / Circuit-Breaker / Outbound-Buffer mitbringt) und ergänzt:

interface UdpSocketOptionsType extends BrokerCommonOptionsType {
bindHost?: string; // default '0.0.0.0'
bindPort?: number; // default 0 = OS-assigned
type?: 'udp4' | 'udp6'; // default 'udp4'
target?: ActorRef<UdpDatagram>; // required: inbound subscriber
}

target ist das einzige Pflichtfeld — ohne es haben eingehende Datagramme kein Ziel. Alle vier werden über den Builder gesetzt:

UdpSocketOptions.create()
.withBindHost('0.0.0.0')
.withBindPort(41234)
.withType('udp4')
.withTarget(target);

Eingehende Pakete werden direkt an den target-Actor zugestellt — eine UdpDatagram-Nachricht pro Paket. Es gibt kein subscribe-Handshake; das Verdrahten des Targets über .withTarget(..) ist bereits die gesamte Subscription:

class PacketHandler extends Actor<UdpDatagram> {
override onReceive(d: UdpDatagram): void {
// d.payload — Uint8Array (packet bytes)
// d.remoteHost — sender's IP address
// d.remotePort — sender's port
this.handleDatagram(d.payload, d.remoteHost, d.remotePort);
}
}
const target = system.spawnAnonymous(PacketHandler);
const udpSocketOptions = UdpSocketOptions.create()
.withBindPort(41234)
.withTarget(target);
const udp = system.spawnAnonymous(
() => new UdpSocketActor(
udpSocketOptions,
),
);

Jedes Paket ist eine logische Nachricht — UDP erhält Paketgrenzen (anders als TCPs Byte-Stream), ein eingehendes Datagramm wird also auf genau ein UdpDatagram abgebildet.

Gesendet wird per { kind: 'send', datagram }-Nachricht. Das Datagramm trägt sein eigenes Ziel — host und port — plus die payload, die ein string sein darf (wird für dich UTF-8-kodiert) oder ein rohes Uint8Array (wird unverändert gesendet):

udp.tell({
kind: 'send',
datagram: { payload: 'ping', host: '127.0.0.1', port: 41234 },
});
udp.tell({
kind: 'send',
datagram: { payload: new Uint8Array([0xde, 0xad]), host: '10.0.0.5', port: 8125 },
});

Ein target ist Pflicht, aber wenn der Actor ausschließlich sendet, kannst du das Binden dem OS überlassen. Lass withBindPort weg (oder übergib 0), und der Socket bindet an einen ephemeren Port — ein reiner Sender für einseitige Telemetrie (statsd, syslog):

const udpSocketOptions = UdpSocketOptions.create().withTarget(target);
new UdpSocketActor(
udpSocketOptions, // OS-assigned port, send-only in practice
);

Drei gute Einsatzfälle:

  1. Telemetrie — Metriken an einen Collector senden (statsd, DogStatsD). Verlust eines Pakets ist akzeptabel; Latenz zählt.
  2. Service Discovery im LAN — mDNS / SSDP / proprietäre Broadcast-Schemata.
  3. Hochfrequente Low-Stakes-Daten — Spielpositionen, Sensor-Streams, bei denen das nächste Paket das vorherige ersetzt.

Nicht die richtige Form für:

  • Alles, was Zustellgarantie braucht — nimm TCP oder ein höherrangiges, zuverlässiges Protokoll.
  • Alles, was Reihenfolge braucht — UDP-Pakete kommen in beliebiger Reihenfolge an.
  • Alles, das größer als die MTU ist — Pakete >1500 Bytes fragmentieren + können leichter verloren gehen.

Settings lösen sich mit der üblichen Präzedenz auf — explizite Builder-Optionen überschreiben HOCON, das wiederum die eingebauten Defaults überschreibt. Der Config-Namespace ist actor-ts.io.broker.udp:

actor-ts.io.broker.udp {
bindHost = "0.0.0.0"
bindPort = 41234
type = "udp4"
}