На тези, които
накратко
Статията обсъжда основите на надеждното предаване на данни, прилага примери за
Протокол на транспортния слой
Осигурява логическа връзка между процесите на приложения, работещи на различни хостове. От гледна точка на приложението, логическата връзка изглежда като канал, който директно свързва процесите.
Това става чрез разделяне (ако е необходимо) на съобщенията на приложния слой на фрагменти и добавяне на заглавка на транспортния слой към всеки от тях.
След това транспортният слой предава сегмента на мрежовия слой на подателя, където сегментът се капсулира в пакет на мрежовия слой (дейтаграма) и се изпраща. В приемащия край мрежовият слой извлича сегмента на транспортния слой от дейтаграмата и го предава на транспортния слой. След това транспортният слой обработва получения сегмент, така че неговите данни да станат достъпни за приемащото приложение.
Принципи на надеждно предаване на данни
Надеждно предаване на данни по напълно защитен канал
Най-простият случай. Изпращащата страна просто получава данните от горния слой, създава пакет, който ги съдържа, и ги изпраща към канала.
сървър
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)
}
}
Надеждно предаване на данни по канал с възможни грешки
Следващата стъпка е да приемем, че всички предадени пакети са получени в реда, в който са били изпратени, но битовете в тях може да са повредени поради факта, че каналът понякога предава данни с изкривявания.
В този случай се използват следните механизми:
- откриване на грешки;
- обратна връзка;
- препредаване.
Надеждни протоколи за пренос на данни, които имат подобни механизми за многократно повтаряне на предаването, се наричат протоколи за автоматично повторение (ARQ).
Освен това си струва да се обмисли възможността за грешки в разписките, когато получаващата страна няма да получи никаква информация за резултатите от прехвърлянето на последния пакет.
Решението на този проблем, използвано и в TCP, е да се добави ново поле към пакета с данни, съдържащо поредния номер на пакета.
Надеждно предаване на данни по ненадежден канал, подложен на изкривяване и загуба на пакети
Заедно с изкривяването, за съжаление, има загуба на пакети в мрежата.
И за решаването на този проблем са необходими механизми:
- определяне на факта на загуба на пакети;
- повторно доставяне на изгубени пакети до приемащата страна.
Освен това, в допълнение към загубата на пакета, е необходимо да се предвиди възможността за загуба на касовата бележка или, ако нищо не се загуби, доставката й със значително закъснение. Във всички случаи се прави едно и също нещо: пакетът се препредава. За да контролира времето, този механизъм използва таймер за обратно отброяване, който ви позволява да определите края на интервала на изчакване. Така че в опаковката
// 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 са получени успешно.
- интервалът на изчакване е изтекъл. За да се определят фактите за загуби и закъснения на пакети и разписки, протоколът използва таймер. Ако интервалът на изчакване изтече, изпращащата страна изпраща отново всички изпратени непотвърдени пакети.
Избирателно повторение
Когато размерът на прозореца и продуктът на забавяне на пропускателната способност са големи, в тръбопровода може да има голям брой пакети. В такъв случай грешка в единичен пакет може да доведе до повторно предаване на голям брой пакети, повечето от които не са били необходими.
Пример
Връх
сървър
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"))
}
}
клиент
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)
}
}
Продукция
Механизми за осигуряване на надежден трансфер и използване на данни
механизъм
Приложение, коментар
Чекова сума
Използва се за откриване на битови грешки в предаван пакет
часовник
Отброява интервала на изчакване и показва кога е изтекъл. Последното означава, че с голяма степен на вероятност пакетът или неговото получаване се губи по време на предаване. Ако пакетът е доставен със закъснение, но не е изгубен (преждевременно изтичане на интервала на изчакване) или е изгубена разписка, повторното предаване води до дублиран пакет от приемащата страна
Сериен номер
Използва се за последователно номериране на пакети с данни, предавани от подател към получател. Пропуските в поредните номера на получените пакети позволяват на приемника да открие загуба на пакети. Еднаквите поредни номера на пакетите означават, че пакетите са дубликати един на друг
Потвърждаване
Генерира се от получаващата страна и показва на изпращащата страна, че съответният пакет или група от пакети е получена успешно. Обикновено потвърждението съдържа поредните номера на успешно получените пакети. В зависимост от протокола се разграничават индивидуални и групови потвърждения
Отрицателно потвърждение
Използва се от получателя, за да информира подателя, че пакетът е получен неправилно. Отрицателното потвърждение обикновено включва поредния номер на пакета, който не е получен правилно
Прозорец, конвейеризация
Ограничете диапазона от поредни номера, които могат да се използват за предаване на пакети. Груповото предаване и ръкостискането могат значително да увеличат пропускателната способност на протокола в сравнение с чакането на потвърждения. Както ще видим, размерът на прозореца може да бъде изчислен въз основа на възможностите за приемане и буфериране на приемащия край, както и нивото на натоварване на мрежата
Още примери за използване на Go за работа в мрежа
В
Източник: www.habr.com