Grunderna för tillförlitlig dataöverföring

Grunderna för tillförlitlig dataöverföring

Till de som söker Dedikerad till att förstå nätverk och protokoll.

I korthet

Artikeln diskuterar grunderna för tillförlitlig dataöverföring, implementerar exempel på Go, inklusive UDP och TCP. Baserat på tid, два, tre och böckerna "Computer Networks. Top-Down Approach", annars diskuterar alla bara Tannenbaum och Oliferov.

Transportlagerprotokoll

Ger en logisk koppling mellan applikationsprocesser som körs på olika värdar. Ur ett applikationsperspektiv ser en logisk koppling ut som en kanal som direkt kopplar samman processer.

Grunderna för tillförlitlig dataöverföring

Transportlagerprotokoll stöds av slutsystem, men inte av nätverksroutrar (förutom - DPI). På avsändarsidan omvandlar transportskiktet applikationslagerdata som det tar emot från den sändande applikationsprocessen till transportlagerpaket som kallas segment.

Grunderna för tillförlitlig dataöverföring

Detta görs genom att dela upp (om nödvändigt) applikationslagermeddelandena i fragment och lägga till ett transportlagerhuvud till vart och ett av dem.

Grunderna för tillförlitlig dataöverföring

Transportlagret skickar sedan segmentet till avsändarens nätverkslager, där segmentet kapslas in i ett nätverkslagerpaket (datagram) och skickas. I den mottagande änden extraherar nätverkslagret transportlagersegmentet från datagrammet och skickar det upp till transportlagret. Därefter bearbetar transportskiktet det mottagna segmentet så att dess data blir tillgänglig för den mottagande applikationen.

Grunderna för tillförlitlig dataöverföring

Principer för tillförlitlig dataöverföring

Tillförlitlig dataöverföring över en helt säker kanal

Det enklaste fallet. Den sändande sidan tar helt enkelt emot data från det övre lagret, skapar ett paket som innehåller det och skickar det till kanalen.

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 + надежный канал
    }
}

kund

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)
    }
}

Tillförlitlig dataöverföring över en kanal med möjliga fel

Nästa steg är att anta att alla överförda paket tas emot i den ordning som de skickades, men bitarna i dem kan vara korrupta på grund av att kanalen ibland sänder data med distorsion.

Grunderna för tillförlitlig dataöverföring

I det här fallet används följande mekanismer:

  • feldetektering;
  • respons;
  • återsändning.

Pålitliga dataöverföringsprotokoll som har liknande mekanismer för att upprepa överföringen flera gånger kallas ARQ-protokoll (Automatic Repeat ReQuest).
Dessutom är det värt att överväga möjligheten av fel i kvitton, när den mottagande parten inte kommer att få någon information om resultatet av överföringen av det sista paketet.
Lösningen på detta problem, som också används i TCP, är att lägga till ett nytt fält till datapaketet som innehåller sekvensnumret för paketet.

Grunderna för tillförlitlig dataöverföring

Tillförlitlig dataöverföring över en opålitlig kanal utsatt för paketförvrängning och förlust

Tillsammans med distorsion finns det tyvärr paketförluster i nätverket.
Och för att lösa detta problem krävs mekanismer:

  • fastställande av paketförlust;
  • återleverans av förlorade paket till den mottagande parten.

Dessutom, utöver förlusten av paketet, är det nödvändigt att sörja för möjligheten av förlust av kvittot eller, om inget går förlorat, leveransen med en betydande försening. I alla fall görs samma sak: paketet återsänds. För att kontrollera tiden använder den här mekanismen en nedräkningstimer, som låter dig bestämma slutet av vänteintervallet. Alltså i paketet netto TCPKeepAlive är inställt på 15 sekunder som standard:

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

Den sändande sidan måste starta en timer varje gång ett paket sänds (både första och andra gången), hantera avbrott från timern och stoppa den.

Så vi har blivit bekanta med nyckelbegreppen för tillförlitliga dataöverföringsprotokoll:

  • kontrollsummor;
  • sekvensnummer för paket;
  • timers;
  • positiva och negativa kvitton.

Men det är inte allt!

Pålitligt dataöverföringsprotokoll med pipelining

I varianten som vi redan har övervägt är det tillförlitliga leveransprotokollet mycket ineffektivt. Den börjar "bromsa" överföringen som tillhandahålls av kommunikationskanalen när RTT ökar. För att öka dess effektivitet och bättre utnyttja kommunikationskanalkapaciteten används pipelining.

Grunderna för tillförlitlig dataöverföring

Användningen av pipelining leder till:

  • öka intervallet av sekvensnummer, eftersom alla skickade paket (förutom återsändningar) måste identifieras unikt;
  • behovet av att öka buffertarna på sändnings- och mottagningssidan.

Sekvensnummerintervallet och kraven på buffertstorlek beror på de åtgärder som protokollet vidtar som svar på paketkorruption, förlust och fördröjning. När det gäller pipelining finns det två metoder för att korrigera fel:

  • returnera N paket tillbaka;
  • selektiv upprepning.

Gå tillbaka N paket - glidande fönsterprotokoll

Grunderna för tillförlitlig dataöverföring

Avsändaren måste stödja tre typer av händelser:

  • samtal med ett protokoll på högre nivå. När datasändningsfunktionen kallas "uppifrån", kontrollerar sändningssidan först fyllnadsgraden av fönstret (det vill säga närvaron av N skickade meddelanden som väntar på mottagande av kvitton). Om fönstret är tomt genereras och sänds ett nytt paket och variabelvärdena uppdateras. Annars returnerar sändningssidan data till det övre lagret, och detta är en implicit indikation på att fönstret är fullt. Vanligtvis kommer det övre lagret att försöka överföra data igen efter en tid. I en riktig applikation skulle avsändaren sannolikt antingen buffra data (istället för att skicka den omedelbart) eller ha en synkroniseringsmekanism (som en semafor eller flagga) som skulle tillåta det övre lagret att anropa sändningsfunktionen endast när fönstret är tomt .
  • får bekräftelse. I protokollet, för ett paket med sekvensnummer N, utfärdas en allmän bekräftelse som indikerar att alla paket med sekvensnummer före N har tagits emot framgångsrikt.
  • vänteintervallet har löpt ut. För att fastställa fakta om förluster och förseningar av paket och kvitton, använder protokollet en timer. Om timeout-intervallet löper ut, skickar sändningssidan om alla skickade okvitterade paket.

Selektiv upprepning

När fönsterstorleken och genomströmnings-utbredningsfördröjningsprodukten är stora, kan ett stort antal paket vara i pipelinen. I ett sådant fall kan ett enda paketfel orsaka att ett stort antal paket återsänds, varav de flesta inte krävdes.

Exempel

Topp teoretisk praxis samlas in i praktiskt genomförande TCP. Och om någon vet hur bäst - välkommen.

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"))
    }
}

kund

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)
    }
}

Utgång

Mekanismer för att säkerställa tillförlitlig dataöverföring och användning

mekanism
Ansökan, kommentar

Kontrollera summan
Används för att upptäcka bitfel i ett överfört paket

timer
Räknar ner timeout-intervallet och indikerar när det har gått ut. Det senare innebär att med en hög grad av sannolikhet går paketet eller dess kvitto förlorat under överföringen. Om ett paket levereras med en fördröjning, men inte går förlorat (för tidigt utgång av timeoutintervallet), eller ett kvitto går förlorat, leder omsändning till ett duplicerat paket på den mottagande sidan

Serienummer
Används för sekventiell numrering av datapaket som överförs från avsändare till mottagare. Luckor i sekvensnumren för mottagna paket tillåter mottagaren att upptäcka paketförlust. Samma paketsekvensnummer betyder att paketen är dubbletter av varandra

Bekräftelse
Genereras av den mottagande änden och indikerar för den sändande änden att motsvarande paket eller grupp av paket har tagits emot framgångsrikt. Typiskt innehåller bekräftelsen sekvensnumren för framgångsrikt mottagna paket. Beroende på protokollet särskiljs individuella och gruppbekräftelser

Negativ bekräftelse
Används av mottagaren för att informera avsändaren om att paketet mottagits felaktigt. En negativ bekräftelse inkluderar vanligtvis sekvensnumret för paketet som inte togs emot korrekt

Fönster, transportör
Begränsa intervallet av sekvensnummer som kan användas för att överföra paket. Multicast och handskakning kan avsevärt öka protokollgenomströmningen jämfört med att vänta på bekräftelser. Som vi kommer att se kan fönsterstorleken beräknas baserat på mottagnings- och buffringskapaciteten hos den mottagande änden, såväl som nätverkets belastningsnivå

Fler exempel på användning av Go för nätverk

В förråd.

Källa: will.com

Lägg en kommentar