Grundlagen der zuverlĂ€ssigen DatenĂŒbertragung

Grundlagen der zuverlĂ€ssigen DatenĂŒbertragung

An diejenigen, die verstehen wollen , die sich mit Netzwerken und Protokollen beschÀftigen.

Kurz

In diesem Artikel werden die Grundlagen der zuverlĂ€ssigen DatenĂŒbertragung behandelt und es werden Beispiele auf Gogegeben, darunter UDP und TCP. Inspiriert von eins, zwei, drei und dem Buch "Computer-Netzwerke. Der absteigende Ansatz", da nur ĂŒber Tanenbaum und OliĂ©fer diskutiert wird.

Transportprotokoll

GewĂ€hrleistet eine logische Verbindung zwischen Anwendungsprozessen, die auf verschiedenen Hosts ausgefĂŒhrt werden. Die logische Verbindung sieht aus Sicht der Anwendungen wie ein Kanal aus, der die Prozesse direkt verbindet.

Grundlagen der zuverlĂ€ssigen DatenĂŒbertragung

Transportprotokolle werden von Endsystemen unterstĂŒtzt, jedoch nicht von Netzwerk-Routern (außer — DPI). Auf der Senderseite wandelt die Transportschicht die vom anwendenden Prozess erhaltenen Daten in Transport-Pakete um, die Segmente genannt werden.

Grundlagen der zuverlĂ€ssigen DatenĂŒbertragung

Dies geschieht durch das Zerlegen (falls notwendig) von Anwendungsnachrichten in Fragmente und das HinzufĂŒgen eines Transportprotokoll-Headers zu jedem davon.

Grundlagen der zuverlĂ€ssigen DatenĂŒbertragung

Anschließend ĂŒbertrĂ€gt die Transportschicht das Segment an die Netzwerkschicht des Senders, wo das Segment in ein Netzwerk-Paket (Datagramm) eingekapselt und versendet wird. Auf der EmpfĂ€ngerseite extrahiert die Netzwerkschicht das Transportsegment aus dem Datagramm und ĂŒbergibt es an die Transportschicht. Diese verarbeitet das empfangene Segment so, dass die Daten der Anwendung des EmpfĂ€ngers zugĂ€nglich sind.

Grundlagen der zuverlĂ€ssigen DatenĂŒbertragung

Prinzipien der zuverlĂ€ssigen DatenĂŒbertragung

ZuverlĂ€ssige DatenĂŒbertragung ĂŒber einen völlig zuverlĂ€ssigen Kanal

Der einfachste Fall. Die sendende Seite nimmt einfach die Daten von der höheren Ebene entgegen, erstellt ein Paket, das sie 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)
    }

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

    // Puffer fĂŒr Daten erstellen
    buf := make([]byte, 1024)

    // Verbindung abwarten
    for {
        // Anfrage lesen
        n, addr, err := serverConn.ReadFromUDP(buf)
        // Daten an die OBERSTE Ebene weitergeben: 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Àssige Verbindung ist
    }
}

den Kunden zurĂŒckzufĂŒhren sind, nicht verfĂŒgbar ist.

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)
    }
    // lokaler 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 {
        // Daten von der OBERSTEN Ebene erhalten
        fmt.Print("Geben Sie einen Satz ein > ")
        var msg string
        _, err := fmt.Scanf("%s", &msg)
        if err != nil {
            log.Fatal(err)
        }
        // es wird ein Byte-Stream, nicht ein String ĂŒbergeben
        buf := []byte(msg)
        // schreiben (ĂŒbertragen) 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 möglichen Fehlern

Der nĂ€chste Schritt besteht darin, zu vermuten, dass alle ĂŒbertragenen Pakete in der Reihenfolge empfangen werden, in der sie gesendet wurden, aber die Bits in ihnen beschĂ€digt sein können, da der Kanal manchmal Daten mit Verzerrungen ĂŒbertrĂ€gt.

Grundlagen der zuverlĂ€ssigen DatenĂŒbertragung

In diesem Fall kommen folgende Mechanismen zur Anwendung:

  • Fehlererkennung;
  • RĂŒckmeldung;
  • WiederĂŒbertragung.

Protokolle fĂŒr die zuverlĂ€ssige DatenĂŒbertragung, die ĂŒber derartige Mechanismen zur mehrfachen Übertragung verfĂŒgen, werden als Protokolle mit automatischer Wiederholungsanforderung (Automatic Repeat reQuest, ARQ) bezeichnet.
DarĂŒber hinaus sollte man die Möglichkeit von Fehlern auch in den BestĂ€tigungen berĂŒcksichtigen, wenn die empfangende Seite keine Informationen ĂŒber das Ergebnis der Übertragung des letzten Pakets erhĂ€lt.
Die Lösung dieses Problems, die unter anderem in TCP verwendet wird, besteht darin, ein neues Feld mit der Paketnummer in das Datenpaket hinzuzufĂŒgen.

Grundlagen der zuverlĂ€ssigen DatenĂŒbertragung

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

Leider gibt es neben Verzerrungen auch Paketverluste im Netzwerk.
FĂŒr die Lösung dieses Problems sind Mechanismen erforderlich:

  • Erkennung von Paketverlusten;
  • WiederĂŒbertragung verlorener Pakete an die empfangende Seite.

ZusĂ€tzlich zur Paketverlust ist es notwendig, die Möglichkeit eines Verlusts von Quittungen oder, falls nichts verloren gegangen ist, deren Zustellung mit erheblichen Verzögerungen zu berĂŒcksichtigen. In allen FĂ€llen erfolgt das Gleiche: die erneute Übertragung des Pakets. Zur Kontrolle der Zeit wird in diesem Mechanismus ein Timer verwendet, der das Ende des Warteintervalls bestimmt. So ist im Paket net der TCPKeepAlive-Parameter 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
)

Die sendende Seite muss jedes Mal einen Timer starten, wenn ein Paket ĂŒbertragen wird (sowohl beim ersten als auch beim erneuten Senden), Unterbrechungen vom Timer verarbeiten und ihn anhalten.

Nun haben wir uns mit den SchlĂŒsselbegriffen der Protokolle fĂŒr zuverlĂ€ssige DatenĂŒbertragung vertraut gemacht:

  • PrĂŒfziffern;
  • Paketnummern;
  • Timer;
  • positive und negative Quittungen.

Aber das ist noch nicht alles!

Protokoll fĂŒr zuverlĂ€ssige DatenĂŒbertragung mit Pipeline-Übertragung

In der bereits betrachteten Variante ist das Protokoll fĂŒr zuverlĂ€ssige Übertragung sehr ineffizient. Es beginnt, die Übertragung, die durch den Kommunikationskanal gewĂ€hrleistet wird, bei steigenden RTTs zu „verlangsamen“. Um seine Effizienz zu steigern und die Bandbreite des Kommunikationskanals besser zu nutzen, wird die Pipeline-Übertragung verwendet.

Grundlagen der zuverlĂ€ssigen DatenĂŒbertragung

Die Anwendung der Pipeline-Übertragung fĂŒhrt zu:

  • einer Erhöhung des Bereichs der Paketnummern, da alle gesendeten Pakete (außer WiederholungsĂŒbertragungen) eindeutig identifizierbar sein mĂŒssen;
  • einem Bedarf an grĂ¶ĂŸeren Puffern auf der sendenden und empfangenden Seite.

Der Bereich der Paketnummern und die Anforderungen an die PuffergrĂ¶ĂŸen hĂ€ngen von den Maßnahmen ab, die das Protokoll als Reaktion auf Verzerrungen, Verluste und Verzögerungen von Paketen ergreift. Im Falle von Pipeline-Übertragungen gibt es zwei Fehlerkorrekturmöglichkeiten:

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

RĂŒckkehr um N Pakete — das 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 vom „oberen“ Level aufgerufen wird, ĂŒberprĂŒft die sendende Seite zunĂ€chst den FĂŒllgrad des Fensters (d.h. ob N gesendete Nachrichten auf die BestĂ€tigungen warten). Ist das Fenster nicht gefĂŒllt, wird ein neues Paket gebildet und gesendet, wĂ€hrend die Werte der Variablen aktualisiert werden. Andernfalls ĂŒbergibt die sendende Seite die Daten an das obere Level zurĂŒck, was eine implizite Anweisung darstellt, dass das Fenster gefĂŒllt ist. Üblicherweise unternimmt das obere Level nach einer gewissen Zeit einen erneuten Versuch, die Daten zu ĂŒbertragen. In einer realen Anwendung wĂŒrde der Sender wahrscheinlich entweder die Daten puffern (anstatt sie sofort zu senden) oder ĂŒber einen Synchronmechanismus (z.B. ein Semaphore oder ein Flag) verfĂŒgen, der es dem ĂŒbergeordneten Level ermöglicht, die Funktion zum Senden von Daten nur dann aufzurufen, wenn das Fenster nicht gefĂŒllt ist.
  • BestĂ€tigung erhalten. Im Protokoll wird fĂŒr das Paket mit der Sequenznummer N eine allgemeine Quittung ausgestellt, die anzeigt, dass alle Pakete mit Sequenznummern vor N erfolgreich empfangen wurden.
  • Ablauf des Warteintervalls. Um Verluste und Verzögerungen von Paketen und Quittungen zu bestimmen, verwendet das Protokoll einen Timer. Wenn das Warteintervall ablĂ€uft, sendet die sendende Seite alle gesendeten, unbestĂ€tigten Pakete erneut.

Selektives Wiederholen

Wenn die FenstergrĂ¶ĂŸe und das Produkt aus Bandbreite und Ausbreitungsverzögerung hoch sind, können sich viele Pakete in der Pipeline befinden. In einem solchen Fall könnte der Fehler eines einzelnen Pakets die erneute Übertragung vieler Pakete auslösen, von denen die meisten nicht benötigt wurden.

Beispiel

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

Server

package main

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

func main() {
    // Erstelle einen Socket mit dem Port 
    ln, err := net.Listen("tcp", ":8081")
    if err != nil {
        log.Fatalln(err)
    }
    // Aufruf abwarten
    conn, _ := ln.Accept()

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

den Kunden zurĂŒckzufĂŒhren sind, nicht verfĂŒgbar ist.

package main

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

func main() {
    \/\/ Verbindung einrichten
    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 zum Senden: ")
        \/\/ Zeilenweise
        text, err := reader.ReadString('n')
        if err != nil {
            log.Fatalln(err)
        }
        \/\/ Senden
        fmt.Fprintf(conn, text+"n")
        \/\/ Empfang
        msg, err := bufio.NewReader(conn).ReadString('n')
        if err != nil {
            log.Fatalln(err)
        }
        \/\/ Ausgabe der empfangenen Antwort
        fmt.Print("Nachricht vom Server: " + msg)
    }
}

Ausgabe

Mechanismen, die eine zuverlĂ€ssige DatenĂŒbertragung und deren Nutzung gewĂ€hrleisten

Mechanismus
Anwendung, Kommentar

PrĂŒfziffer
Wird verwendet, um Bitfehler im ĂŒbertragenen Paket zu erkennen

Timer
Abbruchzeitraum und Hinweis auf dessen Ablauf. Letzteres bedeutet, dass mit hoher Wahrscheinlichkeit das Paket oder dessen Quittung beim Versand verloren gegangen sind. Falls das Paket mit Verzögerung, jedoch nicht verloren (vorzeitiger Ablauf des Abbruchzeitraums) zugestellt wird oder die Quittung verloren geht, fĂŒhrt die WiederĂŒbertragung zu einer Duplizierung des Pakets auf der Empfangsseite.

Folgenummer
Wird zur sequentiellen Nummerierung von Datenpaketen verwendet, die vom Sender an den EmpfĂ€nger ĂŒbertragen werden. LĂŒcken in den Reihenfolgenummern der empfangenen Pakete ermöglichen es dem EmpfĂ€nger, den Verlust eines Pakets zu erkennen. Gleiche Folgenummern von Paketen bedeuten, dass die Pakete sich duplizieren.

BestÀtigung
Wird von der empfangenden Seite generiert 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 Folgenummern erfolgreich empfangener Pakete. Je nach Protokoll unterscheidet man zwischen Einzel- und GruppenbestÀtigungen.

Negative BestÀtigung
Wird vom EmpfÀnger verwendet, um dem Sender mitzuteilen, dass das Paket nicht korrekt empfangen wurde. Eine negative BestÀtigung umfasst normalerweise die Folgenummer des Pakets, das nicht korrekt empfangen wurde.

Fenster, Pipeline
Sie beschrĂ€nken den Bereich der verwendbaren Sequenznummern fĂŒr die Übertragung von Paketen. Die Gruppensendung und das Handshaking ermöglichen eine signifikante Erhöhung der Protokolldurchsatzrate im Vergleich zum BestĂ€tigungsmodus. Wie wir sehen werden, kann die GrĂ¶ĂŸe des Fensters basierend auf den Empfangs- und PufferfĂ€higkeiten der empfangenden Seite sowie dem Niveau der Netzwerkauslastung berechnet werden.

Weitere Beispiele fĂŒr die Verwendung von Go im Netzwerkbereich.

Im das Repository.

Quelle: habr.com

ZuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz kaufen, VPS VDS Server đŸ”„ ZuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz kaufen, VPS VDS Server - ProHoster