Fundamentos de la transmisión de datos confiable

Fundamentos de la transmisión de datos confiable

A quienes está buscando quieren comprender las redes y protocolos, se dedica.

Resumen

El artículo examina los fundamentos de la transmisión confiable de datos, implementando ejemplos en Go, incluyendo UDP y TCP. Basado en uno, dos, tres y el libro 'Redes de Computadoras. Enfoque descendente', ya que todos solo discuten a Tanenbaum y Olifer.

Protocolo de nivel de transporte

Proporciona una conexión lógica entre los procesos de aplicación que se ejecutan en diferentes hosts. La conexión lógica desde la perspectiva de las aplicaciones se presenta como un canal que conecta directamente los procesos.

Fundamentos de la transmisión de datos confiable

Protocolos de nivel de transporte son soportados por sistemas finales, pero no por enrutadores de red (excepto — DPI). En el lado del remitente, el nivel de transporte convierte los datos del nivel de aplicación que recibe del proceso de aplicación transmisor en paquetes de nivel de transporte, llamados segmentos.

Fundamentos de la transmisión de datos confiable

Esto se realiza fragmentando (si es necesario) los mensajes de nivel de aplicación en partes y añadiendo a cada uno de ellos una cabecera de nivel de transporte.

Fundamentos de la transmisión de datos confiable

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

Fundamentos de la transmisión de datos confiable

Principios de transmisión confiable de datos

La transmisión confiable de datos a través de un canal completamente confiable

El caso más simple. La parte emisora simplemente toma los datos del nivel superior, crea un paquete que los contiene y lo envía a través del canal.

Servidor

package main

import (
    "log"
    "net"
)

func main() {
    // Dirección IP del servidor y puerto
    serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:12000")
    if err != nil {
        log.Fatal(err)
    }

    // creamos un socket con el puerto
    serverConn, err := net.ListenUDP("udp", serverAddr)
    if err != nil {
        log.Fatal(err)
    }
    // cierre diferido de la conexión
    defer serverConn.Close()

    // creamos un búfer para los datos
    buf := make([]byte, 1024)

    // esperamos la conexión
    for {
        // leemos la solicitud
        n, addr, err := serverConn.ReadFromUDP(buf)
        // pasamos los datos al NIVEL SUPERIOR: en nuestro caso stdout
        println(string(buf[0:n]), " from ", addr.IP.String())
        if err != nil {
            log.Fatal(err)
        }
        // no hay respuesta, ya que esto es UDP + canal confiable
    }
}

Cliente

package main

import (
    "fmt"
    "log"
    "net"
    "time"
)

func main() {
    // Dirección IP del servidor y puerto
    serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:12000")
    if err != nil {
        log.Fatal(err)
    }
    // Dirección IP local y puerto
    localAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
    if err != nil {
        log.Fatal(err)
    }
    // Establecer la conexión
    conn, err := net.DialUDP("udp", localAddr, serverAddr)
    if err != nil {
        log.Fatal(err)
    }
    // Cierre de conexión diferido
    defer conn.Close()

    for {
        // Obtener datos de la capa SUPERIOR
        fmt.Print("Ingrese una frase en minúsculas > ")
        var msg string
        _, err := fmt.Scanf("%s", &msg)
        if err != nil {
            log.Fatal(err)
        }
        // Se transmite un flujo de bytes, no una cadena
        buf := []byte(msg)
        // Escritura (transmisión) en la conexión
        _, err = conn.Write(buf)
        if err != nil {
            log.Fatal(err)
        }
        // Un segundito
        time.Sleep(time.Second * 1)
    }
}

Transmisión de datos confiable a través de un canal con errores potenciales

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

Fundamentos de la transmisión de datos confiable

En tal caso se utilizan mecanismos:

  • detención de errores;
  • retroalimentación;
  • retransmisión.

Los protocolos de transmisión de datos confiables que disponen de mecanismos de retransmisión se llaman protocolos con solicitud automática de retransmisión (Automatic Repeat reQuest, ARQ).
Además, es necesario prever la posibilidad de errores en los acuses de recibo, cuando la parte receptora no recibe ninguna información sobre los resultados de la transmisión del último paquete.
La solución a este problema, utilizada también en TCP, consiste en agregar un nuevo campo de número de secuencia al paquete de datos.

Fundamentos de la transmisión de datos confiable

Transmisión de datos confiable a través de un canal no confiable que permite distorsiones y pérdidas de paquetes

Lamentablemente, junto con las distorsiones, también hay pérdida de paquetes en la red.
Y para abordar esta problemática se requieren mecanismos:

  • detección de pérdida de paquetes;
  • reentrega de paquetes perdidos a la parte receptora.

Además de la pérdida de paquetes, es necesario prever la posibilidad de pérdida de confirmaciones o, si no se ha perdido nada, de su entrega con un retraso significativo. En todos los casos se realiza lo mismo: la retransmisión del paquete. Para controlar el tiempo en este mecanismo se utiliza un temporizador que permite determinar el final del intervalo de espera. Así que en el paquete net el parámetro TCPKeepAlive está configurado por defecto en 15 segundos:

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

La parte transmisora debe iniciar el temporizador cada vez que se envía un paquete (tanto en el primer envío como en el reenvío), gestionar las interrupciones del temporizador y detenerlo.

Así que hemos revisado los conceptos clave de los protocolos de transmisión de datos confiables:

  • sumas de verificación;
  • números de secuencia de paquetes;
  • temporizadores;
  • confirmaciones positivas y negativas.

¡Pero esto no es todo!

Protocolo de transmisión de datos confiable con canalización

En la variante que ya hemos revisado, el protocolo de entrega confiable es muy ineficiente. Comienza a 'frenar' la transmisión, garantizada por el canal de comunicación, con un aumento del RTT. Para mejorar su eficiencia y optimizar el uso del ancho de banda del canal de comunicación se aplica la canalización.

Fundamentos de la transmisión de datos confiable

La aplicación de la canalización lleva a:

  • un aumento en el rango de números de secuencia, ya que todos los paquetes enviados (excepto las retransmisiones) deben ser identificables de manera única;
  • la necesidad de aumentar los buffers en las partes transmitidora y receptora.

El rango de números de secuencia y los requisitos de tamaño de los buffers dependen de las acciones que el protocolo tome en respuesta a la distorsión, pérdida y retraso del paquete. En el caso de la canalización, existen dos métodos de corrección de errores:

  • retroceder N paquetes;
  • repetición selectiva.

Retroceder N paquetes: protocolo de ventana deslizante

Fundamentos de la transmisión de datos confiable

El emisor debe mantener tres tipos de eventos:

  • llamada desde un protocolo de nivel superior. Cuando se invoca una función de envío de datos desde "arriba", la parte que envía primero verifica el grado de llenado de la ventana (es decir, la cantidad de N mensajes enviados que están a la espera de confirmaciones). Si la ventana está no llena, se forma un nuevo paquete y se envía, actualizando los valores de las variables. En caso contrario, la parte que envía devuelve los datos al nivel superior, lo que indica implícitamente que la ventana está llena. Normalmente, el nivel superior intenta retransmitir los datos después de un tiempo. En una aplicación real, el emisor probablemente habría almacenado los datos en un búfer (en lugar de enviarlos inmediatamente) o tendría un mecanismo de sincronización (como un semáforo o un indicador) que permitiría al nivel superior llamar a la función de envío de datos solo cuando la ventana no está llena.
  • recepción de confirmación. En el protocolo, para el paquete con el 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 han sido recibidos correctamente.
  • tiempo de espera. Para determinar la pérdida de paquetes y la demora en las confirmaciones, el protocolo utiliza un temporizador. Si el tiempo de espera se agota, la parte que envía retransmite todos los paquetes enviados que no han sido confirmados.

retransmisión selectiva

Cuando el tamaño de la ventana y el producto de la capacidad de transmisión por la latencia de propagación son grandes, puede haber una gran cantidad de paquetes en la tubería. En tal caso, el error de un solo paquete puede provocar la retransmisión de muchos paquetes, la mayoría de los cuales no se necesitaban.

Ejemplo

Mejores teóricas prácticas reunidas en una implementación práctica TCP. Y si alguien sabe cómo hacerlo mejor — bienvenido.

Servidor

package main

import (
    "bufio"
    "fmt"
    "log"
    "net"
    "strings"
)

func main() {
    // creando un socket con el puerto 
    ln, err := net.Listen("tcp", ":8081")
    if err != nil {
        log.Fatalln(err)
    }
    // esperando llamada
    conn, _ := ln.Accept()

    for {
        // lectura de datos
        msg, err := bufio.NewReader(conn).ReadString('n')
        if err != nil {
            log.Fatalln(err)
        }
        // salida del mensaje en stdout
        fmt.Print("Message Received:", string(msg))
        // convertir la cadena a mayúsculas
        newMsg := strings.ToUpper(msg)
        // envío de datos
        conn.Write([]byte(newMsg + "n"))
    }
}

Cliente

paquete principal

importar (
    "bufio"
    "fmt"
    "log"
    "net"
    "os"
)

función principal() {
    // establecer conexión
    conn, err := net.Dial("tcp", "127.0.0.1:8081")
    si err != nulo {
        log.Fatalln(err)
    }

    para {
        // leer datos desde stdin
        lector := bufio.NewReader(os.Stdin)
        fmt.Print("Texto a enviar: ")
        // línea por línea
        texto, err := lector.ReadString('n')
        si err != nulo {
            log.Fatalln(err)
        }
        // envío
        fmt.Fprintf(conn, texto+"n")
        // recepción
        msg, err := bufio.NewReader(conn).ReadString('n')
        si err != nulo {
            log.Fatalln(err)
        }
        // salida de la respuesta recibida
        fmt.Print("Msg del Servidor: " + msg)
    }
}

Salida

Mecanismos que garantizan la transmisión y uso confiable de datos

Mecanismo
Aplicación, comentario

Suma de verificación
Se utiliza para detectar errores de bits en el paquete transmitido

Temporizador
Cuenta del intervalo de espera e indicación de su expiración. Esto último significa que es muy probable que el paquete o su acuse de recibo se hayan perdido durante la transmisión. Si el paquete se entrega con retraso, pero no se pierde (expiración prematura del intervalo de espera), o si se pierde el acuse de recibo, la retransmisión resulta en la duplicación del paquete en el lado receptor

Número de serie
Se utiliza para la numeración secuencial de los paquetes de datos transmitidos del remitente al receptor. Las interrupciones en los números de serie de los paquetes recibidos permiten al receptor detectar la pérdida de paquetes. Números de serie idénticos de paquetes significan que los paquetes se duplican entre sí

Confirmación
Generado por el lado receptor e indica al lado transmisor que el paquete correspondiente o grupo de paquetes han sido recibidos exitosamente. Normalmente, la confirmación incluye los números de serie de los paquetes que han sido recibidos con éxito. Dependiendo del protocolo, se distinguen confirmaciones individuales y grupales

Confirmación negativa
Utilizado por el receptor para informar al remitente que el paquete fue recibido incorrectamente. La confirmación negativa generalmente incluye el número de serie del paquete que no fue recibido correctamente

Ventana, canalización
Restringen el rango de números de secuencia que se pueden utilizar para la transmisión de paquetes. La transmisión en grupo y el apretón de manos permiten aumentar significativamente el ancho de banda de los protocolos en comparación con el modo de espera de confirmaciones. Como veremos, el tamaño de la ventana puede calcularse en función de las capacidades de recepción y almacenamiento en búfer del lado receptor, así como del nivel de carga de la red.

Otros ejemplos de uso de Go para trabajar con redes.

En el repositorio.

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster