Grundlæggende om pålidelig dataoverførsel

Grundlæggende om pålidelig dataoverførsel

Til dem, der stræber ønsker at forstå netværk og protokoller.

Kort fortalt

Artiklen omhandler grundlaget for pålidelig dataoverførsel, med eksempler på Go, herunder UDP og TCP. Inspireret af en, to, tre og bogen "Computer Networking. A Top-Down Approach", da alle kun diskuterer Tanenbaum og Olifer.

Transportprotokol

giver en logisk forbindelse mellem applikationsprocesser, der kører på forskellige værter. En logisk forbindelse fra applikationernes synspunkt ser ud som en kanal, der direkte forbinder processerne.

Grundlæggende om pålidelig dataoverførsel

Transportprotokoller understøttes af slut-systemer, men ikke netværksroutere (undtagen — DPI). Ved afsenderens side omdanner transportlaget data fra applikationslaget, som er modtaget fra den sendende applikationsproces, til transportlagspakker, kaldet segmenter.

Grundlæggende om pålidelig dataoverførsel

Dette opnås ved at opdele (hvis nødvendigt) applikationslagets meddelelser i fragmenter og tilføje en transportlagsoverskrift til hver af dem.

Grundlæggende om pålidelig dataoverførsel

Herefter sender transportlaget segmentet til senderens netværkslag, hvor segmentet indkapsles i et netværkslagspakke (datagram) og sendes. På modtagerens side udtrækker netværkslaget transportlagsegmentet fra datagrammet og sender det op til transportlaget. Transportlaget behandler derefter det modtagne segment, så dets data bliver tilgængelige for det modtagende program.

Grundlæggende om pålidelig dataoverførsel

Principper for pålidelig dataoverførsel

Pålidelig dataoverførsel over en fuldstændig pålidelig kanal

Det enkleste tilfælde. Afsenderen modtager blot data fra det øverste niveau, opretter en pakke, der indeholder dem, og sender den gennem kanalen.

Server

package main

import (
    "log"
    "net"
)

func main() {
    // Serverens IP-adresse og port
    serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:12000")
    if err != nil {
        log.Fatal(err)
    }

    // Opretter en socket med port
    serverConn, err := net.ListenUDP("udp", serverAddr)
    if err != nil {
        log.Fatal(err)
    }
    // Udsat lukning af forbindelsen
    defer serverConn.Close()

    // Opretter en buffer til data
    buf := make([]byte, 1024)

    // Venter på forbindelse
    for {
        // Læser anmodningen
        n, addr, err := serverConn.ReadFromUDP(buf)
        // Sender data op til DET ØVERSTE niveau: i vores tilfælde stdout
        println(string(buf[0:n]), " fra ", addr.IP.String())
        if err != nil {
            log.Fatal(err)
        }
        // Ingen svar, da dette er UDP + pålidelig kanal
    }
}

Kunde

package main

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

func main() {
    // Serverens IP-adresse og port
    serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:12000")
    if err != nil {
        log.Fatal(err)
    }
    // Lokal IP-adresse og port
    localAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
    if err != nil {
        log.Fatal(err)
    }
    // Oprettelse af forbindelse
    conn, err := net.DialUDP("udp", localAddr, serverAddr)
    if err != nil {
        log.Fatal(err)
    }
    // Udsat lukning af forbindelse
    defer conn.Close()

    for {
        // Modtagelse af data fra ØVERSTE niveau
        fmt.Print("Indtast en sætning > ")
        var msg string
        _, err := fmt.Scanf("%s", &msg)
        if err != nil {
            log.Fatal(err)
        }
        // Der sendes en byte-strøm, ikke en streng
        buf := []byte(msg)
        // Skrive (transmission) til forbindelsen
        _, err = conn.Write(buf)
        if err != nil {
            log.Fatal(err)
        }
        // Vent et sekund
        time.Sleep(time.Second * 1)
    }
}

Pålidelig dataoverførsel over en kanal med mulige fejl

Det næste trin er at antage, at alle sendte pakker modtages i den rækkefølge, de blev sendt, men de enkelte bits kan være beskadiget, da kanalen nogle gange overfører data med forvrængninger.

Grundlæggende om pålidelig dataoverførsel

I sådanne tilfælde anvendes mekanismer:

  • fejlregistrering;
  • tilbagemelding;
  • genoverførsel.

Protokoller for pålidelig dataoverførsel, der har sådanne mekanismer for gentagen overførsel, kaldes automatiske gentagelsesprotokoller (Automatic Repeat reQuest, ARQ).
Derudover bør fejl i kvitteringer overvejes, når modtageren ikke modtager nogen oplysninger om resultaterne af overførslen af den seneste pakke.
Løsningen på dette problem, som også anvendes i TCP, består i at tilføje et nyt felt til datapakken, der indeholder pakkeordrenummeret.

Grundlæggende om pålidelig dataoverførsel

Pålidelig dataoverførsel over en upålidelig kanal, der tillader forvrængning og pakke tab

Desværre er der samtidig, med forvrængninger, tab af pakker i netværket.
Og til at løse denne opgave kræves mekanismer:

  • identifikation af pakke tab;
  • genlevering af de tabte pakker til modtageren.

Derudover, udover pakettab, skal man tage højde for muligheden for at miste bekræftelser eller, hvis intet er gået tabt, en betydelig forsinkelse i leveringen af dem. I alle tilfælde bliver det samme udført: pakken sendes igen. For at kontrollere tiden i denne mekanisme anvendes en nedtællingstimer, som muliggør bestemmelse af slutningen af ventetiden. Således i pakken net er TCPKeepAlive-indstillingen som standard sat til 15 sekunder:

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

Afsendersiden skal starte timeren hver gang en pakke sendes (både ved den første og ved genudsendelse), håndtere timerens afbrydelser og stoppe den.

Så vi har gjort os bekendt med de vigtigste begreber i protokoller for pålidelig datatransmission:

  • kontrolsummer;
  • sekvensnumre på pakker;
  • timere;
  • positive og negative bekræftelser.

Men det er ikke alt!

Protokol for pålidelig datatransmission med pipelining

I den version, vi allerede har set, er protokollen for pålidelig levering meget ineffektiv. Den begynder at "bremse" transmissionen, der sikres af kommunikationskanalen, når RTT øges. For at forbedre dens effektivitet og bedre udnyttelse af båndbredden på kommunikationskanalen anvendes pipelining.

Grundlæggende om pålidelig dataoverførsel

Anvendelse af pipelining fører til:

  • en stigning i intervallet af sekvensnumre, da alle sendte pakker (bortset fra genudsendelser) skal kunne identificeres entydigt;
  • behovet for at øge buffere på afsender- og modtagersiderne.

Intervallet af sekvensnumre og kravene til bufferstørrelser afhænger af de handlinger, som protokollen udfører som svar på forvrængning, tab og forsinkelse af pakker. I tilfælde af pipelining findes der to metoder til fejlkorrigering:

  • tilbagevenden N pakker tilbage;
  • udvalgt genudsendelse.

Tilbagevenden N pakker tilbage - skydende vinduesprotokol

Grundlæggende om pålidelig dataoverførsel

Afsenderen skal opretholde tre typer begivenheder:

  • opkald på et højere protokolniveau. Når funktionen til at sende data kaldes "ovenfra", kontrollerer afsenderen først, hvor fyldt vinduet er (dvs. om der er N sendte meddelelser, der venter på bekræftelser). Hvis vinduet ikke er fyldt, dannes en ny pakke og sendes, og variablers værdier opdateres. Ellers returnerer afsenderen data til det højere niveau, hvilket implicit indikerer, at vinduet er fyldt. Normalt vil det højere niveau forsøge at sende data igen efter noget tid. I en reel anvendelse vil afsenderen sandsynligvis enten have buffret dataene (i stedet for straks at sende dem) eller have en synkroniseringsmekanisme (f.eks. et semafor eller et flag), der tillader det højere niveau at kalde funktionen til at sende data kun, når vinduet ikke er fyldt.
  • modtagelse af bekræftelse. I protokollen gives der en samlet kvittering for en pakke med sekvensnummer N, som angiver, at alle pakker med sekvensnumre forud for N er blevet modtaget korrekt.
  • timeout. For at bestemme tab og forsinkelser af pakker og bekræftelser anvender protokollen en timer. Hvis timeouten udløber, sender afsenderen alle sendt ubekræftede pakker igen.

Selectiv gentagelse

Når vinduets størrelse og produktet af båndbredde og forsinkelse er store, kan der være mange pakker i pipeline. I så fald kan en fejl i en enkelt pakke medføre, at mange pakker sendes igen, hvoraf de fleste ikke var nødvendige.

Eksempel

De bedste teoretiske praksis er samlet i en praktisk implementering TCP. Og hvis nogen ved, hvordan det kan gøres bedre — welcome.

Server

package main

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

func main() {
    // opretter en socket med porten 
    ln, err := net.Listen("tcp", ":8081")
    if err != nil {
        log.Fatalln(err)
    }
    // venter på opkald
    conn, _ := ln.Accept()

    for {
        // læser data
        msg, err := bufio.NewReader(conn).ReadString('n')
        if err != nil {
            log.Fatalln(err)
        }
        // udskriver meddelelsen i stdout
        fmt.Print("Message Received:", string(msg))
        // konverterer strengen til store bogstaver
        newMsg := strings.ToUpper(msg)
        // sender data
        conn.Write([]byte(newMsg + "n"))
    }
}

Kunde

package main

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

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

    for {
        // læsning af data fra stdin
        reader := bufio.NewReader(os.Stdin)
        fmt.Print("Tekst at sende: ")
        // linje for linje
        text, err := reader.ReadString('n')
        if err != nil {
            log.Fatalln(err)
        }
        // sending
        fmt.Fprintf(conn, text+"n")
        // modtagelse
        msg, err := bufio.NewReader(conn).ReadString('n')
        if err != nil {
            log.Fatalln(err)
        }
        // udskrivning af modtaget svar
        fmt.Print("Msg fra Server: " + msg)
    }
}

Output

Mekanismer, der sikrer pålidelig overførsel af data og deres anvendelse

Mekanisme
Anvendelse, kommentar

Kontrolsum
Bruges til at opdage bitfejl i det overførte pakke

Tæller
Nedtælling af ventetid og indikation af dens udløb. Dette betyder, at pakke eller dens kvittering med stor sandsynlighed er gået tabt under overførslen. Hvis pakken leveres med forsinkelse, men ikke går tabt (for tidlig udløb af ventetid), eller der opstår tab af kvitteringen, fører retransmissionen til dublering af pakken på modtagerens side

Kædenummer
Bruges til sekventiel nummerering af datapakker, der sendes fra afsender til modtager. Brud i kædenumre af de modtagne pakker gør det muligt for modtageren at opdage pakke tab. Ens kædenumre på pakker betyder, at pakkerne duplikerer hinanden

Bekræftelse
Genereres af modtageren og angiver til afsenderen, at den tilsvarende pakke eller gruppe af pakker er modtaget succesfuldt. Bekræftelsen indeholder normalt kædenumre for de succesfuldt modtagne pakker. Afhængigt af protokollen skelnes der mellem individuelle og gruppebekræftelser

Negativ bekræftelse
Bruges af modtageren til at informere afsenderen om, at pakken er modtaget ukorrekt. Den negative bekræftelse inkluderer normalt kædenummeret på den pakke, der ikke blev modtaget korrekt

Vindue, pipeline
Begrænser rækken af sekvensnumre, der kan bruges til at transmittere pakker. Gruppetrafik og håndtryk kan væsentligt øge kapaciteten for protokoller sammenlignet med mode med afventning på bekræftelse. Som vi vil se, kan vinduesstørrelsen beregnes baseret på modtagerens modtagelses- og bufferingskapabiliteter samt netværksbelastningsniveauet.

Andre eksempler på brug af Go til netværksarbejde.

I repository.

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster