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:
- Adressen sind
int64.IPPack(127, 0, 0, 1)packt eine IPv4-Adresse in eine Zahl. Es gibt keinen Zeigertyp und keinen Adressoperator — siehe Rohspeicher. - Puffer werden selbst besorgt und selbst freigegeben.
BufferAlloc/BufferFreeausstd.net.socketoderalloc/freeausstd.alloc. Die Größe gehört beim Freigeben wieder dazu — Lyx führt keine versteckten Metadaten. - Fehler kommen als Rückgabewert, nicht als Ausnahme. Ein negativer Wert oder
fd < 0ist 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 mitpeek32liest und durchIPUnpackschickt, bekommt die Oktette rückwärts (172.66.147.243wird zu243.147.66.172).IPPack/IPUnpackarbeiten 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:
falseals Rückgabewert — es kam gar keine Antwort (Timeout, Netz weg, Server nicht erreichbar).rcode != DNS_RCODE_OK— es kam eine Antwort, aber eine ablehnende.DNS_RCODE_NXDOMAINheißt „Name existiert nicht„,DNS_RCODE_SERVFAIL„Server hat versagt“. Wer nur den ersten Fall prüft, hält ein NXDOMAIN für einen Erfolg mit leerem Ergebnis.
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 lassenHTTPGethängen. Antwortet die Gegenseite mitContent-LengthundConnection: Upgradestatt mitTransfer-Encoding: chunked(beobachtet beineverssl.com), kehrt der Aufruf nicht zurück — das Programm wartet unbegrenzt, die Zeile nachHTTPGetwird nie erreicht. Dieselbe Adresse beantwortetcurlin dreieinhalb Sekunden.
Wer gegen fremde, unbekannte Server spricht, sollte den Aufruf deshalb nicht ohne Aufsicht laufen lassen: entwederHTTPSGetverwenden oder die Anfrage überstd.net.socketmit eigenem Timeout selbst schreiben (Abschnitt 8). Geprüft mit lyxc 1.0.16F, stdlib-Stand80479041.
(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);
HTTPSetHeadergibt den (womöglich neu angelegten) Header-Block zurück — Rückgabewert wieder zuweisen, sonst geht das Ergebnis verloren.HTTPSetBasicAuth(hdrs, user, pass)für Basic-Auth,HTTPSetBearerTokenfür Token.HTTPGetH,HTTPPostH,HTTPDeleteHsind die Varianten mit Headern.HTTPSendWithRedirectsbzw.HTTPGetWithRedirectsfolgen bis zuHTTP_MAX_REDIRECTS(10) Weiterleitungen. Die einfachen Funktionen folgen keiner — ein 301 kommt als 301 zurück.HTTPGetHeader(resp, name)liest einen Antwortheader aus.
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;
}
- Der Client ist ein Speicherblock der Größe
REST_SIZE— selbst anlegen, selbst freigeben.RestClientFreegibt die inneren Allokationen frei (Host, Pfad, Auth-Header),freedanach den Block selbst. https:in der Basis-URL schaltet TLS und Port 443 automatisch. *RestJsonPost/RestJsonPutsetzenContent-Type: application/jsonmit. *RestQueryAppend(buf, bufMax, key, value)baut Query-Strings mit korrekter Kodierung. *RestIsSuccess/RestIsClientError/RestIsServerErrorklassifizieren den Status, statt Zahlen zu vergleichen. * Jede Antwort braucht ihrHTTPResponseFree— auch die fehlgeschlagene. —- ===== 7. TLS =====std.net.httpsist der bequeme Weg (HTTPSGet,HTTPSPost). Wer den Kanal selbst steuert — eigenes Protokoll, langlebige Verbindung, eigenes Framing — nimmtstd.net.tlsdirekt. Die Unit setzt auf OpenSSL auf; sie ist die einzige Net-Unit mit externer Abhängigkeit.TLSInit
Reihenfolge und Lebensdauer sind hier keine Kosmetik: *import std.io; import std.alloc; import std.net.socket; import std.net.tls; import std.net.types; fn main(): int64 { var ctx: TLSContext := TLSInit(); // einmal je Prozess if (ctx.initialized != 1) { PrintLn("TLS-Init fehlgeschlagen"); return 1; } var conn: TCPConn := TCPConnect(IPPack(172, 66, 147, 243), 443); if (conn.fd < 0) { TLSFree(ctx); return 1; } var host: pchar := "example.com"c; // Hostname für SNI und Zertifikatsprüfung var tls: TLSConn := TLSConnect(ctx, conn.fd, host as int64); if (tls.connected != 1) { PrintLn("Handshake fehlgeschlagen"); TCPConnClose(conn); TLSFree(ctx); return 1; } var req: pchar := "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"c; TLSWrite(tls, req as int64, 56); var buf: int64 := alloc(4096); var n: int64 := TLSRead(tls, buf, 4095); if (n > 0) { poke8(buf + n, 0); var antwort: pchar := buf as pchar; PrintLn(antwort); } free(buf, 4096); TLSClose(tls); // TLS-Schicht zuerst TCPConnClose(conn); // dann das Socket TLSFree(ctx); // zuletzt der Kontext return 0; }einmal je Prozess, nicht je Verbindung. Der Kontext hält die geladenen Wurzelzertifikate. * Der Hostname muss anTLSConnectübergeben werden — er dient SNI und dem Abgleich mit dem Zertifikat. Ohne ihn verbindet sich das Programm mit irgendwem. * Abgeräumt wird von innen nach außen:TLSClose→TCPConnClose→TLSFree. * Fehlercodes:TLS_ERR_CONNECT(Handshake),TLS_ERR_VERIFY(Zertifikat abgelehnt),TLS_ERR_HOSTNAME(Name passt nicht zum Zertifikat).TLS_ERR_HOSTNAMEist kein Netzfehler, sondern ein Sicherheitsbefund — nicht wegfangen. —- ===== 8. Server ===== ==== Der Grundaufbau ====TIME_WAIT
*import std.io; import std.net.socket; import std.net.types; fn main(): int64 { var l: TCPListener := TCPListenerNew(); if (l.fd < 0) { return 1; } TCPListenerSetReuseAddr(l); // Neustart ohne TIME_WAIT-Sperre if (TCPListenerBindTo(l, IPPack(0, 0, 0, 0), 8080) < 0) { return 1; } if (TCPListenerListen(l, 16) < 0) { return 1; } var buf: int64 := BufferAlloc(4096); while (true) { var c: TCPConn := TCPListenerAccept(l); if (c.fd < 0) { continue; } // Fehler beim Annehmen: weiter var n: int64 := TCPConnRead(c, buf, 4096); if (n > 0) { TCPConnWrite(c, buf, n); } TCPConnClose(c); // jede Verbindung schließen } BufferFree(buf, 4096); TCPListenerClose(l); return 0; }TCPListenerSetReuseAddrvor dem Bind. Ohne ihn scheitert ein Neustart, solange alte Verbindungen inhängen. *IPPack(0,0,0,0)bindet an alle Schnittstellen,IPPack(127,0,0,1)nur an localhost. Für Dienste, die nicht nach außen sollen, ist das die erste Sicherheitsmaßnahme. * Ports unter 1024 brauchen zusätzlichCAP_NET_BIND_SERVICEvom Betriebssystem — die Lyx-Capability allein genügt dafür nicht. *TCPConnClosein jedem Zweig. Ein vergessenes Close leckt einen Dateideskriptor; nach ein paar tausend Verbindungen nimmtacceptnichts mehr an. ==== Viele Verbindungen: epoll ==== Ein Durchlauf pro Verbindung skaliert nicht.std.net.epollmeldet, welche Deskriptoren bereit sind, und bedient damit tausende Verbindungen in einem Thread.SocketSetNonBlocking
Was hier zählt: * Nicht-blockierend ist Pflicht. Ohneimport std.io; import std.alloc; import std.net.socket; import std.net.epoll; import std.net.types; con MAX_EVENTS: int64 := 64; fn main(): int64 { var l: TCPListener := TCPListenerNew(); TCPListenerSetReuseAddr(l); if (TCPListenerBindTo(l, IPPack(0, 0, 0, 0), 8080) < 0) { return 1; } if (TCPListenerListen(l, 128) < 0) { return 1; } SocketSetNonBlocking(l.fd); var ep: int64 := EpollCreate(); if (ep < 0) { return 1; } EpollAdd(ep, l.fd, EPOLLIN, l.fd); var evBuf: int64 := alloc(MAX_EVENTS * EPOLL_EVENT_SIZE); var buf: int64 := alloc(4096); while (true) { var ready: int64 := EpollWait(ep, evBuf, MAX_EVENTS, -1); // -1 = warten var i: int64 := 0; while (i < ready) { var fd: int64 := EpollEventFd(evBuf, i); var flags: int64 := EpollEventFlags(evBuf, i); if (fd == l.fd) { var c: TCPConn := TCPListenerAccept(l); if (c.fd >= 0) { SocketSetNonBlocking(c.fd); EpollAdd(ep, c.fd, EPOLLIN | EPOLLRDHUP, c.fd); } } else { if ((flags & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) != 0) { var dead: TCPConn; dead.fd := fd; EpollDel(ep, fd); TCPConnClose(dead); } else { var conn: TCPConn; conn.fd := fd; var n: int64 := TCPConnRead(conn, buf, 4096); if (n > 0) { TCPConnWrite(conn, buf, n); } else { EpollDel(ep, fd); TCPConnClose(conn); } } } i := i + 1; } } return 0; }blockiert ein einziger langsamer Client die gesamte Schleife. *EPOLLRDHUPmeldet, dass die Gegenseite geschlossen hat.EPOLLERRundEPOLLHUPsind immer aktiv, auch ohne Anmeldung — sie müssen behandelt werden, sonst dreht die Schleife auf einem toten Deskriptor durch. * Der Nutzwert (viertes Argument vonEpollAdd) kommt inEpollEventDatazurück. Hier wird schlicht der Deskriptor mitgegeben; für eine Verbindungstabelle setzt man dort einen Index oder eine Adresse ein. *EpollDelvor dem Schließen. Ein geschlossener Deskriptor verschwindet zwar von selbst aus dem Set, aber nur, wenn ihn nichts anderes offen hält. *EpollWaitmit Timeout in Millisekunden oder-1für unbegrenzt. Wer nebenher periodische Arbeit erledigen will, gibt ein Timeout an, statt einen zweiten Thread aufzumachen. *EventFdCreate/EventFdSignalweckt eine wartendeEpollWaitaus einem anderen Thread — der saubere Weg, einen Server herunterzufahren. ==== Ein Socket aus einem Deskriptor ====TCPConnist eine schlichte Struktur um den Deskriptor. Wer aus epoll nur eine Zahl zurückbekommt, baut sich die Struktur wieder zusammen:network.tcp.connect
—- ===== 9. UDP ===== Verbindungslos: kein Handshake, keine Reihenfolgegarantie, kein Wiederholen. Dafür ein Syscall weniger und kein Verbindungszustand.var conn: TCPConn; conn.fd := fd;
Absender-IP und -Port sind Ausgabeparameter als Zelle — acht Byte Speicher, die die Funktion beschreibt. Das ist in Lyx das durchgängige Muster für Rückgaben jenseits des einen Rückgabewerts, weil es keinen Adressoperator gibt. Ein Datagramm passt in einen Aufruf. Der Puffer muss deshalb groß genug für das größte erwartete Paket sein — was nicht passt, wird abgeschnitten und ist verloren. —- ===== 10. Capabilities und die Sandbox ===== Netzwerkzugriff ist der Bereich, in dem sich das Capability-Modell am deutlichsten auswirkt: Ohne Deklaration läuft das Programm ungehärtet; mit Deklaration schaltet der Compiler seccomp, Landlock und den Netzwerk-Proxy scharf. ^ Capability ^ Erlaubt ^ |@capabilities([system.exit, system.memory.heap, network.udp.bind]) import std.io grant [system.write]; import std.alloc grant [system.memory.heap]; import std.net.socket grant [network.udp.bind, system.memory.heap]; fn main(): int64 { var sock: UDPSocket := UDPSocketNew(); if (sock.fd < 0) { return 1; } UDPSocketSetReuseAddr(sock); if (UDPSocketBindTo(sock, IPPack(0, 0, 0, 0), 9000) < 0) { return 1; } var buf: int64 := alloc(2048); var ipZelle: int64 := alloc(8); // Ausgabeparameter: Absender-IP var portZelle: int64 := alloc(8); // Ausgabeparameter: Absender-Port var n: int64 := UDPSocketRecv(sock, buf, 2048, ipZelle, portZelle); if (n > 0) { Print("Bytes: "); PrintLn(IntToStr(n)); Print("Absender-Port: "); PrintLn(IntToStr(peek64(portZelle))); UDPSocketSendToAddr(sock, buf, n, peek64(ipZelle), peek64(portZelle)); } free(portZelle, 8); free(ipZelle, 8); free(buf, 2048); UDPSocketClose(sock); return 0; }| ausgehende TCP-Verbindung (Client, HTTP, TLS) | |network.tcp.bind| TCP-Port belegen (Server) | |network.udp.connect| UDP ausgehend | |network.udp.bind| UDP-Port belegen | |network.unix| Unix-Domain-Sockets | |network.raw| Raw-Sockets (ICMP, ARP) — höchste Stufe, nur mit gutem Grund | Dazu praktisch immer:system.exit,system.memory.heap(Puffer) undsystem.write(Ausgabe) für die importierten Units.grant []
Der Compiler quittiert das im LCBS Security Audit nach jeder Übersetzung:@capabilities([system.exit, system.memory.heap, network.tcp.connect]) import std.io grant [system.write]; import std.net.socket grant [network.tcp.connect, system.memory.heap]; import std.net.types grant [];
* Jeder Import ohneExplizite Capabilities: + system.exit + system.memory.heap + network.tcp.connect Runtime-Schutz: + seccomp (SECCOMP_RET_KILL_PROCESS, 71 Regeln) + landlock (0 Pfad-Regeln) + Userspace Proxy (Netzwerk) + Stack Canaries Sicherheits-Score: 38/45grantkostet 2 Punkte und wird als Warnung gemeldet. Ein leeresist eine gültige Aussage („diese Unit braucht nichts„) und kostet nichts. * Ohne@capabilitiessteht dortCapability-Modell: NONE— kein seccomp, keine Härtung. Für ein produktives Netzprogramm ist das die falsche Voreinstellung. * Die Sandbox tötet den Prozess bei einem nicht erlaubten Syscall (SECCOMP_RET_KILL_PROCESS) — es gibt keinen Fehlercode zum Abfangen. Wer im Betrieb ein wortloses Sterben sieht, prüft zuerst die Capability-Liste. <box gh> >network.tcp.connect(host: „api.example.com“)host:undport:an Netzwerk-Capabilities werden nicht durchgesetzt.prüft nur den Argumentnamen; die Verbindung zu jedem anderen Rechner bleibt möglich. Der Compiler meldet das an jeder betroffenen Stelle mitCapability-Argument wird NICHT durchgesetzt. Nurpath:anfs-Capabilities wirkt (Landlock-Regel je Pfad). > (Issue #1108) </box> —- ===== 11. Fehler behandeln ===== Es gibt drei Fehlerquellen, die man auseinanderhalten muss: ^ Ebene ^ Erkennbar an ^ Typische Ursache ^ | Socket |fd < 0, Rückgabewert < 0 | kein Deskriptor frei, Adresse belegt, Ziel nicht erreichbar | | Verbindung |TCPConnGetError(conn)| Verbindung abgewiesen, unterwegs abgerissen | | Protokoll |statusCode,rcode| HTTP 404, DNS NXDOMAIN — das Netz funktionierte |TCPConnSetNodelay(conn, true)
Weitere Stellschrauben an einer bestehenden Verbindung: *var conn: TCPConn := TCPConnect(ip, 443); if (conn.fd < 0) { // gar nicht erst zustande gekommen return 1; } var err: int64 := TCPConnGetError(conn); if (err != 0) { // Socket existiert, Verbindung ist trotzdem defekt TCPConnClose(conn); return 1; }— Nagle abschalten. Richtig für interaktive Protokolle mit kleinen Nachrichten, falsch für Massendurchsatz. *TCPConnSetKeepAlive(conn, true)— tote Gegenstellen erkennen, die sich nicht verabschiedet haben. *TCPConnSetRecvBuf/TCPConnSetSendBuf— Puffergrößen im Kernel.< 0TCPConnReadgibt 0 zurück, wenn die Gegenseite geschlossen hat — das ist kein Fehler, sondern das Ende. Wer nur aufprüft, dreht danach in einer Endlosschleife. —- ===== 12. Ressourcen: was muss wieder weg ===== ^ Angelegt mit ^ Freigegeben mit ^ |BufferAlloc(n)|BufferFree(buf, n)— mit derselben Größe | |alloc(n)|free(ptr, n)| |TCPConnect,TCPListenerAccept|TCPConnClose| |TCPListenerNew|TCPListenerClose| |UDPSocketNew|UDPSocketClose| |EpollCreate| Deskriptor schließen | |HTTPGetund alle Verwandten |HTTPResponseFree(resp)— auch im Fehlerfall | |HTTPSetHeader-Kette |HTTPHeadersFree(hdrs)| |RestClientInit|RestClientFreeundfreeauf den Block | |TLSInit|TLSFree| |TLSConnect|TLSClose| Ein Serverprozess läuft Wochen. Jedes vergesseneCloseist dort kein Schönheitsfehler, sondern ein Ausfall mit Ansage. —- ===== 13. Fallstricke ===== <box gh> >PrintLn(buf as pchar)PrintLn(feld)druckt bei einempchar-Feld die Adresse statt der Zeichenkette. Betroffen ist der Zugriff auf ein Struct- oder Klassenfeld: > >
> > Die Typumwandlung im Argument (> PrintLn(resp.statusText); // gibt die Adresse aus > var s: pchar := resp.statusText; > PrintLn(s); // richtig >) war bis lyxc 1.0.15G ebenso betroffen und arbeitet seit 1.0.16F korrekt.PrintStrLnist in keinem der Fälle betroffen. </box> * Der Empfangspuffer ist nicht nullterminiert. NachTCPConnRead/TLSReadsteht am Ende der Daten kein0. Wer den Inhalt als Zeichenkette behandeln will, setzt es selbst:poke8(buf + n, 0)— und liest deshalb nurbufsize - 1Byte ein. * EinSetReuseAddrreadist keine Nachricht. TCP kennt keine Grenzen. Eine „Zeile“ oder ein „Paket„ kommt womöglich in drei Teilen an oder zwei Nachrichten in einem Aufruf. Wer ein Protokoll spricht, muss selbst puffern und trennen. *HTTPSetHeadergibt einen neuen Block zurück — Rückgabewert zuweisen, sonst ist der Header weg. * Ports unter 1024 brauchen Betriebssystem-Rechte, unabhängig von Lyx-Capabilities. * Zwei Programme, ein Port:erlaubt den schnellen Neustart,SetReusePort'' erlaubt mehrere Prozesse am selben Port (Lastverteilung durch den Kernel). Das sind verschiedene Dinge. * TLS ohne Hostname hebt die Zertifikatsprüfung praktisch auf. Immer den echten Namen übergeben. —- ===== 14. Weiterführend ===== * std.net — vollständige Unit-Referenz (alle 28 Units) * std.net.socket · std.net.http · std.net.tls · std.net.epoll · std.net.dns * Capabilities — das Sicherheitsmodell im Detail * Rohspeicher: alloc, peek & poke * Handles und Dateideskriptoren * Cloud-Guide — HTTP-Aufrufe gegen AWS, DigitalOcean, GCP * Kryptographie-Guide — was hinter TLS liegt Alle Codebeispiele dieser Seite sind mit lyxc 1.0.16F übersetzt worden.
