
A quienes 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 , incluyendo UDP y TCP. Basado en , , 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.

son soportados por sistemas finales, pero no por enrutadores de red (excepto — ). 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.

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.

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.

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.

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.

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 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.

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

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 prácticas reunidas en una implementación práctica . Y si alguien sabe cómo hacerlo mejor — .
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 .
Fuente: habr.com
