Fundamentos da transferência confiável de dados

Fundamentos da transferência confiável de dados

Para aqueles que procura Dedicado à compreensão de redes e protocolos.

Brevemente

O artigo discute os fundamentos da transmissão confiável de dados, implementa exemplos em Go, incluindo UDP e TCP. Baseado em tempo, два, três e os livros "Redes de Computadores. Abordagem Top-Down", caso contrário, todos estão discutindo apenas Tannenbaum e Oliferov.

Protocolo da camada de transporte

Fornece uma conexão lógica entre processos de aplicativos executados em hosts diferentes. Do ponto de vista do aplicativo, uma conexão lógica se parece com um canal que conecta processos diretamente.

Fundamentos da transferência confiável de dados

Protocolos da camada de transporte são suportados por sistemas finais, mas não por roteadores de rede (exceto - DPI). No lado do remetente, a camada de transporte converte os dados da camada de aplicação que recebe do processo de aplicação remetente em pacotes da camada de transporte chamados segmentos.

Fundamentos da transferência confiável de dados

Isso é feito dividindo (se necessário) as mensagens da camada de aplicação em fragmentos e adicionando um cabeçalho da camada de transporte a cada um deles.

Fundamentos da transferência confiável de dados

A camada de transporte então passa o segmento para a camada de rede do remetente, onde o segmento é encapsulado em um pacote da camada de rede (datagrama) e enviado. Na extremidade receptora, a camada de rede extrai o segmento da camada de transporte do datagrama e o passa para a camada de transporte. Em seguida, a camada de transporte processa o segmento recebido para que seus dados fiquem disponíveis para a aplicação receptora.

Fundamentos da transferência confiável de dados

Princípios de transmissão confiável de dados

Transmissão de dados confiável em um canal totalmente seguro

O caso mais simples. O lado emissor simplesmente recebe os dados da camada superior, cria um pacote contendo-os e os envia ao 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)
    }
}

Transmissão de dados confiável através de um canal com possíveis erros

O próximo passo é assumir que todos os pacotes transmitidos são recebidos na ordem em que foram enviados, mas os bits neles podem estar corrompidos devido ao fato de que o canal às vezes transmite dados com distorções.

Fundamentos da transferência confiável de dados

Neste caso, são utilizados os seguintes mecanismos:

  • detecção de erro;
  • opinião;
  • retransmissão.

Protocolos de transferência de dados confiáveis ​​que possuem mecanismos semelhantes para repetir a transmissão várias vezes são chamados de protocolos Automatic Repeat ReQuest (ARQ).
Além disso, vale considerar a possibilidade de erros nos recebimentos, quando o destinatário não receberá nenhuma informação sobre o resultado da transferência do último pacote.
A solução para este problema, também utilizada no TCP, é adicionar um novo campo ao pacote de dados contendo o número de sequência do pacote.

Fundamentos da transferência confiável de dados

Transmissão de dados confiável através de um canal não confiável sujeito a distorção e perda de pacotes

Junto com a distorção, infelizmente, há perda de pacotes na rede.
E para resolver este problema, são necessários mecanismos:

  • determinar o fato da perda de pacotes;
  • reentrega de pacotes perdidos à parte receptora.

Adicionalmente, além do extravio da encomenda, é necessário prever a possibilidade de extravio do recibo ou, caso nada se perca, a sua entrega com atraso significativo. Em todos os casos, faz-se a mesma coisa: o pacote é retransmitido. Para controlar o tempo, este mecanismo utiliza um cronômetro de contagem regressiva, que permite determinar o final do intervalo de espera. Então no pacote líquido TCPKeepAlive é definido como 15 segundos por padrão:

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

O lado remetente precisa iniciar um cronômetro toda vez que um pacote é transmitido (tanto na primeira quanto na segunda vez), tratar as interrupções do cronômetro e interrompê-lo.

Assim, nos familiarizamos com os principais conceitos de protocolos confiáveis ​​de transferência de dados:

  • somas de verificação;
  • números de sequência de pacotes;
  • temporizadores;
  • receitas positivas e negativas.

Mas isso não é tudo!

Protocolo de transferência de dados confiável com pipeline

Na variante que já consideramos, o protocolo de entrega confiável é muito ineficiente. Começa a “desacelerar” a transmissão fornecida pelo canal de comunicação à medida que o RTT aumenta. Para aumentar sua eficiência e utilizar melhor a capacidade do canal de comunicação, utiliza-se o pipelining.

Fundamentos da transferência confiável de dados

O uso de pipelining leva a:

  • aumentar o intervalo de números de sequência, uma vez que todos os pacotes enviados (exceto retransmissões) devem ser identificados de forma única;
  • a necessidade de aumentar os buffers nos lados de transmissão e recepção.

O intervalo de números de sequência e os requisitos de tamanho do buffer dependem das ações que o protocolo executa em resposta à corrupção, perda e atraso de pacotes. No caso de pipelining, existem dois métodos para corrigir erros:

  • retornar N pacotes de volta;
  • repetição seletiva.

Voltando N pacotes - protocolo de janela deslizante

Fundamentos da transferência confiável de dados

O remetente deve suportar três tipos de eventos:

  • chamada por um protocolo de nível superior. Quando a função de envio de dados é chamada “de cima”, o remetente verifica primeiro o grau de preenchimento da janela (ou seja, a presença de N mensagens enviadas aguardando recebimento de recibos). Se a janela estiver vazia, um novo pacote é gerado e transmitido, e os valores das variáveis ​​são atualizados. Caso contrário, o lado emissor retorna os dados para a camada superior, e esta é uma indicação implícita de que a janela está cheia. Normalmente, a camada superior tentará transmitir os dados novamente após algum tempo. Em uma aplicação real, o remetente provavelmente armazenaria os dados em buffer (em vez de enviá-los imediatamente) ou teria um mecanismo de sincronização (como um semáforo ou sinalizador) que permitiria à camada superior chamar a função de envio somente quando a janela estivesse vazia .
  • recebendo confirmação. No protocolo, para um pacote com número de sequência N, uma confirmação geral é emitida indicando que todos os pacotes com números de sequência anteriores a N foram recebidos com sucesso.
  • o intervalo de espera expirou. Para determinar os fatos de perdas e atrasos de pacotes e recebimentos, o protocolo utiliza um temporizador. Se o intervalo de tempo limite expirar, o lado remetente reenviará todos os pacotes não confirmados enviados.

Repetição seletiva

Quando o tamanho da janela e o produto do atraso de propagação da taxa de transferência são grandes, um grande número de pacotes pode estar no pipeline. Nesse caso, um único erro de pacote pode causar a retransmissão de um grande número de pacotes, a maioria dos quais não eram necessários.

Exemplo

Topo teórico práticas são coletadas na implementação prática TCP. E se alguém souber a melhor forma - boas-vindas.

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

Jogar aviator online grátis: hack aviator funciona

Mecanismos para garantir transferência e uso confiável de dados

Mecanismo
Aplicação, comentário

Checar soma
Usado para detectar erros de bits em um pacote transmitido

Cronômetro
Faz a contagem regressiva do intervalo de tempo limite e indica quando ele expirou. Este último significa que com um alto grau de probabilidade o pacote ou seu recebimento será perdido durante a transmissão. Se um pacote for entregue com atraso, mas não for perdido (expiração prematura do intervalo de tempo limite), ou um recebimento for perdido, a retransmissão levará a um pacote duplicado no lado receptor

Número de série
Usado para numeração sequencial de pacotes de dados transmitidos do remetente ao destinatário. Lacunas nos números de sequência dos pacotes recebidos permitem que o receptor detecte a perda de pacotes. Os mesmos números de sequência de pacotes significam que os pacotes são duplicados um do outro

Confirmação
Gerado pela extremidade receptora e indicando à extremidade emissora que o pacote ou grupo de pacotes correspondente foi recebido com sucesso. Normalmente, a confirmação contém os números de sequência dos pacotes recebidos com sucesso. Dependendo do protocolo, as confirmações individuais e de grupo são diferenciadas

Confirmação negativa
Usado pelo destinatário para informar ao remetente que o pacote foi recebido incorretamente. Uma confirmação negativa geralmente inclui o número de sequência do pacote que não foi recebido corretamente

Janela, transportador
Limite o intervalo de números de sequência que podem ser usados ​​para transmitir pacotes. Multicast e handshake podem aumentar significativamente o rendimento do protocolo em comparação com a espera por confirmações. Como veremos, o tamanho da janela pode ser calculado com base nas capacidades de recepção e buffer da extremidade receptora, bem como no nível de carga da rede.

Mais exemplos de uso do Go para networking

В repositórios.

Fonte: habr.com

Adicionar um comentário