Basisprincipes van betrouwbare gegevensoverdracht

Basisprincipes van betrouwbare gegevensoverdracht

Aan degenen die probeert zich willen verdiepen in netwerken en protocollen, opgedragen.

Kort

In dit artikel worden de basisprincipes van betrouwbare gegevensoverdracht besproken, met voorbeelden op Go, waaronder UDP en TCP. GeĆÆnspireerd door one, two, drie en het boek "Computer Networks. A descending approach", aangezien iedereen alleen maar over Tanenbaum en Olifer spreekt.

Transportlaagprotocol

verzorgt een logische verbinding tussen applicatieprocessen die op verschillende hosts draaien. Een logische verbinding ziet er vanuit applicaties uit als een kanaal dat de processen rechtstreeks verbindt.

Basisprincipes van betrouwbare gegevensoverdracht

Transportlaagprotocollen worden ondersteund door eindsystemen, maar niet door netwerkroutetters (behalve — DPI). Aan de verzendzijde zet de transportlaag de gegevens van de applicatielaag, die het ontvangt van het verzendende applicatieproces, om in transportlaagpakketten, die segmenten worden genoemd.

Basisprincipes van betrouwbare gegevensoverdracht

Dit gebeurt door (indien nodig) berichten van de applicatielaag in fragmenten op te breken en elk fragment van een transportlaagheader te voorzien.

Basisprincipes van betrouwbare gegevensoverdracht

Vervolgens verzendt de transportlaag het segment naar de netlaag van de verzender, waar het segment wordt ingekapseld in een netwerklaagpakket (datagram) en verzonden. Aan de ontvangende kant haalt de netlaag het transportlaagsegment uit het datagram en geeft het omhoog door aan de transportlaag. Vervolgens behandelt de transportlaag het ontvangen segment zodanig dat de gegevens beschikbaar komen voor de ontvangende applicatie.

Basisprincipes van betrouwbare gegevensoverdracht

Principes van betrouwbare gegevensoverdracht

Betrouwbare gegevensoverdracht via een volkomen betrouwbaar kanaal

De eenvoudigste zaak. De verzendende partij ontvangt eenvoudig gegevens van de bovenliggende laag, maakt een pakket aan dat deze bevat en verzendt het naar het kanaal.

Server

package main

import (
    "log"
    "net"
)

func main() {
    // IP-adres van de server en poort
    serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:12000")
    if err != nil {
        log.Fatal(err)
    }

    // maak een socket met de poort
    serverConn, err := net.ListenUDP("udp", serverAddr)
    if err != nil {
        log.Fatal(err)
    }
    // uitgestelde sluiting van de verbinding
    defer serverConn.Close()

    // maak een buffer voor gegevens
    buf := make([]byte, 1024)

    // wacht op verbinding
    for {
        // lees het verzoek
        n, addr, err := serverConn.ReadFromUDP(buf)
        // stuur gegevens naar de BOVENLIGGENDE laag: in ons geval stdout
        println(string(buf[0:n]), " van ", addr.IP.String())
        if err != nil {
            log.Fatal(err)
        }
        // er is geen antwoord, omdat dit UDP + betrouwbaar kanaal is
    }
}

Klant

package main

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

func main() {
    // IP-adres van de server en poort
    serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:12000")
    if err != nil {
        log.Fatal(err)
    }
    // Lokaal IP-adres en poort
    localAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
    if err != nil {
        log.Fatal(err)
    }
    // Verbinding opzetten
    conn, err := net.DialUDP("udp", localAddr, serverAddr)
    if err != nil {
        log.Fatal(err)
    }
    // Vertraging bij het sluiten van de verbinding
    defer conn.Close()

    for {
        // Ontvang gegevens van de BOVENSTE laag
        fmt.Print("Voer een zin in > ")
        var msg string
        _, err := fmt.Scanf("%s", &msg)
        if err != nil {
            log.Fatal(err)
        }
        // Er wordt een byte-stream verzonden, geen string
        buf := []byte(msg)
        // Schrijven (verzenden) naar de verbinding
        _, err = conn.Write(buf)
        if err != nil {
            log.Fatal(err)
        }
        // 1 seconde wachten
        time.Sleep(time.Second * 1)
    }
}

Betrouwbare gegevensoverdracht over een kanaal met mogelijke fouten

De volgende stap is de veronderstelling dat alle verzonden pakketten zijn ontvangen in de volgorde waarin ze zijn verzonden, maar dat de bits daarin beschadigd kunnen zijn, omdat het kanaal soms gegevens met vervormingen verzendt.

Basisprincipes van betrouwbare gegevensoverdracht

In dat geval worden de mechanismen toegepast:

  • foutdetectie;
  • feedback;
  • herverzending.

Protocollen voor betrouwbare gegevensoverdracht die dergelijke mechanismen voor herhaalde verzending hebben, worden automatische herverzoekprotocollen (Automatic Repeat reQuest, ARQ) genoemd.
Daarnaast is het belangrijk om rekening te houden met de mogelijkheid van fouten in de bevestigingen, wanneer de ontvangende partij geen informatie ontvangt over de resultaten van de overdracht van het laatste pakket.
De oplossing voor dit probleem, die ook in TCP wordt gebruikt, bestaat uit het toevoegen van een nieuw veld aan het datapakket dat het volgnummer van het pakket bevat.

Basisprincipes van betrouwbare gegevensoverdracht

Betrouwbare gegevensoverdracht over een onbetrouwbaar kanaal dat vervorming en pakketverlies toelaat

Tegelijkertijd is er helaas ook pakketverlies in het netwerk.
En om dit probleem op te lossen zijn mechanismen vereist:

  • vaststellen van het feit van pakketverlies;
  • herlevering van verloren pakketten aan de ontvangende partij.

Bovendien, naast pakkettenverlies, moet er rekening worden gehouden met de mogelijkheid van het verlies van bevestigingen of, als er niets verloren is, de levering ervan met aanzienlijke vertraging. In alle gevallen gebeurt hetzelfde: het opnieuw verzenden van het pakket. Voor het controleren van de tijd in dit mechanisme wordt een timer gebruikt die het einde van de wachttijd kan bepalen. Zo in het pakket net is de TCPKeepAlive parameter standaard ingesteld op 15 seconden:

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

De verzendende partij moet de timer elke keer starten bij het verzenden van een pakket (zowel bij de eerste als bij een herhaalde verzending), onderbrekingen van de timer verwerken en deze stoppen.

Dus, we hebben kennis gemaakt met de belangrijkste concepten van betrouwbare gegevensoverdracht protocollen:

  • checksums;
  • pakketvolgnummer;
  • timers;
  • positieve en negatieve bevestigingen.

Maar dat is nog niet alles!

Het protocol voor betrouwbare gegevensoverdracht met pipelining

In de versie die we al hebben besproken, is het protocol voor betrouwbare levering zeer inefficiƫnt. Het begint de overdracht, die door het communic kanaal wordt geboden, te "vertragen" bij een toenemende RTT. Om de efficiƫntie te verhogen en het gebruik van de bandbreedte van het communic kanaal te verbeteren, wordt pipelining toegepast.

Basisprincipes van betrouwbare gegevensoverdracht

Het gebruik van pipelining leidt tot:

  • een uitbreiding van de reeks volgnummer, omdat alle verzonden pakketten (met uitzondering van herhaalde verzendingen) eenduidig identificeerbaar moeten zijn;
  • de noodzaak voor het vergroten van de buffers aan de verzendende en ontvangende zijde.

De reeks volgnummer en de eisen aan bufferformaten zijn afhankelijk van de acties die het protocol onderneemt in reactie op vervormingen, verlies en vertraging van pakketten. In het geval van pipelining zijn er twee methoden voor foutcorrectie:

  • terugkeer naar N pakketten terug;
  • selectieve herhaling.

Terugkeer naar N pakketten terug — het glijdende vensterprotocol

Basisprincipes van betrouwbare gegevensoverdracht

De verzender moet drie soorten evenementen onderhouden:

  • oproep via een hoger niveau protocol. Wanneer de functie voor het verzenden van gegevens van 'boven' wordt aangeroepen, controleert de verzender eerst de mate van het vullen van het venster (dat wil zeggen, het aantal verzonden berichten dat wacht op ontvangstbevestigingen). Als het venster niet vol is, wordt een nieuw pakket gevormd en verzonden, en worden de waarden van de variabelen bijgewerkt. In andere gevallen geeft de verzender de gegevens terug aan het hogere niveau, wat een impliciete aanwijzing is dat het venster vol is. Gewoonlijk probeert het hogere niveau op een later tijdstip opnieuw de gegevens te verzenden. In een echte applicatie zou de verzender waarschijnlijk de gegevens bufferen (in plaats van deze onmiddellijk te verzenden) of een synchronisatiemechanisme (zoals een semafoor of vlag) hebben dat het hogere niveau toestaat de functie voor het verzenden van gegevens alleen aan te roepen als het venster niet vol is.
  • ontvangstbevestiging. In het protocol voor het pakket met volgnummer N wordt een algemene ontvangstbevestiging verstrekt, die aangeeft dat alle pakketten met volgnummers voorafgaand aan N succesvol zijn ontvangen.
  • verval van de wachttijd. Om verlies- en vertragingseffecten van pakketten en ontvangstbevestigingen vast te stellen, gebruikt het protocol een timer. Als de wachttijd verstrijkt, verzendt de verzender alle verzonden, onbevestigde pakketten opnieuw.

Selectieve herhaling

Wanneer de venstergrootte en het product van bandbreedte en propagatietijd groot zijn, kunnen er veel pakketten in de pijplijn zijn. In dat geval kan de fout van een enkel pakket leiden tot de herverzending van een groot aantal pakketten, waarvan de meeste niet nodig waren.

Voorbeeld

De beste theoretische praktijken zijn verzameld in een praktische implementatie TCP. En als iemand weet hoe het beter kan — welkom.

Server

package main

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

func main() {
    // Maak een socket aan met poort 
    ln, err := net.Listen("tcp", ":8081")
    if err != nil {
        log.Fatalln(err)
    }
    // Wacht op een oproep
    conn, _ := ln.Accept()

    for {
        // Gegevens lezen
        msg, err := bufio.NewReader(conn).ReadString('n')
        if err != nil {
            log.Fatalln(err)
        }
        // Bericht afdrukken op stdout
        fmt.Print("Bericht Ontvangen:", string(msg))
        // Vertaal de regel naar hoofdletters
        newMsg := strings.ToUpper(msg)
        // Gegevens verzenden
        conn.Write([]byte(newMsg + "n"))
    }
}

Klant

pakket hoofd

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

func hoofd() {
    // verbinding instellen
    conn, err := net.Dial("tcp", "127.0.0.1:8081")
    als err != nil {
        log.Fatalln(err)
    }

    voor {
        // gegevens lezen van stdin
        lezer := bufio.NewReader(os.Stdin)
        fmt.Print("Tekst om te verzenden: ")
        // regel per regel
        tekst, err := lezer.ReadString('n')
        als err != nil {
            log.Fatalln(err)
        }
        // verzending
        fmt.Fprintf(conn, tekst+"n")
        // ontvangst
        msg, err := bufio.NewReader(conn).ReadString('n')
        als err != nil {
            log.Fatalln(err)
        }
        // weergegeven antwoord
        fmt.Print("Bericht van Server: " + msg)
    }
}

Uitslag

Mechanismen die zorgen voor betrouwbare gegevensoverdracht en gebruik daarvan

Mechanisme
Toepassing, opmerking

Controlegetal
Wordt gebruikt voor het opsporen van bits fouten in het verzonden pakket

Timer
Tijdslimiet tellen en aangeven dat deze is verstreken. Laatstgenoemde betekent dat het zeer waarschijnlijk is dat het pakket of de ontvangstbevestiging verloren is gegaan tijdens de overdracht. In het geval dat het pakket met vertraging wordt afgeleverd, maar niet verloren gaat (voorbarige tijdslimiet), of dat er een verlies van de ontvangstbevestiging plaatsvindt, dan leidt een herverzending tot duplicatie van het pakket aan de ontvangende zijde.

Volgnummer
Wordt gebruikt voor de volgorde nummering van gegevenspakketten die van de zender naar de ontvanger worden verzonden. Hiaten in de volgordes van de ontvangen pakketten stellen de ontvanger in staat om het verlies van een pakket op te sporen. Identieke volgordes van pakketten betekenen dat de pakketten elkaar dupliceren.

Bevestiging
Wordt gegenereerd door de ontvangende kant en geeft de verzendende kant aan dat het bijbehorende pakket of de groep pakketten succesvol is ontvangen. Bevestiging bevat meestal de volgnummer van succesvol ontvangen pakketten. Afhankelijk van het protocol onderscheiden we individuele en groepsbevestigingen.

Negatieve bevestiging
Wordt door de ontvanger gebruikt om de zender te informeren dat het pakket onjuist is ontvangen. Negatieve bevestiging bevat meestal het volgnummer van het pakket dat niet correct is ontvangen.

Venster, pipelining
Beperken het bereik van volgordennummers die kunnen worden gebruikt voor pakketoverdracht. Groepstransmissie en handdruk maken het mogelijk de doorvoersnelheid van protocollen aanzienlijk te verhogen in vergelijking met de bevestigingsmodus. Zoals we zullen zien, kan de venstergrootte worden berekend op basis van de ontvangstcapaciteiten en buffering van de ontvangende partij, evenals het netwerkniveau van belasting.

Andere voorbeelden van het gebruik van Go voor netwerktoepassingen.

In de repository.

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers šŸ”„ Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster