Fundamentos de transferencia de datos fiable

Fundamentos de transferencia de datos fiable

A aqueles que busca Dedicado á comprensión de redes e protocolos.

Brevemente

O artigo analiza os conceptos básicos da transmisión de datos fiable, implementa exemplos Go, incluíndo UDP e TCP. Baseado en tempo, два, tres e os libros "Computer Networks. Top-Down Approach", se non, todos discuten só Tannenbaum e Oliferov.

Protocolo de capa de transporte

Ofrece unha conexión lóxica entre os procesos de aplicación que se executan en hosts diferentes. Desde a perspectiva da aplicación, unha conexión lóxica parece unha canle que conecta directamente os procesos.

Fundamentos de transferencia de datos fiable

Protocolos da capa de transporte son compatibles con sistemas finais, pero non por enrutadores de rede (excepto - DPI). No lado do remitente, a capa de transporte converte os datos da capa de aplicación que recibe do proceso de envío da aplicación en paquetes de capa de transporte chamados segmentos.

Fundamentos de transferencia de datos fiable

Isto faise dividindo (se é necesario) as mensaxes da capa de aplicación en fragmentos e engadindo unha cabeceira de capa de transporte a cada unha delas.

Fundamentos de transferencia de datos fiable

A capa de transporte pasa entón o segmento á capa de rede do remitente, onde o segmento se encapsula nun paquete da capa de rede (datagrama) e se envía. No extremo receptor, a capa de rede extrae o segmento da capa de transporte do datagrama e pásao á capa de transporte. A continuación, a capa de transporte procesa o segmento recibido para que os seus datos estean dispoñibles para a aplicación receptora.

Fundamentos de transferencia de datos fiable

Principios de transmisión fiable de datos

Transmisión de datos fiable a través dunha canle completamente segura

O caso máis sinxelo. O lado emisor simplemente recibe os datos da capa superior, crea un paquete que os contén e envíaos á canle.

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

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

Transmisión de datos fiable a través dunha canle con posibles erros

O seguinte paso é asumir que todos os paquetes transmitidos se reciben na orde en que foron enviados, pero os bits neles poden estar corrompidos debido ao feito de que a canle ás veces transmite datos con distorsións.

Fundamentos de transferencia de datos fiable

Neste caso, utilízanse os seguintes mecanismos:

  • detección de erros;
  • retroalimentación;
  • retransmisión.

Os protocolos de transferencia de datos fiables que teñen mecanismos similares para repetir a transmisión varias veces chámanse protocolos de repetición automática (ARQ).
Ademais, convén considerar a posibilidade de erros nos recibos, cando a parte receptora non recibirá información sobre os resultados da transferencia do último paquete.
A solución a este problema, tamén usado en TCP, consiste en engadir un novo campo ao paquete de datos que conteña o número de secuencia do paquete.

Fundamentos de transferencia de datos fiable

Transmisión de datos fiable a través dunha canle pouco fiable suxeita a distorsión e perda de paquetes

Xunto coa distorsión, desafortunadamente, hai perda de paquetes na rede.
E para resolver este problema, son necesarios mecanismos:

  • determinar o feito da perda de paquetes;
  • reentrega dos paquetes perdidos á parte receptora.

Ademais, ademais da perda do paquete, é necesario prever a posibilidade de perda do recibo ou, se non se perde nada, a súa entrega cun atraso importante. En todos os casos faise o mesmo: retransmítese o paquete. Para controlar o tempo, este mecanismo utiliza un temporizador de conta atrás, que permite determinar o final do intervalo de espera. Entón, no paquete net TCPKeepAlive está configurado en 15 segundos por defecto:

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

O lado emisor debe iniciar un temporizador cada vez que se transmite un paquete (tanto a primeira como a segunda), xestionar as interrupcións do temporizador e detelo.

Polo tanto, familiarizamos cos conceptos clave dos protocolos de transferencia de datos fiables:

  • sumas de verificación;
  • números de secuencia de paquetes;
  • temporizadores;
  • recibos positivos e negativos.

Pero iso non é todo!

Protocolo de transferencia de datos fiable con canalización

Na variante que xa consideramos, o protocolo de entrega fiable é moi ineficiente. Comeza a "ralentizar" a transmisión proporcionada pola canle de comunicación a medida que aumenta o RTT. Para aumentar a súa eficiencia e aproveitar mellor a capacidade da canle de comunicación, utilízase o pipeline.

Fundamentos de transferencia de datos fiable

O uso de canalizacións leva a:

  • aumentar o rango de números de secuencia, xa que todos os paquetes enviados (agás as retransmisións) deben ser identificados de forma única;
  • a necesidade de aumentar os buffers nos lados transmisor e receptor.

O intervalo de números de secuencia e os requisitos de tamaño do búfer dependen das accións que realice o protocolo en resposta á corrupción, perda e atraso dos paquetes. No caso da canalización, hai dous métodos para corrixir erros:

  • devolver N paquetes;
  • repetición selectiva.

Volver atrás N paquetes - protocolo de xanela deslizante

Fundamentos de transferencia de datos fiable

O remitente debe admitir tres tipos de eventos:

  • chamada por un protocolo de nivel superior. Cando a función de envío de datos chámase "desde arriba", o lado emisor comproba primeiro o grao de enchemento da xanela (é dicir, a presenza de N mensaxes enviadas á espera de recibir os recibos). Se a xanela está baleira, xérase e transmítese un novo paquete e actualízanse os valores das variables. En caso contrario, o lado emisor devolve os datos á capa superior, e isto é unha indicación implícita de que a xanela está chea. Normalmente, a capa superior tentará transmitir os datos de novo despois dun tempo. Nunha aplicación real, o remitente probablemente almacenaría os datos (en lugar de envialos inmediatamente) ou disporía dun mecanismo de sincronización (como un semáforo ou unha bandeira) que permitiría á capa superior chamar á función de envío só cando a xanela estea baleira. .
  • recibindo confirmación. No protocolo, para un paquete con número de secuencia N, emítese un acuse de recibo xeral que indica que todos os paquetes con números de secuencia anteriores a N foron recibidos con éxito.
  • o intervalo de espera expirou. Para determinar os feitos de perdas e atrasos de paquetes e recibos, o protocolo utiliza un temporizador. Se o intervalo de tempo de espera expira, o lado remitente reenvía todos os paquetes enviados sen acuse de recibo.

Repetición selectiva

Cando o tamaño da xanela e o produto de atraso de propagación do rendemento son grandes, pode haber un gran número de paquetes en proceso. Neste caso, un só erro de paquete pode provocar que se retransmita unha gran cantidade de paquetes, a maioría dos cales non foron necesarios.

Exemplo

O mellor teórico as prácticas recóllense na aplicación práctica TCP. E se alguén sabe como mellor... Benvido.

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

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

Saída

Mecanismos para garantir a transferencia e o uso fiables dos datos

Mecanismo
Aplicación, comentario

Verificar suma
Úsase para detectar erros de bit nun paquete transmitido

Temporizador
Conta atrás o intervalo de tempo de espera e indica cando expirou. Isto último significa que, cun alto grao de probabilidade, o paquete ou a súa recepción pérdese durante a transmisión. Se un paquete se entrega con atraso, pero non se perde (expiración prematura do intervalo de tempo de espera) ou se perde un recibo, a retransmisión leva a un paquete duplicado no lado receptor.

Número de serie
Úsase para a numeración secuencial dos paquetes de datos transmitidos do remitente ao destinatario. As lagoas nos números de secuencia dos paquetes recibidos permiten que o receptor detecte a perda de paquetes. Os mesmos números de secuencia de paquetes significan que os paquetes son duplicados entre si

Confirmación
Xerado polo extremo receptor e indicando ao extremo emisor que o paquete ou grupo de paquetes correspondente foi recibido con éxito. Normalmente, o acuse de recibo contén os números de secuencia dos paquetes recibidos con éxito. Segundo o protocolo, distínguense confirmacións individuais e grupais

Confirmación negativa
Usado polo destinatario para informar ao remitente de que o paquete foi recibido incorrectamente. Un acuse de recibo negativo adoita incluír o número de secuencia do paquete que non se recibiu correctamente

Fiestra, transporte
Limite o rango de números de secuencia que se poden usar para transmitir paquetes. Multicast e handshake poden aumentar significativamente o rendemento do protocolo en comparación coa espera de recoñecementos. Como veremos, o tamaño da xanela pódese calcular en función das capacidades de recepción e almacenamento en búfer do extremo receptor, así como do nivel de carga da rede.

Máis exemplos de uso de Go para redes

В repositorios.

Fonte: www.habr.com

Engadir un comentario