Fondamenti di un trasferimento dati affidabile

Fondamenti di un trasferimento dati affidabile

A coloro che стремится Dedicato alla comprensione di reti e protocolli.

brevemente

L'articolo discute le basi di una trasmissione dati affidabile, implementa esempi su Go, inclusi UDP e TCP. Basato su tempo, два, tre e i libri "Reti di computer. Approccio top-down", altrimenti tutti discutono solo di Tannenbaum e Oliferov.

Protocollo del livello di trasporto

Fornisce una connessione logica tra i processi applicativi in ​​esecuzione su host diversi. Dal punto di vista dell'applicazione, una connessione logica assomiglia a un canale che collega direttamente i processi.

Fondamenti di un trasferimento dati affidabile

Protocolli del livello di trasporto sono supportati dai sistemi finali, ma non dai router di rete (eccetto - DPI). Dal lato del mittente, il livello di trasporto converte i dati del livello di applicazione che riceve dal processo dell'applicazione di invio in pacchetti del livello di trasporto chiamati segmenti.

Fondamenti di un trasferimento dati affidabile

Questo viene fatto suddividendo (se necessario) i messaggi del livello di applicazione in frammenti e aggiungendo un'intestazione del livello di trasporto a ciascuno di essi.

Fondamenti di un trasferimento dati affidabile

Il livello di trasporto passa quindi il segmento al livello di rete del mittente, dove il segmento viene incapsulato in un pacchetto del livello di rete (datagramma) e inviato. All'estremità ricevente, lo strato di rete estrae il segmento dello strato di trasporto dal datagramma e lo passa allo strato di trasporto. Successivamente, il livello di trasporto elabora il segmento ricevuto in modo che i suoi dati diventino disponibili per l'applicazione ricevente.

Fondamenti di un trasferimento dati affidabile

Principi di trasmissione affidabile dei dati

Trasmissione dati affidabile su un canale completamente sicuro

Il caso più semplice. Il lato mittente riceve semplicemente i dati dallo strato superiore, crea un pacchetto che li contiene e li invia al canale.

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

Cliente

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

Trasmissione dati affidabile su un canale con possibili errori

Il passo successivo è presumere che tutti i pacchetti trasmessi vengano ricevuti nell'ordine in cui sono stati inviati, ma i bit in essi contenuti potrebbero essere danneggiati a causa del fatto che il canale a volte trasmette i dati con distorsioni.

Fondamenti di un trasferimento dati affidabile

In questo caso vengono utilizzati i seguenti meccanismi:

  • rilevamento degli errori;
  • feedback;
  • ritrasmissione.

I protocolli di trasferimento dati affidabili che dispongono di meccanismi simili per ripetere la trasmissione più volte sono chiamati protocolli ARQ (Automatic Repeat ReQuest).
Inoltre, vale la pena considerare la possibilità di errori nelle ricevute, quando la parte ricevente non riceverà alcuna informazione sui risultati del trasferimento dell'ultimo pacchetto.
La soluzione a questo problema, utilizzata anche in TCP, è aggiungere al pacchetto dati un nuovo campo contenente il numero di sequenza del pacchetto.

Fondamenti di un trasferimento dati affidabile

Trasmissione dati affidabile su un canale inaffidabile soggetto a distorsione e perdita di pacchetti

Oltre alla distorsione, purtroppo, si verifica anche la perdita di pacchetti nella rete.
E per risolvere questo problema, sono necessari meccanismi:

  • determinare il fatto della perdita di pacchetti;
  • riconsegna dei pacchetti persi alla parte ricevente.

Inoltre, oltre allo smarrimento del pacco, è necessario prevedere la possibilità di smarrimento della ricevuta o, se non viene smarrito nulla, della sua consegna con notevole ritardo. In tutti i casi si fa la stessa cosa: il pacchetto viene ritrasmesso. Per controllare il tempo, questo meccanismo utilizza un timer per il conto alla rovescia, che consente di determinare la fine dell'intervallo di attesa. Quindi nel pacchetto rete TCPKeepAlive è impostato su 15 secondi per impostazione predefinita:

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

Il lato mittente deve avviare un timer ogni volta che un pacchetto viene trasmesso (sia la prima che la seconda volta), gestire le interruzioni del timer e fermarlo.

Quindi, abbiamo acquisito familiarità con i concetti chiave dei protocolli di trasferimento dati affidabili:

  • somme di controllo;
  • numeri progressivi dei colli;
  • temporizzatori;
  • incassi positivi e negativi.

Ma non è tutto!

Protocollo di trasferimento dati affidabile con pipeline

Nella variante che abbiamo già considerato, il protocollo di consegna affidabile è molto inefficiente. Inizia a “rallentare” la trasmissione fornita dal canale di comunicazione man mano che aumenta l'RTT. Per aumentarne l'efficienza e utilizzare meglio la capacità del canale di comunicazione, viene utilizzato il pipelining.

Fondamenti di un trasferimento dati affidabile

L’uso del pipeline porta a:

  • aumentare l'intervallo dei numeri di sequenza, poiché tutti i pacchetti inviati (ad eccezione delle ritrasmissioni) devono essere identificati in modo univoco;
  • la necessità di aumentare i buffer sui lati trasmittente e ricevente.

L'intervallo dei numeri di sequenza e i requisiti relativi alla dimensione del buffer dipendono dalle azioni intraprese dal protocollo in risposta alla corruzione, alla perdita e al ritardo dei pacchetti. Nel caso del pipeline ci sono due metodi per correggere gli errori:

  • restituire N pacchetti;
  • ripetizione selettiva.

Tornando indietro N pacchetti - protocollo a finestra scorrevole

Fondamenti di un trasferimento dati affidabile

Il mittente deve supportare tre tipi di eventi:

  • chiamata tramite un protocollo di livello superiore. Quando la funzione di invio dati è detta “dall'alto”, il lato mittente verifica innanzitutto il grado di riempimento della finestra (cioè la presenza di N messaggi inviati in attesa di ricezione). Se la finestra è vuota, viene generato e trasmesso un nuovo pacchetto e i valori delle variabili vengono aggiornati. Altrimenti, il lato mittente restituisce i dati al livello superiore e questa è un'indicazione implicita che la finestra è piena. In genere il livello superiore tenterà di trasmettere nuovamente i dati dopo un po' di tempo. In un'applicazione reale, il mittente probabilmente bufferizzerebbe i dati (invece di inviarli immediatamente) o disporrebbe di un meccanismo di sincronizzazione (come un semaforo o un flag) che consentirebbe al livello superiore di chiamare la funzione di invio solo quando la finestra è vuota .
  • ricevendo conferma. Nel protocollo, per un pacchetto con numero di sequenza N, viene emessa una conferma generale che indica che tutti i pacchetti con numeri di sequenza precedenti N sono stati ricevuti con successo.
  • l'intervallo di attesa è scaduto. Per determinare i fatti relativi alle perdite e ai ritardi di pacchetti e ricevute, il protocollo utilizza un timer. Se l'intervallo di timeout scade, il lato mittente rinvia tutti i pacchetti inviati non riconosciuti.

Ripetizione selettiva

Quando la dimensione della finestra e il prodotto del ritardo della propagazione del throughput sono elevati, è possibile che nella pipeline sia presente un numero elevato di pacchetti. In tal caso, un singolo errore di pacchetto può causare la ritrasmissione di un gran numero di pacchetti, la maggior parte dei quali non erano necessari.

esempio

Top teorico le pratiche vengono raccolte nell'attuazione pratica TCP. E se qualcuno sa come farlo al meglio... il benvenuto.

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

Cliente

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

conclusione

Meccanismi per garantire il trasferimento e l’utilizzo affidabili dei dati

Механизм
Applicazione, commento

Controlla la somma
Utilizzato per rilevare errori di bit in un pacchetto trasmesso

Timer
Conta alla rovescia l'intervallo di timeout e indica quando è scaduto. Quest'ultimo significa che con un alto grado di probabilità il pacchetto o la sua ricevuta vengono persi durante la trasmissione. Se un pacchetto viene consegnato con un ritardo, ma non viene perso (scadenza anticipata dell'intervallo di timeout), o una ricezione viene persa, la ritrasmissione porta a un pacchetto duplicato sul lato ricevente

Numero di serie
Utilizzato per la numerazione sequenziale dei pacchetti di dati trasmessi dal mittente al destinatario. Le lacune nei numeri di sequenza dei pacchetti ricevuti consentono al ricevitore di rilevare la perdita di pacchetti. Gli stessi numeri di sequenza dei pacchetti indicano che i pacchetti sono duplicati l'uno dell'altro

Conferma
Generato dal destinatario e indica al mittente che il pacchetto o il gruppo di pacchetti corrispondente è stato ricevuto con successo. In genere la conferma contiene i numeri di sequenza dei pacchetti ricevuti con successo. A seconda del protocollo si distinguono conferme individuali e di gruppo

Conferma negativa
Utilizzato dal destinatario per informare il mittente che il pacchetto è stato ricevuto in modo errato. Una conferma negativa solitamente include il numero di sequenza del pacchetto che non è stato ricevuto correttamente

Finestra, trasportatore
Limita l'intervallo di numeri di sequenza che possono essere utilizzati per trasmettere i pacchetti. Il multicast e l'handshake possono aumentare significativamente la velocità effettiva del protocollo rispetto all'attesa dei riconoscimenti. Come vedremo, la dimensione della finestra può essere calcolata in base alle capacità di ricezione e buffering del destinatario, nonché al livello di carico della rete

Altri esempi di utilizzo di Go per il networking

В repository.

Fonte: habr.com

Aggiungi un commento