A aqueles que
Brevemente
O artigo analiza os conceptos básicos da transmisión de datos fiable, implementa exemplos
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.
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.
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.
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.
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.
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
// 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.
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
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
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
В
Fonte: www.habr.com