Till de som
I korthet
Artikeln diskuterar grunderna för tillförlitlig dataöverföring, implementerar exempel på
Transportlagerprotokoll
Ger en logisk koppling mellan applikationsprocesser som körs på olika värdar. Ur ett applikationsperspektiv ser en logisk koppling ut som en kanal som direkt kopplar samman processer.
Detta görs genom att dela upp (om nödvändigt) applikationslagermeddelandena i fragment och lägga till ett transportlagerhuvud till vart och ett av dem.
Transportlagret skickar sedan segmentet till avsändarens nätverkslager, där segmentet kapslas in i ett nätverkslagerpaket (datagram) och skickas. I den mottagande änden extraherar nätverkslagret transportlagersegmentet från datagrammet och skickar det upp till transportlagret. Därefter bearbetar transportskiktet det mottagna segmentet så att dess data blir tillgänglig för den mottagande applikationen.
Principer för tillförlitlig dataöverföring
Tillförlitlig dataöverföring över en helt säker kanal
Det enklaste fallet. Den sändande sidan tar helt enkelt emot data från det övre lagret, skapar ett paket som innehåller det och skickar det till kanalen.
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 + надежный канал
}
}
kund
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)
}
}
Tillförlitlig dataöverföring över en kanal med möjliga fel
Nästa steg är att anta att alla överförda paket tas emot i den ordning som de skickades, men bitarna i dem kan vara korrupta på grund av att kanalen ibland sänder data med distorsion.
I det här fallet används följande mekanismer:
- feldetektering;
- respons;
- återsändning.
Pålitliga dataöverföringsprotokoll som har liknande mekanismer för att upprepa överföringen flera gånger kallas ARQ-protokoll (Automatic Repeat ReQuest).
Dessutom är det värt att överväga möjligheten av fel i kvitton, när den mottagande parten inte kommer att få någon information om resultatet av överföringen av det sista paketet.
Lösningen på detta problem, som också används i TCP, är att lägga till ett nytt fält till datapaketet som innehåller sekvensnumret för paketet.
Tillförlitlig dataöverföring över en opålitlig kanal utsatt för paketförvrängning och förlust
Tillsammans med distorsion finns det tyvärr paketförluster i nätverket.
Och för att lösa detta problem krävs mekanismer:
- fastställande av paketförlust;
- återleverans av förlorade paket till den mottagande parten.
Dessutom, utöver förlusten av paketet, är det nödvändigt att sörja för möjligheten av förlust av kvittot eller, om inget går förlorat, leveransen med en betydande försening. I alla fall görs samma sak: paketet återsänds. För att kontrollera tiden använder den här mekanismen en nedräkningstimer, som låter dig bestämma slutet av vänteintervallet. Alltså i paketet
// defaultTCPKeepAlive is a default constant value for TCPKeepAlive times
// See golang.org/issue/31510
const (
defaultTCPKeepAlive = 15 * time.Second
)
Den sändande sidan måste starta en timer varje gång ett paket sänds (både första och andra gången), hantera avbrott från timern och stoppa den.
Så vi har blivit bekanta med nyckelbegreppen för tillförlitliga dataöverföringsprotokoll:
- kontrollsummor;
- sekvensnummer för paket;
- timers;
- positiva och negativa kvitton.
Men det är inte allt!
Pålitligt dataöverföringsprotokoll med pipelining
I varianten som vi redan har övervägt är det tillförlitliga leveransprotokollet mycket ineffektivt. Den börjar "bromsa" överföringen som tillhandahålls av kommunikationskanalen när RTT ökar. För att öka dess effektivitet och bättre utnyttja kommunikationskanalkapaciteten används pipelining.
Användningen av pipelining leder till:
- öka intervallet av sekvensnummer, eftersom alla skickade paket (förutom återsändningar) måste identifieras unikt;
- behovet av att öka buffertarna på sändnings- och mottagningssidan.
Sekvensnummerintervallet och kraven på buffertstorlek beror på de åtgärder som protokollet vidtar som svar på paketkorruption, förlust och fördröjning. När det gäller pipelining finns det två metoder för att korrigera fel:
- returnera N paket tillbaka;
- selektiv upprepning.
Gå tillbaka N paket - glidande fönsterprotokoll
Avsändaren måste stödja tre typer av händelser:
- samtal med ett protokoll på högre nivå. När datasändningsfunktionen kallas "uppifrån", kontrollerar sändningssidan först fyllnadsgraden av fönstret (det vill säga närvaron av N skickade meddelanden som väntar på mottagande av kvitton). Om fönstret är tomt genereras och sänds ett nytt paket och variabelvärdena uppdateras. Annars returnerar sändningssidan data till det övre lagret, och detta är en implicit indikation på att fönstret är fullt. Vanligtvis kommer det övre lagret att försöka överföra data igen efter en tid. I en riktig applikation skulle avsändaren sannolikt antingen buffra data (istället för att skicka den omedelbart) eller ha en synkroniseringsmekanism (som en semafor eller flagga) som skulle tillåta det övre lagret att anropa sändningsfunktionen endast när fönstret är tomt .
- får bekräftelse. I protokollet, för ett paket med sekvensnummer N, utfärdas en allmän bekräftelse som indikerar att alla paket med sekvensnummer före N har tagits emot framgångsrikt.
- vänteintervallet har löpt ut. För att fastställa fakta om förluster och förseningar av paket och kvitton, använder protokollet en timer. Om timeout-intervallet löper ut, skickar sändningssidan om alla skickade okvitterade paket.
Selektiv upprepning
När fönsterstorleken och genomströmnings-utbredningsfördröjningsprodukten är stora, kan ett stort antal paket vara i pipelinen. I ett sådant fall kan ett enda paketfel orsaka att ett stort antal paket återsänds, varav de flesta inte krävdes.
Exempel
Topp
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"))
}
}
kund
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)
}
}
Utgång
Mekanismer för att säkerställa tillförlitlig dataöverföring och användning
mekanism
Ansökan, kommentar
Kontrollera summan
Används för att upptäcka bitfel i ett överfört paket
timer
Räknar ner timeout-intervallet och indikerar när det har gått ut. Det senare innebär att med en hög grad av sannolikhet går paketet eller dess kvitto förlorat under överföringen. Om ett paket levereras med en fördröjning, men inte går förlorat (för tidigt utgång av timeoutintervallet), eller ett kvitto går förlorat, leder omsändning till ett duplicerat paket på den mottagande sidan
Serienummer
Används för sekventiell numrering av datapaket som överförs från avsändare till mottagare. Luckor i sekvensnumren för mottagna paket tillåter mottagaren att upptäcka paketförlust. Samma paketsekvensnummer betyder att paketen är dubbletter av varandra
Bekräftelse
Genereras av den mottagande änden och indikerar för den sändande änden att motsvarande paket eller grupp av paket har tagits emot framgångsrikt. Typiskt innehåller bekräftelsen sekvensnumren för framgångsrikt mottagna paket. Beroende på protokollet särskiljs individuella och gruppbekräftelser
Negativ bekräftelse
Används av mottagaren för att informera avsändaren om att paketet mottagits felaktigt. En negativ bekräftelse inkluderar vanligtvis sekvensnumret för paketet som inte togs emot korrekt
Fönster, transportör
Begränsa intervallet av sekvensnummer som kan användas för att överföra paket. Multicast och handskakning kan avsevärt öka protokollgenomströmningen jämfört med att vänta på bekräftelser. Som vi kommer att se kan fönsterstorleken beräknas baserat på mottagnings- och buffringskapaciteten hos den mottagande änden, såväl som nätverkets belastningsnivå
Fler exempel på användning av Go för nätverk
В
Källa: will.com