Grundlagen der zuverlässigen Datenübertragung

Grundlagen der zuverlässigen Datenübertragung

Zu denen, die sucht Dem Verständnis von Netzwerken und Protokollen gewidmet.

Kurz gesagt

Der Artikel diskutiert die Grundlagen einer zuverlässigen Datenübertragung und implementiert Beispiele dazu Go, einschließlich UDP und TCP. Bezogen auf Zeit, два, drei und die Bücher „Computernetzwerke. Top-Down-Ansatz“, ansonsten diskutieren alle nur über Tannenbaum und Oliferov.

Transportschichtprotokoll

Stellt eine logische Verbindung zwischen Anwendungsprozessen bereit, die auf verschiedenen Hosts ausgeführt werden. Aus Anwendungssicht sieht eine logische Verbindung wie ein Kanal aus, der Prozesse direkt verbindet.

Grundlagen der zuverlässigen Datenübertragung

Protokolle der Transportschicht werden von Endsystemen unterstützt, nicht jedoch von Netzwerkroutern (außer - DPI). Auf der Senderseite wandelt die Transportschicht die Anwendungsschichtdaten, die sie vom sendenden Anwendungsprozess empfängt, in Transportschichtpakete um, die als Segmente bezeichnet werden.

Grundlagen der zuverlässigen Datenübertragung

Dazu werden (falls erforderlich) die Nachrichten der Anwendungsschicht in Fragmente aufgeteilt und jedem von ihnen ein Transportschicht-Header hinzugefügt.

Grundlagen der zuverlässigen Datenübertragung

Die Transportschicht übergibt das Segment dann an die Netzwerkschicht des Absenders, wo das Segment in ein Netzwerkschichtpaket (Datagramm) eingekapselt und gesendet wird. Auf der Empfangsseite extrahiert die Netzwerkschicht das Transportschichtsegment aus dem Datagramm und leitet es an die Transportschicht weiter. Als nächstes verarbeitet die Transportschicht das empfangene Segment, sodass seine Daten für die empfangende Anwendung verfügbar werden.

Grundlagen der zuverlässigen Datenübertragung

Grundsätze einer zuverlässigen Datenübertragung

Zuverlässige Datenübertragung über einen absolut sicheren Kanal

Der einfachste Fall. Die sendende Seite empfängt einfach die Daten von der oberen Schicht, erstellt ein Paket mit diesen Daten und sendet es an den Kanal.

Server

package main

import (
    "log"
    "net"
)

func main() {
    // IP-адрес сервера и порт
    serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:12000")
    if err != nil {
        log.Fatal(err)
    }

    // создаем сокет с портом
    serverConn, err := net.ListenUDP("udp", serverAddr)
    if err != nil {
        log.Fatal(err)
    }
    // отложенное закрытие соединения
    defer serverConn.Close()

    // создаем буфер для данных
    buf := make([]byte, 1024)

    // ждем соединение
    for {
        // читаем запрос
        n, addr, err := serverConn.ReadFromUDP(buf)
        // передаем данные в ВЕРХНИЙ уровень: в нашем случае stdout
        println(string(buf[0:n]), " form ", addr.IP.String())
        if err != nil {
            log.Fatal(err)
        }
        // ответа нет, т.к. это UDP + надежный канал
    }
}

Auftraggeber

package main

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

func main() {
    // IP-адрес сервера и порт
    serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:12000")
    if err != nil {
        log.Fatal(err)
    }
    // локальный IP-адрес и порт
    localAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
    if err != nil {
        log.Fatal(err)
    }
    // установка соединения
    conn, err := net.DialUDP("udp", localAddr, serverAddr)
    if err != nil {
        log.Fatal(err)
    }
    // отложенное закрытие соединения
    defer conn.Close()

    for {
        // получение данных от ВЕРХНЕГО уровня
        fmt.Print("Введите строчное предложение > ")
        var msg string
        _, err := fmt.Scanf("%s", &msg)
        if err != nil {
            log.Fatal(err)
        }
        // передается поток байт, а не строка
        buf := []byte(msg)
        // запись (передача) в соединение
        _, err = conn.Write(buf)
        if err != nil {
            log.Fatal(err)
        }
        // 1 секундочку
        time.Sleep(time.Second * 1)
    }
}

Zuverlässige Datenübertragung über einen Kanal mit möglichen Fehlern

Der nächste Schritt besteht darin, davon auszugehen, dass alle übertragenen Pakete in der Reihenfolge empfangen werden, in der sie gesendet wurden, die darin enthaltenen Bits jedoch möglicherweise beschädigt sind, da der Kanal manchmal Daten mit Verzerrungen überträgt.

Grundlagen der zuverlässigen Datenübertragung

Dabei kommen folgende Mechanismen zum Einsatz:

  • Fehlererkennung;
  • Rückmeldung;
  • Neuübertragung.

Zuverlässige Datenübertragungsprotokolle, die über ähnliche Mechanismen zur mehrmaligen Wiederholung der Übertragung verfügen, werden als ARQ-Protokolle (Automatic Repeat ReQuest) bezeichnet.
Darüber hinaus lohnt es sich, die Möglichkeit von Empfangsfehlern in Betracht zu ziehen, wenn der Empfänger keine Informationen über die Ergebnisse 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 Sequenznummer des Pakets enthält.

Grundlagen der zuverlässigen Datenübertragung

Zuverlässige Datenübertragung über einen unzuverlässigen Kanal, der Paketverzerrungen und -verlusten unterliegt

Neben Verzerrungen kommt es leider auch zu Paketverlusten im Netzwerk.
Und um dieses Problem zu lösen, sind Mechanismen erforderlich:

  • Bestimmen der Tatsache des Paketverlusts;
  • erneute Zustellung verlorener Pakete an die empfangende Partei.

Darüber hinaus muss neben dem Verlust des Pakets auch mit der Möglichkeit des Verlusts der Quittung bzw., wenn nichts verloren geht, mit einer erheblichen Verzögerung der Zustellung gerechnet werden. In allen Fällen geschieht das Gleiche: Das Paket wird erneut übertragen. Zur Zeitsteuerung verwendet dieser Mechanismus einen Countdown-Timer, mit dem Sie das Ende des Warteintervalls bestimmen können. Also im Paket Netto- TCPKeepAlive 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
)

Die sendende Seite muss bei jeder Übertragung eines Pakets (sowohl beim ersten als auch beim zweiten Mal) einen Timer starten, Unterbrechungen durch den Timer verarbeiten und ihn stoppen.

Wir haben uns also mit den Schlüsselkonzepten zuverlässiger Datenübertragungsprotokolle vertraut gemacht:

  • Prüfsummen;
  • Sequenznummern von Paketen;
  • Zeitgeber;
  • positive und negative Einnahmen.

Aber das ist nicht alles!

Zuverlässiges Datenübertragungsprotokoll mit Pipelining

In der von uns bereits betrachteten Variante ist das zuverlässige Zustellungsprotokoll sehr ineffizient. Es beginnt, die vom Kommunikationskanal bereitgestellte Übertragung zu „verlangsamen“, wenn die RTT zunimmt. Um die Effizienz zu steigern und die Kapazität des Kommunikationskanals besser zu nutzen, wird Pipelining eingesetzt.

Grundlagen der zuverlässigen Datenübertragung

Der Einsatz von Pipelining führt zu:

  • Erweiterung des Bereichs der Sequenznummern, da alle gesendeten Pakete (außer Neuübertragungen) eindeutig identifiziert werden müssen;
  • die Notwendigkeit, die Puffer auf der Sende- und Empfangsseite zu erhöhen.

Der Sequenznummernbereich und die Anforderungen an die Puffergröße hängen von den Aktionen ab, die das Protokoll als Reaktion auf Paketbeschädigung, -verlust und -verzögerung ergreift. Beim Pipelining gibt es zwei Methoden zur Fehlerkorrektur:

  • N Pakete zurückgeben;
  • selektive Wiederholung.

N Pakete zurückgehen – Sliding-Window-Protokoll

Grundlagen der zuverlässigen Datenübertragung

Der Absender muss drei Arten von Ereignissen unterstützen:

  • Aufruf durch ein übergeordnetes Protokoll. Wenn die Datensendefunktion „von oben“ aufgerufen wird, prüft die sendende Seite zunächst den Füllgrad des Fensters (d. h. das Vorhandensein von N gesendeten Nachrichten, die auf den Empfang von Empfangsbestätigungen warten). Wenn das Fenster leer ist, wird ein neues Paket generiert und übertragen und die Variablenwerte werden aktualisiert. Andernfalls gibt die sendende Seite Daten an die obere Schicht zurück, und dies ist ein impliziter Hinweis darauf, dass das Fenster voll ist. Normalerweise versucht die obere Schicht nach einiger Zeit erneut, die Daten zu übertragen. In einer realen Anwendung würde der Absender die Daten wahrscheinlich entweder zwischenspeichern (anstatt sie sofort zu senden) oder über einen Synchronisierungsmechanismus (z. B. ein Semaphor oder Flag) verfügen, der es der oberen Schicht ermöglichen würde, die Sendefunktion nur dann aufzurufen, wenn das Fenster leer ist .
  • Bestätigung erhalten. Im Protokoll wird für ein Paket mit der Sequenznummer N eine allgemeine Bestätigung ausgegeben, die angibt, dass alle Pakete mit der Sequenznummer vor N erfolgreich empfangen wurden.
  • das Warteintervall ist abgelaufen. Um den Sachverhalt von Verlusten und Verzögerungen von Paketen und Empfangsbestätigungen zu ermitteln, verwendet das Protokoll einen Timer. Wenn das Timeout-Intervall abläuft, sendet die sendende Seite alle gesendeten unbestätigten Pakete erneut.

Selektive Wiederholung

Wenn die Fenstergröße und das Durchsatz-Ausbreitungsverzögerungsprodukt groß sind, befindet sich möglicherweise eine große Anzahl von Paketen in der Pipeline. In einem solchen Fall kann ein einzelner Paketfehler dazu führen, dass eine große Anzahl von Paketen erneut übertragen wird, von denen die meisten nicht erforderlich waren.

Beispiel

Top theoretisch Praktiken werden in der praktischen Umsetzung gesammelt TCP. Und wenn jemand weiß, wie es am besten geht – willkommen.

Server

package main

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

func main() {
    // создаем сокет с портом 
    ln, err := net.Listen("tcp", ":8081")
    if err != nil {
        log.Fatalln(err)
    }
    // ожидание вызова
    conn, _ := ln.Accept()

    for {
        // считывание данных
        msg, err := bufio.NewReader(conn).ReadString('n')
        if err != nil {
            log.Fatalln(err)
        }
        // вывод сообщения в stdout
        fmt.Print("Message Received:", string(msg))
        // перевод строки в верхний регистр
        newMsg := strings.ToUpper(msg)
        // отправка данных
        conn.Write([]byte(newMsg + "n"))
    }
}

Auftraggeber

package main

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

func main() {
    // установка соединения
    conn, err := net.Dial("tcp", "127.0.0.1:8081")
    if err != nil {
        log.Fatalln(err)
    }

    for {
        // считывание данных с stdin
        reader := bufio.NewReader(os.Stdin)
        fmt.Print("Text to send: ")
        // построчно
        text, err := reader.ReadString('n')
        if err != nil {
            log.Fatalln(err)
        }
        // отправка
        fmt.Fprintf(conn, text+"n")
        // прием
        msg, err := bufio.NewReader(conn).ReadString('n')
        if err != nil {
            log.Fatalln(err)
        }
        // вывод полученного ответа
        fmt.Print("Msg from Server: " + msg)
    }
}

Abschluss

Mechanismen zur Gewährleistung einer zuverlässigen Datenübertragung und -nutzung

Mechanismus
Bewerbung, Kommentar

Prüfsumme
Wird verwendet, um Bitfehler in einem übertragenen Paket zu erkennen

Timer
Zählt das Timeout-Intervall herunter und zeigt an, wann es abgelaufen ist. Letzteres bedeutet, dass das Paket bzw. dessen Empfang mit hoher Wahrscheinlichkeit bei der Übertragung verloren geht. Wenn ein Paket verzögert zugestellt wird, aber nicht verloren geht (vorzeitiger Ablauf des Timeout-Intervalls) oder eine Quittung verloren geht, führt die erneute Übertragung zu einem doppelten Paket auf der Empfängerseite

Ordnungsnummer
Wird zur fortlaufenden Nummerierung der vom Absender zum Empfänger übertragenen Datenpakete verwendet. Lücken in der Sequenznummer empfangener Pakete ermöglichen es dem Empfänger, Paketverluste zu erkennen. Gleiche Paketsequenznummern bedeuten, dass es sich bei den Paketen um Duplikate voneinander handelt

Bestätigung
Wird vom empfangenden Ende generiert und zeigt dem sendenden Ende an, dass das entsprechende Paket oder die entsprechende Paketgruppe erfolgreich empfangen wurde. Normalerweise enthält die Bestätigung die Sequenznummern erfolgreich empfangener Pakete. Je nach Protokoll werden Einzel- und Gruppenbestätigungen unterschieden

Negative Bestätigung
Wird vom Empfänger verwendet, um den Absender darüber zu informieren, dass das Paket falsch empfangen wurde. Eine negative Bestätigung enthält normalerweise die Sequenznummer des Pakets, das nicht korrekt empfangen wurde

Fenster, Fördertechnik
Beschränken Sie den Bereich der Sequenznummern, die zum Übertragen von Paketen verwendet werden können. Multicast und Handshake können den Protokolldurchsatz im Vergleich zum Warten auf Bestätigungen erheblich steigern. Wie wir sehen werden, kann die Fenstergröße basierend auf den Empfangs- und Pufferfähigkeiten des Empfängers sowie der Netzwerklaststufe berechnet werden

Weitere Beispiele für die Verwendung von Go zum Networking

В Lagerstätten.

Source: habr.com

Kommentar hinzufügen