Основи на надеждната предаване на данни

Основи на надеждната предаване на данни

На тези, които се стреми искат да разберат мрежите и протоколите, се посвещава.

Накратко

В статията се разглеждат основите на надеждната предаване на данни, реализират се примери на Go, включително UDP и TCP. По мотиви на раз, два, три и книгата "Компютърни мрежи. Понизходящ подход", тъй като всички обсъждат само Таненбаум и Олифери.

Протокол на транспортния слой

осигурява логическа връзка между приложни процеси, изпълняващи се на различни хостове. Логическата връзка от гледна точка на приложенията изглежда като канал, който директно свързва процесите.

Основи на надеждната предаване на данни

Протоколите на транспортния слой се поддържат от крайните системи, но не и от мрежовите маршрутизатори (освен — DPI). От страна на изпращача транспортният слой преобразува данните на приложния слой, които получава от предаващия приложен процес, в пакети на транспортния слой, наречени сегменти.

Основи на надеждната предаване на данни

Това се извършва чрез разделяне (при необходимост) на съобщенията на приложния слой на фрагменти и добавяне на заглавие на транспортния слой към всеки от тях.

Основи на надеждната предаване на данни

След това транспортният слой предава сегмента на мрежовия слой на изпращача, където сегментът се инкапсулира в пакет на мрежовия слой (дейтаграма) и се изпраща. На приемащата страна мрежовият слой извлича сегмента на транспортния слой от дейтаграмата и го предава на транспортния слой. След това транспортният слой обработва получения сегмент по такъв начин, че данните му да станат достъпни за приложението получател.

Основи на надеждната предаване на данни

Принципи на надеждната предаване на данни

Надеждното предаване на данни по напълно надежден канал

Най-простият случай. Изпращащата страна просто приема данните от горния слой, създава пакет, който съдържа данните, и го изпраща в канала.

Сървър

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

Клиент

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

Надеждна предаване на данни през канал с възможни грешки

Следващият етап е предположението, че всички предадени пакети са получени в реда, в който са били изпратени, но битовете в тях могат да бъдат повредени, поради факта, че каналът понякога предава данни с изкривявания.

Основи на надеждната предаване на данни

В такъв случай се прилагат механизми:

  • откриване на грешки;
  • обратна връзка;
  • повторно предаване.

Протоколите за надеждна предаване на данни, притежаващи подобни механизми за многократно повторно предаване, се наричат протоколи с автоматичен заявка за повторно предаване (Automatic Repeat reQuest, ARQ).
Освен това, трябва да се предвиди възможността за грешки и в потвържденията, когато приемащата страна не получава никаква информация за резултатите от предаването на последния пакет.
Решението на тази задача, използвано също в TCP, се състои в добавянето към пакета данни на ново поле, съдържащо пореден номер на пакета.

Основи на надеждната предаване на данни

Надеждна предаване на данни по ненадежден канал, допускащ и изкривяване и загуба на пакети

Паралелно с изкривяванията, за съжаление, в мрежата присъства загуба на пакети.
И за решаването на този проблем са необходими механизми:

  • определяне на факта за загуба на пакети;
  • повторно предаване на загубените пакети на приемащата страна.

Допълнително, освен загуба на пакет, е необходимо да се предвиди възможността за загуба на потвърждение или, ако нищо не е загубено, неговото предаване с значително закъснение. Във всички случаи се извършва едно и също: повторно предаване на пакета. За контролиране на времето в този механизъм се използва таймер за обратно отброяване, който позволява да се определи края на времевия интервал на очакване. Така в пакета net параметърът TCPKeepAlive е зададен на 15 секунди по подразбиране:

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

Изпращащата страна трябва да стартира таймера всеки път при предаване на пакета (както при първия, така и при повторното), да обработва прекъсвания от таймера и да го спира.

И така, запознахме се с основните понятия на протоколите за надеждно предаване на данни:

  • контролни суми;
  • порядкови номера на пакетите;
  • таймери;
  • положителни и отрицателни потвърждения.

Но и това не е всичко!

Протокол за надеждно предаване на данни с конвейеризация

В разгледания вариант, протоколът за надеждно предаване е много неефективен. Той започва да "забавя" предаването, осигурено от канала за комуникация, при увеличаване на RTT. За повишаване на ефективността му и по-добре използване на пропускателната способност на канала за комуникация се прилага конвейеризация.

Основи на надеждната предаване на данни

Приложението на конвейеризацията води до:

  • увеличаване на диапазона на порядковите номера, тъй като всички изпратени пакети (с изключение на повторните предавания) трябва да бъдат единствено идентифицируеми;
  • необходимост от увеличаване на буферите на изпращащата и приемащата страна.

Диапазонът на порядковите номера и изискванията за размери на буферите зависят от действията, предприети от протокола в отговор на изкривяване, загуба и закъснение на пакета. В случай на конвейеризация съществуват два метода за корекция на грешки:

  • върнат назад N пакета;
  • изборно повторение.

Връщане на N пакета назад — протокол на плъзгащото се окно

Основи на надеждната предаване на данни

Изпращачът трябва да поддържа три типа събития:

  • виклик протоколом на по-високому рівні. Коли «зверху» викликається функція надсилання даних, сторона, що передає, спочатку перевіряє ступінь заповнення вікна (тобто наявність N надісланих повідомлень, що чекають підтвердження). Якщо вікно виявляється незаповненим, формується новий пакет та передається, а значення змінних оновлюються. Інакше сторона, що передає, повертає дані на верхній рівень, і це є неявною вказівкою на те, що вікно заповнене. Зазвичай верхній рівень намагається повторно передати дані через деякий час. У справжньому застосуванні відправник, швидше за все, або буферизував би дані (замість негайної відправки), або мав би механізм синхронізації (наприклад, семафор або прапорець), який дозволяв би вищому рівню викликати функцію надсилання даних лише за незаповненого вікна.
  • отримання підтвердження. У протоколі для пакета з порядковим номером N видається загальна квитанція, що вказує на те, що всі пакети з порядковими номерами, що передують N, успішно прийняті.
  • вичерпання інтервалу очікування. Для визначення фактів втрат і затримок пакетів та квитанцій протокол використовує таймер. Якщо інтервал очікування завершується, сторона, що передає, повторно надсилає всі надіслані непідтверджені пакети.

Вибіркове повторення

Коли розмір вікна та добуток пропускної спроможності на затримку розповсюдження великі, в конвеєрі може знаходитися велика кількість пакетів. У такому випадку помилка окремого пакета може спричинити повторну передачу великої кількості пакетів, більшість з яких не були потрібні.

Пример

Найкращі теоретичні практики зібрані в практичній реалізації TCP. А якщо хтось знає, як краще — добре дошли.

Сървър

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

Клиент

пакет основной

импорт (
    "bufio"
    "fmt"
    "log"
    "net"
    "os"
)

функция основная() {
    \// установка соединения
    conn, err := net.Dial("tcp", "127.0.0.1:8081")
    если err != nil {
        log.Fatalln(err)
    }

    для {
        \// считывание данных с stdin
        reader := bufio.NewReader(os.Stdin)
        fmt.Print("Текст для отправки: ")
        \// построчно
        text, err := reader.ReadString('n')
        если err != nil {
            log.Fatalln(err)
        }
        \// отправка
        fmt.Fprintf(conn, text+"n")
        \// прием
        msg, err := bufio.NewReader(conn).ReadString('n')
        если err != nil {
            log.Fatalln(err)
        }
        \// вывод полученного ответа
        fmt.Print("Сообщение от сервера: " + msg)
    }
}

Извод

Механизми, които осигуряват надеждна предаване на данни и тяхното използване

Механизмът
Приложение, коментар

Контролна сума
Използва се за откриване на битови грешки в предадения пакет

Таймер
Отсчет на интервала на изчакване и указание за неговото изтичане. Последното означава, че с висока вероятност пакетът или неговото потвърждение са загубени по време на предаването. В случай, че пакетът се доставя с закъснение, но не се губи (предварително изтичане на интервала на изчакване), или се случва загуба на потвърждение, повторната предаване води до дублиране на пакета на страната получател.

Поредност
Използва се за последователна нумерация на предавани данни от изпращача към получателя. Пропуските в порядковите номера на получените пакети позволяват на получателя да открие загубата на пакет. Идентичните порядкови номера на пакети означават, че пакетите дублират един друг.

Потвърждение
Генерира се от страната на получателя и указва на предаващата страна, че съответният пакет или група от пакети са успешно приети. Обикновено потвърждението съдържа порядковите номера на успешно приетите пакети. В зависимост от протокола се различават индивидуални и групови потвърждения.

Негативно потвърждение
Използва се от получателя, за да съобщи на изпращача, че пакетът е бил получен неправилно. Негативното потвърждение обикновено включва порядковия номер на пакета, който не е бил получен коректно.

Прозорец, конвейеризация
Ограничават диапазона на редовите номера, които могат да се използват за предаване на пакети. Груповото предаване и ръкостискането значително увеличават пропускната способност на протоколите в сравнение с режима на изчакване за потвърждение. Както ще видим, размерът на прозореца може да бъде изчислен на база възможностите за приемане и буфериране на получателя, а също и нивото на натоварване на мрежата.

Други примери за използване на Go за работа с мрежата.

В репозитории.

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster