Fundamentos de la transferencia de datos confiable

Fundamentos de la transferencia de datos confiable

A aquellos quienes стремится Dedicado a comprender redes y protocolos.

En pocas palabras

El artículo analiza los conceptos básicos de la transmisión de datos confiable, implementa ejemplos sobre Go, incluidos UDP y TCP. Residencia en tiempo, два, tres y los libros "Redes informáticas. Enfoque de arriba hacia abajo", por lo demás, todo el mundo habla sólo de Tannenbaum y Oliferov.

Protocolo de capa de transporte

Proporciona una conexión lógica entre procesos de aplicaciones que se ejecutan en diferentes hosts. Desde la perspectiva de una aplicación, una conexión lógica parece un canal que conecta procesos directamente.

Fundamentos de la transferencia de datos confiable

Protocolos de capa de transporte son compatibles con los sistemas finales, pero no con los enrutadores de red (excepto - DPI). En el lado del remitente, la capa de transporte convierte los datos de la capa de aplicación que recibe del proceso de aplicación de envío en paquetes de la capa de transporte llamados segmentos.

Fundamentos de la transferencia de datos confiable

Esto se hace dividiendo (si es necesario) los mensajes de la capa de aplicación en fragmentos y agregando un encabezado de capa de transporte a cada uno de ellos.

Fundamentos de la transferencia de datos confiable

Luego, la capa de transporte pasa el segmento a la capa de red del remitente, donde el segmento se encapsula en un paquete de capa de red (datagrama) y se envía. En el extremo receptor, la capa de red extrae el segmento de la capa de transporte del datagrama y lo pasa a la capa de transporte. A continuación, la capa de transporte procesa el segmento recibido para que sus datos estén disponibles para la aplicación receptora.

Fundamentos de la transferencia de datos confiable

Principios de una transmisión de datos confiable

Transmisión de datos confiable a través de un canal completamente seguro

El caso más simple. El lado emisor simplemente recibe los datos de la capa superior, crea un paquete que los contiene y los envía 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 + надежный канал
    }
}

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 confiable a través de un canal con posibles errores.

El siguiente paso es suponer que todos los paquetes transmitidos se reciben en el orden en que fueron enviados, pero los bits que contienen pueden estar dañados debido al hecho de que el canal a veces transmite datos con distorsiones.

Fundamentos de la transferencia de datos confiable

En este caso, se utilizan los siguientes mecanismos:

  • detección de errores;
  • comentario;
  • retransmisión.

Los protocolos de transferencia de datos confiables que tienen mecanismos similares para repetir la transmisión varias veces se denominan protocolos de solicitud de repetición automática (ARQ).
Además, vale la pena considerar la posibilidad de errores en los recibos, cuando el destinatario no recibirá ninguna información sobre los resultados de la transferencia del último paquete.
La solución a este problema, también utilizada en TCP, es agregar un nuevo campo al paquete de datos que contiene el número de secuencia del paquete.

Fundamentos de la transferencia de datos confiable

Transmisión de datos confiable a través de un canal no confiable sujeto a distorsión y pérdida de paquetes

Desafortunadamente, junto con la distorsión, también se pierden paquetes en la red.
Y para solucionar este problema se requieren mecanismos:

  • determinar el hecho de la pérdida de paquetes;
  • reenvío de paquetes perdidos a la parte receptora.

Además, además de la pérdida del paquete, es necesario prever la posibilidad de pérdida del recibo o, si no se pierde nada, de su entrega con un retraso importante. En todos los casos se hace lo mismo: el paquete se retransmite. Para controlar el tiempo, este mecanismo utiliza un temporizador de cuenta regresiva, que le permite determinar el final del intervalo de espera. Entonces en el paquete red TCPKeepAlive está configurado en 15 segundos de forma predeterminada:

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

El lado emisor necesita iniciar un temporizador cada vez que se transmite un paquete (tanto la primera como la segunda vez), manejar las interrupciones del temporizador y detenerlo.

Entonces, nos familiarizamos con los conceptos clave de protocolos confiables de transferencia de datos:

  • sumas de control;
  • números de secuencia de paquetes;
  • temporizadores;
  • recibos positivos y negativos.

¡Pero eso no es todo!

Protocolo de transferencia de datos confiable con canalización

En la variante que ya hemos considerado, el protocolo de entrega fiable es muy ineficiente. Comienza a "ralentizar" la transmisión proporcionada por el canal de comunicación a medida que aumenta el RTT. Para aumentar su eficiencia y utilizar mejor la capacidad del canal de comunicación, se utiliza canalización.

Fundamentos de la transferencia de datos confiable

El uso de canalización conduce a:

  • aumentar el rango de números de secuencia, ya que todos los paquetes enviados (excepto las retransmisiones) deben identificarse de forma única;
  • la necesidad de aumentar los buffers en los lados transmisor y receptor.

El rango de números de secuencia y los requisitos de tamaño del búfer dependen de las acciones que realiza el protocolo en respuesta a la corrupción, pérdida y retraso de los paquetes. En el caso de la canalización, existen dos métodos para corregir errores:

  • devolver N paquetes;
  • repetición selectiva.

Regresar N paquetes - protocolo de ventana deslizante

Fundamentos de la transferencia de datos confiable

El remitente debe admitir tres tipos de eventos:

  • llamada mediante un protocolo de nivel superior. Cuando la función de envío de datos se llama "desde arriba", el lado emisor primero verifica el grado de llenado de la ventana (es decir, la presencia de N mensajes enviados en espera de recibir recibos). Si la ventana está vacía, se genera y transmite un nuevo paquete y se actualizan los valores de las variables. De lo contrario, el lado emisor devuelve datos a la capa superior, y esto es una indicación implícita de que la ventana está llena. Normalmente, la capa superior intentará transmitir los datos nuevamente después de un tiempo. En una aplicación real, el remitente probablemente almacenaría los datos en un buffer (en lugar de enviarlos inmediatamente) o tendría un mecanismo de sincronización (como un semáforo o una bandera) que permitiría a la capa superior llamar a la función de envío solo cuando la ventana esté vacía. .
  • recibiendo confirmación. En el protocolo, para un paquete con número de secuencia N, se emite un acuse de recibo general que indica que todos los paquetes con números de secuencia anteriores a N se recibieron con éxito.
  • el intervalo de espera ha expirado. Para determinar los hechos de pérdidas y retrasos de paquetes y recibos, el protocolo utiliza un temporizador. Si el intervalo de tiempo de espera expira, el lado emisor reenvía todos los paquetes enviados no reconocidos.

repetición selectiva

Cuando el tamaño de la ventana y el producto del retardo de propagación del rendimiento son grandes, es posible que haya una gran cantidad de paquetes en proceso. En tal caso, un solo error de paquete puede provocar la retransmisión de una gran cantidad de paquetes, la mayoría de los cuales no eran necesarios.

ejemplo

Superior teórico Las prácticas se recogen en la implementación práctica. TCP. Y si alguien sabe cuál es la mejor manera... bienvenido.

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

conclusión

Mecanismos para garantizar la transferencia y el uso confiable de datos

Mecanismo
Aplicación, comentario

Suma de cheque
Se utiliza para detectar errores de bits en un paquete transmitido.

Timer
Cuenta atrás el intervalo de tiempo de espera e indica cuándo ha expirado. Esto último significa que con un alto grado de probabilidad el paquete o su recepción se pierde durante la transmisión. Si un paquete se entrega con retraso, pero no se pierde (expiración prematura del intervalo de tiempo de espera) o se pierde un recibo, la retransmisión genera un paquete duplicado en el lado receptor.

Numero de secuencia
Se utiliza para la numeración secuencial de paquetes de datos transmitidos del remitente al destinatario. Los espacios en los números de secuencia de los paquetes recibidos permiten al receptor detectar la pérdida de paquetes. Los mismos números de secuencia de paquetes significan que los paquetes son duplicados entre sí.

Confirmación
Generado por el extremo receptor e indicando al extremo emisor que el paquete o grupo de paquetes correspondiente se ha recibido con éxito. Normalmente, el acuse de recibo contiene los números de secuencia de los paquetes recibidos correctamente. Según el protocolo se distinguen confirmaciones individuales y grupales.

Confirmación negativa
Utilizado por el destinatario para informar al remitente que el paquete se recibió incorrectamente. Un acuse de recibo negativo suele incluir el número de secuencia del paquete que no se recibió correctamente.

Ventana, transportadorización
Limite el rango de números de secuencia que se pueden utilizar para transmitir paquetes. La multidifusión y el protocolo de enlace pueden aumentar significativamente el rendimiento del protocolo en comparación con la espera de reconocimientos. Como veremos, el tamaño de la ventana se puede calcular en función de las capacidades de recepción y almacenamiento en búfer del extremo receptor, así como del nivel de carga de la red.

Más ejemplos de uso de Go para networking

В repositorios.

Fuente: habr.com

Añadir un comentario