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

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

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

Кратко

В статията се разгледат основите на надеждната предаване на данни, реализират се примери на 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)
        // предаваме данните на ГОРНОТО ниво: в нашия случай стандартния изход
        println(string(buf[0:n]), " form ", addr.IP.String())
        if err != nil {
            log.Fatal(err)
        }
        // отговор няма, тъй като това е UDP + надежден канал
    }
}

Клиент

пакет main

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

функция main() {
    \/\/ IP-адрес на сървъра и порт
    serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:12000")
    ако err != nil {
        log.Fatal(err)
    }
    \/\/ локален IP-адрес и порт
    localAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
    ако err != nil {
        log.Fatal(err)
    }
    \/\/ установяване на връзка
    conn, err := net.DialUDP("udp", localAddr, serverAddr)
    ако err != nil {
        log.Fatal(err)
    }
    \/\/ отложено затваряне на връзката
    defer conn.Close()

    за {
        \/\/ получаване на данни от ВЕРХНИЯ НИВО
        fmt.Print("Въведете строчно изречение > ")
        var msg string
        _, err := fmt.Scanf("%s", &msg)
        ако err != nil {
            log.Fatal(err)
        }
        \/\/ предаване на поток от байтове, а не стринга
        buf := []byte(msg)
        \/\/ запис (предаване) във връзката
        _, err = conn.Write(buf)
        ако 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"
)

функция main() {
    // установка соединения
    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