Inhaltsverzeichnis

Netzwerk-Guide

Aufgabenorientierter Einstieg in std.net: vom ersten TCP-Client über HTTP und TLS bis zum Server mit vielen gleichzeitigen Verbindungen. Diese Seite erklärt die Arbeitsweise — welche Unit für welche Aufgabe, welche Capability dazugehört, welche Fehler typisch sind. Die vollständige Funktionsliste je Unit steht in der std.net-Referenz.

std.net — Unit-Referenz · Capabilities · Rohspeicher: alloc, peek & poke


1. Orientierung: welche Unit für welche Aufgabe?

Aufgabe Unit Einstiegsfunktion
Ausgehende TCP-Verbindung std.net.socket TCPConnect
TCP-Server std.net.socket TCPListenerNew
UDP senden/empfangen std.net.socket UDPSocketNew
Viele Verbindungen gleichzeitig std.net.epoll EpollCreate
Namen auflösen (A, MX, TXT …) std.net.dns DNSResolveGoogle
HTTP-Anfrage std.net.http HTTPGet, HTTPSend
HTTPS-Anfrage std.net.https HTTPSGet
Verschlüsselter Kanal, selbst gesteuert std.net.tls TLSInit, TLSConnect
REST-API mit Basis-URL und Token std.net.rest RestClientInit
Mail versenden / abholen std.net.smtp / std.net.imap SMTPSend / IMAP-Session
Geräte abfragen (SNMP), Verzeichnis (LDAP) std.net.snmp / std.net.ldap

Faustregel: so hoch wie möglich einsteigen. Wer eine JSON-API anspricht, nimmt std.net.rest und nicht std.net.socket. Zur Socket-Ebene steigt man ab, wenn das Protokoll nicht abgedeckt ist oder man die Verbindung selbst verwalten muss (Timeouts, Wiederverbindung, eigenes Framing).

Die Schichten

rest · smtp · imap · mqtt · ssh · snmp · ldap   ← Anwendungsprotokolle
  http · https · dns                              ← baut auf socket (+ tls)
  socket · tls · epoll · types                    ← Transport
  syscalls                                        ← sys_socket, sys_connect, …

Importiert wird nur die oberste benötigte Unit — die Abhängigkeiten zieht der Compiler nach.


2. Das kleinste vollständige Programm

Ein TCP-Client, der sich verbindet, liest und wieder aufräumt:

import std.io;
import std.net.socket;
import std.net.types;

fn main(): int64 {
    var conn: TCPConn := TCPConnect(IPPack(127, 0, 0, 1), 8080);
    if (conn.fd < 0) {
        PrintLn("Verbindung fehlgeschlagen");
        return 1;
    }

    var buf: int64 := BufferAlloc(4096);
    var n: int64 := TCPConnRead(conn, buf, 4096);
    Print("Empfangen: "); PrintLn(IntToStr(n));

    BufferFree(buf, 4096);
    TCPConnClose(conn);
    return 0;
}

Drei Dinge, die für alle Netz-Programme in Lyx gelten:

  1. Adressen sind int64. IPPack(127, 0, 0, 1) packt eine IPv4-Adresse in eine Zahl. Es gibt keinen Zeigertyp und keinen Adressoperator — siehe Rohspeicher.
  2. Puffer werden selbst besorgt und selbst freigegeben. BufferAlloc/BufferFree aus std.net.socket oder alloc/free aus std.alloc. Die Größe gehört beim Freigeben wieder dazu — Lyx führt keine versteckten Metadaten.
  3. Fehler kommen als Rückgabewert, nicht als Ausnahme. Ein negativer Wert oder fd < 0 ist der Fehlerfall. Es fliegt nichts.

3. Adressen, Puffer, Byte-Reihenfolge

IPv4-Adressen

var ip: int64 := IPPack(93, 184, 216, 34);   // Adresse als Zahl
var a: IPAddr := IPUnpack(ip);               // zurück in vier Oktette
// a.a, a.b, a.c, a.d — jeweils uint8

IPAddr trägt zusätzlich family und port; TCPConnectAddr nimmt eine solche Struktur direkt entgegen, wenn der Port schon eingetragen ist.

 
Rohdaten aus dem Netz sind in Netzwerk-Byte-Reihenfolge. Wer eine IPv4-Adresse aus einer DNS-Antwort oder einem Paket-Header mit peek32 liest und durch IPUnpack schickt, bekommt die Oktette rückwärts (172.66.147.243 wird zu 243.147.66.172). IPPack/IPUnpack arbeiten mit der Lyx-internen Reihenfolge, nicht mit der des Kabels. Bei Rohdaten deshalb byteweise lesen:

> var o1: int64 := peek8(daten + 0);
> var o2: int64 := peek8(daten + 1);
> var o3: int64 := peek8(daten + 2);
> var o4: int64 := peek8(daten + 3);
> 

Puffer

BufferAlloc(n) liefert einen Puffer, BufferFree(buf, n) gibt ihn zurück. Für alles andere std.alloc. Ein Puffer wird einmal vor der Schleife angelegt, nicht je Durchlauf — das spart Syscalls und macht die Laufzeit vorhersagbar.


4. Namen auflösen — std.net.dns

std.net.dns spricht DNS selbst, ohne /etc/resolv.conf und ohne libc-Resolver. Für jeden Record-Typ gibt es drei Varianten: mit eigenem Server (DNSResolve…), gegen Google (…Google) und gegen Cloudflare (…Cloudflare).

Das Ergebnis wird nicht zurückgegeben, sondern in einen bereitgestellten Speicherbereich geschrieben: 40 Byte Kopf (DNSResult) plus Datenbereich dahinter.

import std.io;
import std.alloc;
import std.net.dns;

con DNS_RESULT_SIZE: int64 := 40;    // Kopf: success, recordType, ttl, rcode, dataLen
con DNS_DATA_MAX:    int64 := 512;   // Datenbereich ab Offset 40

fn main(): int64 {
    var res: int64 := alloc(DNS_RESULT_SIZE + DNS_DATA_MAX);
    if (res == 0) { return 1; }

    var host: pchar := "example.com"c;
    if (!DNSResolveGoogle(host as int64, 11, res)) {
        PrintLn("DNS: keine Antwort");
        free(res, DNS_RESULT_SIZE + DNS_DATA_MAX);
        return 1;
    }
    if (peek64(res + DNSResult.rcode) != DNS_RCODE_OK) {
        PrintLn("DNS: Fehlercode vom Server");
        free(res, DNS_RESULT_SIZE + DNS_DATA_MAX);
        return 1;
    }

    // A-Record: vier Oktette ab dem Datenbereich, byteweise
    Print(IntToStr(peek8(res + DNS_RESULT_SIZE + 0))); Print(".");
    Print(IntToStr(peek8(res + DNS_RESULT_SIZE + 1))); Print(".");
    Print(IntToStr(peek8(res + DNS_RESULT_SIZE + 2))); Print(".");
    PrintLn(IntToStr(peek8(res + DNS_RESULT_SIZE + 3)));

    free(res, DNS_RESULT_SIZE + DNS_DATA_MAX);
    return 0;
}

Zwei Ebenen von Fehlern, die man beide prüfen muss:

A.feld ist in Lyx der Byte-Offset des Feldes — DNSResult.rcode liefert also die Zahl, die man auf den Basiszeiger addiert. Dasselbe Muster trägt durch die ganze Standardbibliothek.

Record Funktion Datenbereich enthält
A DNSResolveGoogle 4 Byte IPv4
AAAA DNSResolveAAAAGoogle 16 Byte IPv6
MX DNSResolveMXGoogle Priorität + Hostname
TXT DNSResolveTXTGoogle Textsegmente
NS, SOA, SRV, CAA, PTR, DS, DNSKEY DNSResolve<Typ>Google typabhängig

5. HTTP

Der kurze Weg

import std.io;
import std.net.http;

fn main(): int64 {
    var resp: HTTPResponse := HTTPGet("example.com"c, "/"c);

    Print("Status: "); PrintLn(IntToStr(resp.statusCode));
    if (resp.statusCode == HTTP_OK) {
        Print("Bytes im Rumpf: "); PrintLn(IntToStr(resp.bodySize));
    }

    HTTPResponseFree(resp);
    return 0;
}

HTTPResponse trägt statusCode, contentLength, bodyPtr, bodySize sowie die Rohheader (headersRaw/headersSize). Der Rumpf liegt als Speicherbereich vor, nicht als Zeichenkette — bodyPtr plus bodySize byteweise lesen.

HTTPResponseFree ist Pflicht. Ohne den Aufruf bleiben Rumpf und Header liegen.

Gegen example.com liefert das Programm Status: 200 und 571 Byte im Rumpf. Zeigt der Host ins Leere — etwa 127.0.0.1 ohne lauschenden Dienst — kommt statusCode als 0 zurück; auch dieser Fall gehört geprüft.

 
Ein Rest bleibt: manche Server lassen HTTPGet hängen. Antwortet die Gegenseite mit Content-Length und Connection: Upgrade statt mit Transfer-Encoding: chunked (beobachtet bei neverssl.com), kehrt der Aufruf nicht zurück — das Programm wartet unbegrenzt, die Zeile nach HTTPGet wird nie erreicht. Dieselbe Adresse beantwortet curl in dreieinhalb Sekunden.

Wer gegen fremde, unbekannte Server spricht, sollte den Aufruf deshalb nicht ohne Aufsicht laufen lassen: entweder HTTPSGet verwenden oder die Anfrage über std.net.socket mit eigenem Timeout selbst schreiben (Abschnitt 8). Geprüft mit lyxc 1.0.16F, stdlib-Stand 80479041.
(Issue #1309)

Header, Auth, Redirects

var hdrs: int64 := 0;
hdrs := HTTPSetHeader(hdrs, "Accept"c as int64, "application/json"c as int64);
hdrs := HTTPSetBearerToken(hdrs, "GEHEIM"c as int64);

var resp: HTTPResponse := HTTPGetH("api.example.com"c, "/v1/users"c, hdrs);
// ... auswerten ...
HTTPResponseFree(resp);
HTTPHeadersFree(hdrs);

Volle Kontrolle: HTTPRequest

var req: HTTPRequest;
req.method   := HTTP_POST;
req.host     := "api.example.com"c as int64;
req.path     := "/v1/jobs"c as int64;
req.port     := HTTP_PORT;
req.headers  := hdrs;
req.body     := rumpf;
req.bodySize := rumpfLen;

var resp: HTTPResponse := HTTPSend(req);


6. REST-APIs — std.net.rest

std.net.rest hält Basis-URL, Port, HTTPS-Schalter und Auth-Header an einem Ort fest, sodass die einzelnen Aufrufe nur noch den Pfad nennen.

import std.io;
import std.alloc;
import std.net.http;
import std.net.rest;

fn main(): int64 {
    var client: int64 := alloc(REST_SIZE);
    RestClientInit(client, "https://api.example.com/v1"c as int64, 0);
    RestClientSetBearerToken(client, "GEHEIM"c as int64);

    var resp: HTTPResponse := RestGet(client, "/users"c as int64);
    if (RestIsSuccess(resp.statusCode) == 1) {
        Print("Bytes: "); PrintLn(IntToStr(resp.bodySize));
    } else {
        Print("HTTP "); PrintLn(IntToStr(resp.statusCode));
    }

    HTTPResponseFree(resp);
    RestClientFree(client);
    free(client, REST_SIZE);
    return 0;
}