
An diejenigen, die , 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 gegeben, darunter UDP und TCP. Inspiriert von , , 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.

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

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

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.

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.

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.

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 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.

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

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 Praktiken sind in einer praktischen Umsetzung gesammelt . Und wenn jemand weiĂ, wie es besser geht â .
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 .
Quelle: habr.com
