Fonaments de la transferència de dades fiable

Fonaments de la transferència de dades fiable

A aquells que busca Dedicat a la comprensió de xarxes i protocols.

Breument

L'article tracta els conceptes bàsics de la transmissió de dades fiable, implementa exemples Go, inclosos UDP i TCP. Basat en temps, два, 03:00 i els llibres "Computer Networks. Top-Down Approach", en cas contrari, tothom parla només de Tannenbaum i Oliferov.

Protocol de la capa de transport

Proporciona una connexió lògica entre els processos d'aplicació que s'executen en diferents amfitrions. Des de la perspectiva de l'aplicació, una connexió lògica sembla un canal que connecta directament processos.

Fonaments de la transferència de dades fiable

Protocols de la capa de transport són compatibles amb els sistemes finals, però no pels encaminadors de xarxa (excepte - DPI). Al costat del remitent, la capa de transport converteix les dades de la capa d'aplicació que rep del procés d'enviament de l'aplicació en paquets de la capa de transport anomenats segments.

Fonaments de la transferència de dades fiable

Això es fa dividint (si cal) els missatges de la capa d'aplicació en fragments i afegint una capçalera de capa de transport a cadascun d'ells.

Fonaments de la transferència de dades fiable

A continuació, la capa de transport passa el segment a la capa de xarxa del remitent, on el segment s'encapsula en un paquet de capa de xarxa (datagrama) i s'envia. A l'extrem receptor, la capa de xarxa extreu el segment de la capa de transport del datagrama i el passa a la capa de transport. A continuació, la capa de transport processa el segment rebut perquè les seves dades estiguin disponibles per a l'aplicació receptora.

Fonaments de la transferència de dades fiable

Principis de transmissió fiable de dades

Transmissió de dades fiable per un canal completament segur

El cas més senzill. El costat emissor simplement rep les dades de la capa superior, crea un paquet que les conté i les envia al canal.

Servidor

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

Client

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

Transmissió de dades fiable per un canal amb possibles errors

El següent pas és assumir que tots els paquets transmesos es reben en l'ordre en què van ser enviats, però els bits que contenen poden estar danyats a causa del fet que el canal de vegades transmet dades amb distorsions.

Fonaments de la transferència de dades fiable

En aquest cas, s'utilitzen els mecanismes següents:

  • detecció d'errors;
  • retroalimentació;
  • retransmissió.

Els protocols de transferència de dades fiables que tenen mecanismes similars per repetir la transmissió diverses vegades s'anomenen protocols de repetició automàtica (ARQ).
A més, val la pena considerar la possibilitat d'errors en els rebuts, quan la part receptora no rebrà cap informació sobre els resultats de la transferència de l'últim paquet.
La solució a aquest problema, també utilitzat en TCP, és afegir un nou camp al paquet de dades que conté el número de seqüència del paquet.

Fonaments de la transferència de dades fiable

Transmissió de dades fiable per un canal poc fiable subjecte a distorsió i pèrdua de paquets

Juntament amb la distorsió, malauradament, hi ha pèrdua de paquets a la xarxa.
I per resoldre aquest problema, calen mecanismes:

  • determinar el fet de la pèrdua de paquets;
  • tornar a lliurar els paquets perduts a la part receptora.

Addicionalment, a més de la pèrdua del paquet, cal preveure la possibilitat de pèrdua del rebut o, si no es perd res, el seu lliurament amb un retard important. En tots els casos es fa el mateix: es retransmet el paquet. Per controlar el temps, aquest mecanisme utilitza un temporitzador de compte enrere, que permet determinar el final de l'interval d'espera. Així que al paquet net TPCKeepAlive està establert per defecte en 15 segons:

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

El costat emissor ha d'iniciar un temporitzador cada vegada que es transmet un paquet (tant el primer com el segon), gestionar les interrupcions del temporitzador i aturar-lo.

Per tant, ens hem familiaritzat amb els conceptes clau dels protocols de transferència de dades fiables:

  • sumes de control;
  • números de seqüència de paquets;
  • temporitzadors;
  • rebuts positius i negatius.

Però això no és tot!

Protocol de transferència de dades fiable amb canalització

En la variant que ja hem considerat, el protocol de lliurament fiable és molt ineficient. Comença a "ralentir" la transmissió proporcionada pel canal de comunicació a mesura que augmenta l'RTT. Per augmentar la seva eficiència i aprofitar millor la capacitat del canal de comunicació, s'utilitza la canalització.

Fonaments de la transferència de dades fiable

L'ús de canonades condueix a:

  • augmentar el rang de números de seqüència, ja que tots els paquets enviats (excepte les retransmissions) s'han d'identificar de manera única;
  • la necessitat d'augmentar els buffers als costats transmissor i receptor.

L'interval de nombres de seqüència i els requisits de mida de la memòria intermèdia depenen de les accions que el protocol faci en resposta a la corrupció, la pèrdua i el retard de paquets. En el cas de la canalització, hi ha dos mètodes per corregir errors:

  • retornar N paquets;
  • repetició selectiva.

Tornant enrere N paquets - protocol de finestra lliscant

Fonaments de la transferència de dades fiable

El remitent ha de suportar tres tipus d'esdeveniments:

  • trucada per un protocol de nivell superior. Quan la funció d'enviament de dades s'anomena "des de dalt", el costat d'enviament comprova primer el grau d'ompliment de la finestra (és a dir, la presència de N missatges enviats a l'espera de rebre rebuts). Si la finestra està buida, es genera i es transmet un nou paquet i s'actualitzen els valors de les variables. En cas contrari, el costat d'enviament retorna les dades a la capa superior, i això és una indicació implícita que la finestra està plena. Normalment, la capa superior intentarà transmetre les dades de nou al cap d'un temps. En una aplicació real, el remitent probablement guardaria les dades a la memòria intermèdia (en lloc d'enviar-les immediatament) o disposaria d'un mecanisme de sincronització (com ara un semàfor o una bandera) que permetria que la capa superior cridés la funció d'enviament només quan la finestra estigui buida. .
  • rebent confirmació. En el protocol, per a un paquet amb el número de seqüència N, s'emet un reconeixement general que indica que tots els paquets amb números de seqüència anteriors a N s'han rebut correctament.
  • l'interval d'espera ha expirat. Per determinar els fets de pèrdues i retards de paquets i rebuts, el protocol utilitza un temporitzador. Si l'interval de temps d'espera expira, el costat emissor torna a enviar tots els paquets enviats sense reconèixer.

Repetició selectiva

Quan la mida de la finestra i el producte de retard de propagació de rendiment són grans, pot ser que hi hagi un gran nombre de paquets en procés. En aquest cas, un sol error de paquet pot provocar que es retransmeti un gran nombre de paquets, la majoria dels quals no eren necessaris.

Exemple

Superior teòric les pràctiques es recullen en la implementació pràctica TCP. I si algú sap com és millor... benvinguda.

Servidor

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

Client

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

Sortida

Mecanismes per garantir la transferència i l'ús fiable de les dades

Mecanisme
Aplicació, comentari

Comprova la suma
S'utilitza per detectar errors de bits en un paquet transmès

temporitzador
Compta enrere l'interval de temps d'espera i indica quan ha expirat. Això últim significa que amb un alt grau de probabilitat el paquet o la seva recepció es perd durant la transmissió. Si un paquet s'entrega amb un retard, però no es perd (expiració prematura de l'interval de temps d'espera) o es perd un rebut, la retransmissió condueix a un paquet duplicat al costat receptor.

Número de sèrie
S'utilitza per a la numeració seqüencial dels paquets de dades transmesos del remitent al destinatari. Els buits en els números de seqüència dels paquets rebuts permeten al receptor detectar la pèrdua de paquets. Els mateixos números de seqüència de paquets signifiquen que els paquets són duplicats els uns dels altres

Confirmació
Generat per l'extrem receptor i indicant a l'extrem emissor que el paquet o grup de paquets corresponent s'ha rebut correctament. Normalment, el reconeixement conté els números de seqüència dels paquets rebuts amb èxit. Segons el protocol, es distingeixen confirmacions individuals i grupals

Confirmació negativa
Utilitzat pel destinatari per informar al remitent que el paquet s'ha rebut incorrectament. Un reconeixement negatiu sol incloure el número de seqüència del paquet que no s'ha rebut correctament

Finestra, transportador
Limiteu el rang de números de seqüència que es poden utilitzar per transmetre paquets. La multidifusió i l'encaix de mans poden augmentar significativament el rendiment del protocol en comparació amb l'espera d'avisos de recepció. Com veurem, la mida de la finestra es pot calcular en funció de les capacitats de recepció i memòria intermèdia de l'extrem receptor, així com el nivell de càrrega de la xarxa.

Més exemples d'ús de Go per a xarxes

В repositoris.

Font: www.habr.com

Afegeix comentari