Grundlagen der zuverlĂ€ssigen DatenĂŒbertragung

Grundlagen der zuverlĂ€ssigen DatenĂŒbertragung

FĂŒr diejenigen, die danach sich mit Netzwerken und Protokollen auseinandersetzen möchten.

Zusammenfassung

In diesem Artikel werden die Grundlagen der zuverlĂ€ssigen DatenĂŒbertragung erlĂ€utert, mit Beispielen auf Go, einschließlich UDP und TCP. Inspiriert von eins, zwei, drei und dem Buch "Computer Networks: A Top-Down Approach", da nur ĂŒber Tanenbaum und Olifer gesprochen wird.

Transportprotokoll

Stellt eine logische Verbindung zwischen Anwendungsprozessen her, die auf unterschiedlichen Hosts ausgefĂŒhrt werden. Aus Sicht der Anwendungen sieht eine logische Verbindung wie ein Kanal aus, der die Prozesse direkt verbindet.

Grundlagen der zuverlĂ€ssigen DatenĂŒbertragung

Transportprotokolle werden von Endsystemen unterstĂŒtzt, nicht von Netzwerkrouten (mit Ausnahme von — DPI). Auf der Senderseite wandelt die Transportschicht die Anwendungsdaten, die sie vom ĂŒbertragenden Anwendungsprozess erhĂ€lt, in Transportschichtpakete um, die Segmente genannt werden.

Grundlagen der zuverlĂ€ssigen DatenĂŒbertragung

Dies geschieht durch die Aufteilung (wenn nötig) der Anwendungsnachrichten in Fragmente und das HinzufĂŒgen eines Headers der Transportschicht zu jedem dieser Fragmente.

Grundlagen der zuverlĂ€ssigen DatenĂŒbertragung

Anschließend ĂŒbertrĂ€gt die Transportschicht das Segment an die Netzwerkschicht des Senders, wo das Segment in ein Netzpaket (Datagramm) eingekapselt und versendet wird. Auf der EmpfĂ€ngerseite extrahiert die Netzwerkschicht das Segment der Transportschicht aus dem Datagramm und leitet es an die Transportschicht weiter. Daraufhin verarbeitet die Transportschicht das erhaltene Segment, damit die Daten der Empfangsanwendung zur VerfĂŒgung stehen.

Grundlagen der zuverlĂ€ssigen DatenĂŒbertragung

Prinzipien der zuverlĂ€ssigen DatenĂŒbertragung

ZuverlĂ€ssige DatenĂŒbertragung ĂŒber einen absolut zuverlĂ€ssigen Kanal

Der einfachste Fall. Die sendende Seite nimmt einfach die Daten von der oberen Ebene entgegen, erstellt ein Paket, das diese enthĂ€lt, und sendet es ĂŒber den Kanal.

Server

package main

import (
    "log"
    "net"
)

func main() {
    \/\/ IP-Adresse des Servers und Port
    serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:12000")
    if err != nil {
        log.Fatal(err)
    }

    \/\/ Erstellen eines Sockets mit Port
    serverConn, err := net.ListenUDP("udp", serverAddr)
    if err != nil {
        log.Fatal(err)
    }
    \/\/ Verzögertes Schließen der Verbindung
    defer serverConn.Close()

    \/\/ Erstellen eines Puffers fĂŒr Daten
    buf := make([]byte, 1024)

    \/\/ Warten auf Verbindung
    for {
        \/\/ Lesen der Anfrage
        n, addr, err := serverConn.ReadFromUDP(buf)
        \/\/ Übertragen der Daten an die OBERSTE Ebene: in unserem Fall stdout
        println(string(buf[0:n]), " form ", addr.IP.String())
        if err != nil {
            log.Fatal(err)
        }
        \/\/ Keine Antwort, da dies UDP + zuverlÀssiger Kanal ist
    }
}

Kunde

package main

import (
    "fmt"
    "log"
    "net"
    "time"
)

func main() {
    // IP-Adresse des Servers und Port
    serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:12000")
    if err != nil {
        log.Fatal(err)
    }
    // Lokale IP-Adresse und Port
    localAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
    if err != nil {
        log.Fatal(err)
    }
    // Verbindung herstellen
    conn, err := net.DialUDP("udp", localAddr, serverAddr)
    if err != nil {
        log.Fatal(err)
    }
    // Verzögertes Schließen der Verbindung
    defer conn.Close()

    for {
        // Eingabe von Daten auf OBERE EBENE
        fmt.Print("Bitte geben Sie einen Satz ein > ")
        var msg string
        _, err := fmt.Scanf("%s", &msg)
        if err != nil {
            log.Fatal(err)
        }
        // Es wird ein Byte-Stream ĂŒbermittelt, kein String
        buf := []byte(msg)
        // Schreiben (Übertragung) in die Verbindung
        _, err = conn.Write(buf)
        if err != nil {
            log.Fatal(err)
        }
        // 1 Sekunde warten
        time.Sleep(time.Second * 1)
    }
}

ZuverlĂ€ssige DatenĂŒbertragung ĂŒber KanĂ€le mit potenziellen Fehlern

Der nĂ€chste Schritt besteht darin, davon auszugehen, dass alle ĂŒbertragenen Pakete in der Reihenfolge empfangen wurden, in der sie gesendet wurden, aber die Bits in ihnen möglicherweise beschĂ€digt sind, da der Kanal manchmal Daten verzerrt ĂŒbertrĂ€gt.

Grundlagen der zuverlĂ€ssigen DatenĂŒbertragung

In diesem Fall kommen folgende Mechanismen zum Einsatz:

  • Fehlererkennung;
  • Feedback;
  • WiederĂŒbertragung.

Übertragungsprotokolle, die ĂŒber Mechanismen zur mehrmaligen Übertragung verfĂŒgen, werden als Protokolle mit automatischem Wiederholungsanfrage (Automatic Repeat reQuest, ARQ) bezeichnet.
ZusĂ€tzlich sollte auch die Möglichkeit von Fehlern in den EmpfangsbestĂ€tigungen berĂŒcksichtigt werden, wenn die empfangende Seite keine Informationen ĂŒber das Ergebnis der Übertragung des letzten Pakets erhĂ€lt.
Die Lösung dieses Problems, die auch in TCP verwendet wird, besteht darin, dem Datenpaket ein neues Feld hinzuzufĂŒgen, das die Seriennummer des Pakets enthĂ€lt.

Grundlagen der zuverlĂ€ssigen DatenĂŒbertragung

ZuverlĂ€ssige DatenĂŒbertragung ĂŒber einen unzuverlĂ€ssigen Kanal, der Verzerrungen und Paketverluste zulĂ€sst.

Leider treten in Netzwerken neben Verzerrungen auch Paketverluste auf.
Um dieses Problem zu lösen, werden folgende Mechanismen benötigt:

  • Erkennung des Paketverlusts;
  • erneute Zustellung der verlorenen Pakete an die empfangende Seite.

ZusĂ€tzlich zu Paketverlusten muss auch die Möglichkeit des Verlusts einer BestĂ€tigung oder, falls nichts verloren geht, der verspĂ€teten Zustellung berĂŒcksichtigt werden. In allen FĂ€llen erfolgt das Gleiche: die erneute Übertragung des Pakets. Ein Timer wird zur Kontrolle der Zeit in diesem Mechanismus verwendet, der das Ende des Wartintervals bestimmt. So im Paket. net Der TCPKeepAlive-Parameter ist standardmĂ€ĂŸig auf 15 Sekunden eingestellt:

// defaultTCPKeepAlive is a default constant value for TCPKeepAlive times
// See golang.org/issue/31510
const (
    defaultTCPKeepAlive = 15 * time.Second
)

Der sendenden Seite muss bei jeder PaketĂŒbertragung (sowohl bei der ersten als auch bei der Wiederholung) den Timer starten, Interrupts vom Timer verarbeiten und ihn anhalten.

Nun sind wir mit den SchlĂŒsselkConcepten der zuverlĂ€ssigen DatenĂŒbertragungsprotokolle vertraut:

  • PrĂŒfziffern;
  • Paketsequenznummern;
  • Timer;
  • positive und negative BestĂ€tigungen.

Aber das ist noch nicht alles!

Das Protokoll fĂŒr zuverlĂ€ssige DatenĂŒbertragung mit Pipeline-Verarbeitung.

In der bereits betrachteten Variante ist das Protokoll fĂŒr die zuverlĂ€ssige Zustellung sehr ineffizient. Es beginnt, die Übertragung ĂŒber den Kommunikationskanal zu „verlangsamen“, wenn die RTT steigt. Um die Effizienz zu steigern und eine bessere Auslastung der Bandbreite zu erreichen, wird die Pipeline-Technologie eingesetzt.

Grundlagen der zuverlĂ€ssigen DatenĂŒbertragung

Die Anwendung der Pipeline-Technologie fĂŒhrt zu:

  • einer Erhöhung des Bereichs der Sequenznummern, da alle gesendeten Pakete (außer den WiederĂŒbertragungen) eindeutig identifizierbar sein mĂŒssen;
  • der Notwendigkeit, die Puffer sowohl auf der Sender- als auch auf der Empfangsseite zu vergrĂ¶ĂŸern.

Der Bereich der Sequenznummern und die Anforderungen an die PufferspeichergrĂ¶ĂŸe hĂ€ngen von den Maßnahmen ab, die das Protokoll als Reaktion auf Verzerrungen, Paketverlust und Verzögerungen ergreift. Bei der Pipeline-Technologie gibt es zwei Methoden zur Fehlerkorrektur:

  • RĂŒckkehr um N Pakete;
  • selektive Wiederholung.

Die RĂŒckkehr um N Pakete ist ein Sliding-Window-Protokoll.

Grundlagen der zuverlĂ€ssigen DatenĂŒbertragung

Der Sender muss drei Arten von Ereignissen verwalten:

  • Aufruf durch ein höheres Protokoll. Wenn die Funktion zum Senden von Daten auf einer höheren Ebene aufgerufen wird, ĂŒberprĂŒft die sendende Seite zunĂ€chst den FĂŒllstand des Fensters (d.h. die Anzahl der gesendeten Nachrichten, die auf eine BestĂ€tigung warten). Ist das Fenster nicht voll, wird ein neues Paket gebildet und ĂŒbertragen, und die Variablenwerte werden aktualisiert. Andernfalls gibt die sendende Seite die Daten an die oberere Ebene zurĂŒck, was implizit bedeutet, dass das Fenster voll ist. In der Regel unternimmt die obere Ebene nach einiger Zeit einen neuen Übertragungsversuch. In einer realen Anwendung hĂ€tte der Sender wahrscheinlich die Daten zwischengespeichert (anstatt sie sofort zu senden) oder einen Synchronmechanismus (wie ein Semaphore oder Flag) implementiert, welcher es der höheren Ebene ermöglichen wĂŒrde, die Funktion zum Senden von Daten nur bei einem nicht vollen Fenster aufzurufen.
  • BestĂ€tigungsempfang. Im Protokoll erhĂ€lt das Paket mit der Sequenznummer N eine allgemeine Quittung, die angibt, dass alle Pakete mit Sequenznummern, die vor N kommen, erfolgreich empfangen wurden.
  • Die Wartezeit ist abgelaufen. Um Paketverluste und Verzögerungen sowie BestĂ€tigungen zu bestimmen, verwendet das Protokoll einen Timer. Wenn die Wartezeit ablĂ€uft, sendet die sendende Seite alle unbestĂ€tigten Pakete erneut.

Selective Repeat

Wenn die FenstergrĂ¶ĂŸe und das Produkt aus Bandbreite und Ausbreitungsverzögerung groß sind, kann sich eine erhebliche Anzahl von Paketen in der Pipeline befinden. In diesem Fall kann der Fehler eines einzelnen Pakets dazu fĂŒhren, dass eine große Anzahl von Paketen erneut ĂŒbertragen wird, von denen die meisten nicht erforderlich waren.

Beispiel

Die besten theoretischen Praktiken sind in einer praktischen Umsetzung zusammengefasst TCP. Und falls jemand weiß, wie es besser geht — Willkommen.

Server

package main

import (
    "bufio"
    "fmt"
    "log"
    "net"
    "strings"
)

func main() {
    // Erstellen eines Sockets mit Port 
    ln, err := net.Listen("tcp", ":8081")
    if err != nil {
        log.Fatalln(err)
    }
    // Warten auf einen Aufruf
    conn, _ := ln.Accept()

    for {
        // Daten lesen
        msg, err := bufio.NewReader(conn).ReadString('n')
        if err != nil {
            log.Fatalln(err)
        }
        // Nachricht in stdout ausgeben
        fmt.Print("Nachricht empfangen:", string(msg))
        // String in Großbuchstaben umwandeln
        newMsg := strings.ToUpper(msg)
        // Daten senden
        conn.Write([]byte(newMsg + "n"))
    }
}

Kunde

package main

import (
    "bufio"
    "fmt"
    "log"
    "net"
    "os"
)

func main() {
    // Verbindung herstellen
    conn, err := net.Dial("tcp", "127.0.0.1:8081")
    if err != nil {
        log.Fatalln(err)
    }

    for {
        // Daten von stdin lesen
        reader := bufio.NewReader(os.Stdin)
        fmt.Print("Text zu senden: ")
        // zeilenweise
        text, err := reader.ReadString('n')
        if err != nil {
            log.Fatalln(err)
        }
        // senden
        fmt.Fprintf(conn, text+"n")
        // empfangen
        msg, err := bufio.NewReader(conn).ReadString('n')
        if err != nil {
            log.Fatalln(err)
        }
        // Ausgabe der empfangenen Antwort
        fmt.Print("Nachricht vom Server: " + msg)
    }
}

Fazit

Mechanismen zur GewĂ€hrleistung einer zuverlĂ€ssigen DatenĂŒbertragung und deren Nutzung

Mechanismus
Anwendung, Kommentar

PrĂŒfziffer
Wird zur Erkennung von Bitfehlern im ĂŒbertragenen Paket verwendet

Timer
Zeitintervall und Indikator fĂŒr dessen Ablauf. Letzteres deutet darauf hin, dass mit hoher Wahrscheinlichkeit das Paket oder dessen BestĂ€tigung wĂ€hrend der Übertragung verloren gegangen sind. Wenn das Paket mit Verzögerung, aber nicht verloren ankommt (vorzeitiges Ablaufen des Zeitintervalls), oder wenn die BestĂ€tigung verloren geht, fĂŒhrt eine erneute Übertragung zu einer Verdopplung des Pakets auf der EmpfĂ€ngerseite.

Sequenznummer
Wird zur sequenziellen Nummerierung von Datenpaketen verwendet, die vom Sender an den EmpfĂ€nger ĂŒbertragen werden. LĂŒcken in den sequentiellen Nummern der empfangenen Pakete ermöglichen es dem EmpfĂ€nger, den Verlust eines Pakets zu erkennen. Identische sequentielle Nummern der Pakete bedeuten, dass die Pakete sich gegenseitig duplizieren.

BestÀtigung
Wird von der empfangenden Seite erzeugt und zeigt der sendenden Seite an, dass das entsprechende Paket oder eine Gruppe von Paketen erfolgreich empfangen wurde. In der Regel enthÀlt die BestÀtigung die sequentiellen Nummern der erfolgreich empfangenen Pakete. AbhÀngig vom Protokoll wird zwischen Einzel- und GruppenbestÀtigungen unterschieden.

Negative BestÀtigung
Wird vom EmpfÀnger verwendet, um dem Sender mitzuteilen, dass das Paket fehlerhaft empfangen wurde. Die negative BestÀtigung enthÀlt in der Regel die sequentielle Nummer des Pakets, das nicht korrekt empfangen wurde.

Fenster, Pipeline
EinschrĂ€nkungen des Bereichs der Sequenznummern, die fĂŒr die PaketĂŒbertragung genutzt werden können. Gruppierung und Handshake ermöglichen eine signifikante Erhöhung der ProtokolldurchsatzfĂ€higkeit im Vergleich zum BestĂ€tigungsmodus. Wie wir sehen werden, kann die FenstergrĂ¶ĂŸe basierend auf den Empfangs- und PufferkapazitĂ€ten der Empfangsseite sowie dem Netzwerkbelastungsgrad berechnet werden.

Weitere Beispiele fĂŒr die Verwendung von Go zur Netzwerkarbeit.

In Repository.

Quelle: habr.com

Erwerben Sie zuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz, VPS VDS-Server đŸ”„ Kaufen Sie zuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster