Podstawy niezawodnego przesyłania danych

Podstawy niezawodnego przesyłania danych

Do tych którzy szuka Dedykowany zrozumieniu sieci i protokołów.

Krótko

W artykule omówiono podstawy niezawodnej transmisji danych, zaimplementowano przykłady nt Go, w tym UDP i TCP. Oparte na czas, два, trzy oraz książki „Sieci komputerowe. Podejście odgórne”, w przeciwnym razie wszyscy dyskutują tylko o Tannenbaumie i Oliferowie.

Protokół warstwy transportowej

Zapewnia logiczne połączenie pomiędzy procesami aplikacji działającymi na różnych hostach. Z punktu widzenia aplikacji połączenie logiczne wygląda jak kanał bezpośrednio łączący procesy.

Podstawy niezawodnego przesyłania danych

Protokoły warstwy transportowej są obsługiwane przez systemy końcowe, ale nie przez routery sieciowe (z wyjątkiem - DPI). Po stronie nadawcy warstwa transportowa konwertuje dane warstwy aplikacji otrzymane z procesu aplikacji wysyłającej na pakiety warstwy transportowej zwane segmentami.

Podstawy niezawodnego przesyłania danych

Odbywa się to poprzez podzielenie (jeśli to konieczne) komunikatów warstwy aplikacji na fragmenty i dodanie do każdego z nich nagłówka warstwy transportowej.

Podstawy niezawodnego przesyłania danych

Następnie warstwa transportowa przekazuje segment do warstwy sieci nadawcy, gdzie segment jest hermetyzowany w pakiecie warstwy sieciowej (datagram) i wysyłany. Po stronie odbiorczej warstwa sieciowa wyodrębnia segment warstwy transportowej z datagramu i przekazuje go do warstwy transportowej. Następnie warstwa transportowa przetwarza odebrany segment tak, aby jego dane stały się dostępne dla aplikacji odbierającej.

Podstawy niezawodnego przesyłania danych

Zasady niezawodnej transmisji danych

Niezawodna transmisja danych całkowicie bezpiecznym kanałem

Najprostszy przypadek. Strona wysyłająca po prostu odbiera dane z wyższej warstwy, tworzy zawierający je pakiet i wysyła je do kanału.

Server

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 + надежный канал
    }
}

Klient

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

Niezawodna transmisja danych kanałem z możliwymi błędami

Następnym krokiem jest założenie, że wszystkie przesyłane pakiety są odbierane w kolejności, w jakiej zostały wysłane, jednak zawarte w nich bity mogą być uszkodzone ze względu na fakt, że kanał czasami przesyła dane ze zniekształceniami.

Podstawy niezawodnego przesyłania danych

W takim przypadku stosowane są następujące mechanizmy:

  • wykrywanie błędów;
  • informacja zwrotna;
  • retransmisja.

Niezawodne protokoły przesyłania danych, które mają podobne mechanizmy wielokrotnego powtarzania transmisji, nazywane są protokołami Automatic Repeat ReQuest (ARQ).
Dodatkowo warto uwzględnić możliwość wystąpienia błędów w paragonach, gdy odbiorca nie otrzyma żadnej informacji o wynikach przekazania ostatniego pakietu.
Rozwiązanie tego problemu, stosowane również w protokole TCP, polega na dodaniu do pakietu danych nowego pola zawierającego numer sekwencyjny pakietu.

Podstawy niezawodnego przesyłania danych

Niezawodna transmisja danych przez zawodny kanał narażony na zniekształcenia i utratę pakietów

Niestety wraz z zakłóceniami dochodzi do utraty pakietów w sieci.
Aby rozwiązać ten problem, wymagane są mechanizmy:

  • ustalenie faktu utraty pakietów;
  • ponowne dostarczenie utraconych pakietów do strony odbierającej.

Dodatkowo oprócz utraty przesyłki należy uwzględnić możliwość zagubienia paragonu lub, jeśli nic nie zginęło, jej doręczenia ze znacznym opóźnieniem. We wszystkich przypadkach robi się to samo: pakiet jest retransmitowany. Aby kontrolować czas, mechanizm ten wykorzystuje licznik czasu, który pozwala określić koniec okresu oczekiwania. Tak w pakiecie netto Domyślnie protokół TCPKeepAlive jest ustawiony na 15 sekund:

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

Strona wysyłająca musi uruchamiać licznik czasu za każdym razem, gdy pakiet jest przesyłany (zarówno za pierwszym, jak i za drugim razem), obsługiwać przerwania ze strony licznika czasu i go zatrzymywać.

Zapoznaliśmy się więc z kluczowymi koncepcjami niezawodnych protokołów przesyłania danych:

  • sumy kontrolne;
  • numery porządkowe pakietów;
  • timery;
  • wpływy pozytywne i negatywne.

Ale to nie wszystko!

Niezawodny protokół przesyłania danych z potokowaniem

W wariancie, który już rozważaliśmy, niezawodny protokół dostawy jest bardzo nieefektywny. Zaczyna „spowalniać” transmisję zapewnianą przez kanał komunikacyjny wraz ze wzrostem RTT. Aby zwiększyć jego efektywność i lepiej wykorzystać przepustowość kanału komunikacyjnego, stosuje się potokowanie.

Podstawy niezawodnego przesyłania danych

Zastosowanie rurociągów prowadzi do:

  • zwiększenie zakresu numerów sekwencyjnych, ponieważ wszystkie wysyłane pakiety (z wyjątkiem retransmisji) muszą być jednoznacznie identyfikowane;
  • konieczność zwiększenia buforów po stronie nadawczej i odbiorczej.

Zakres numerów sekwencyjnych i wymagania dotyczące rozmiaru bufora zależą od działań podejmowanych przez protokół w odpowiedzi na uszkodzenie, utratę i opóźnienie pakietu. W przypadku potokowania istnieją dwie metody poprawiania błędów:

  • zwróć N pakietów;
  • selektywne powtarzanie.

Wracanie N pakietów - protokół przesuwanego okna

Podstawy niezawodnego przesyłania danych

Nadawca musi obsługiwać trzy typy zdarzeń:

  • połączenie przez protokół wyższego poziomu. Gdy funkcja wysyłania danych zostanie wywołana „z góry”, strona wysyłająca w pierwszej kolejności sprawdza stopień wypełnienia okna (czyli obecność N wysłanych komunikatów oczekujących na otrzymanie potwierdzeń). Jeśli okno jest puste, generowany i przesyłany jest nowy pakiet, a wartości zmiennych są aktualizowane. W przeciwnym razie strona wysyłająca zwraca dane do wyższej warstwy, co jest dorozumianą wskazówką, że okno jest pełne. Zwykle po pewnym czasie górna warstwa będzie próbowała ponownie przesłać dane. W prawdziwej aplikacji nadawca prawdopodobnie albo buforowałby dane (zamiast natychmiast je wysyłać), albo posiadał mechanizm synchronizacji (taki jak semafor lub flaga), który pozwalałby górnej warstwie wywoływać funkcję wysyłania tylko wtedy, gdy okno jest puste .
  • otrzymania potwierdzenia. W protokole dla pakietu o numerze sekwencyjnym N wydawane jest ogólne potwierdzenie wskazujące, że wszystkie pakiety o numerach sekwencyjnych poprzedzających N zostały pomyślnie odebrane.
  • upłynął okres oczekiwania. Aby określić fakty dotyczące strat i opóźnień pakietów i potwierdzeń, protokół wykorzystuje timer. Jeśli upłynie limit czasu, strona wysyłająca ponownie wysyła wszystkie wysłane niepotwierdzone pakiety.

Wybiórcze powtórzenie

Gdy rozmiar okna i produkt opóźnienia propagacji przepustowości są duże, w potoku może znajdować się duża liczba pakietów. W takim przypadku błąd pojedynczego pakietu może spowodować retransmisję dużej liczby pakietów, z których większość nie była wymagana.

Przykład

Najlepsze teoretyczny praktyki są gromadzone w praktycznym wdrażaniu TCP. A jeśli ktoś wie jak najlepiej - powitanie.

Server

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

Klient

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

Wniosek

Mechanizmy zapewniające niezawodny transfer i wykorzystanie danych

mechanizm
Aplikacja, komentarz

Sprawdź sumę
Służy do wykrywania błędów bitowych w przesyłanym pakiecie

Regulator czasowy
Odlicza limit czasu i wskazuje, kiedy upłynął. To drugie oznacza, że ​​z dużym prawdopodobieństwem pakiet lub jego odbiór zostanie utracony podczas transmisji. Jeżeli pakiet został dostarczony z opóźnieniem, ale nie został utracony (przedwczesny upłynięcie limitu czasu) lub utracono potwierdzenie, retransmisja prowadzi do zduplikowania pakietu po stronie odbierającej

Numer seryjny
Służy do sekwencyjnego numerowania pakietów danych przesyłanych od nadawcy do odbiorcy. Luki w numerach sekwencji odebranych pakietów umożliwiają odbiornikowi wykrycie utraty pakietów. Te same numery sekwencji pakietów oznaczają, że pakiety są duplikatami siebie

Potwierdzenie
Generowane przez stronę odbiorczą i wskazujące stronie wysyłającej, że odpowiedni pakiet lub grupa pakietów została pomyślnie odebrana. Zazwyczaj potwierdzenie zawiera numery kolejne pomyślnie odebranych pakietów. W zależności od protokołu rozróżnia się potwierdzenia indywidualne i grupowe

Negatywne potwierdzenie
Używane przez odbiorcę w celu poinformowania nadawcy, że pakiet został odebrany nieprawidłowo. Negatywne potwierdzenie zwykle zawiera numer kolejny pakietu, który nie został poprawnie odebrany

Okno, przenośnik
Ogranicz zakres numerów sekwencyjnych, których można używać do przesyłania pakietów. Multicast i uzgadnianie mogą znacząco zwiększyć przepustowość protokołu w porównaniu z oczekiwaniem na potwierdzenia. Jak zobaczymy, rozmiar okna można obliczyć na podstawie możliwości odbioru i buforowania strony odbiorczej, a także poziomu obciążenia sieci

Więcej przykładów wykorzystania Go do networkingu

В repozytoria.

Źródło: www.habr.com

Dodaj komentarz