
Těm, kdo rozumí sítím a protokolům, je tento text věnován.
Stručně
Článek se zabývá základy spolehlivého přenosu dat a uvádí příklady na , včetně UDP a TCP. Na motivy , , a knihy "Počítačové sítě. Sestupný přístup", protože všichni diskutují jen o Tanenbaumovi a Oliferové.
Protokol transportní úrovně
zajišťuje logické spojení mezi aplikačními procesy, které běží na různých hostitelích. Z pohledu aplikací vypadá logické spojení jako kanál, který přímo spojuje procesy.

jsou podporovány koncovými systémy, ale ne síťovými směrovači (kromě - ). Na straně odesílatele transportní úroveň převádí data aplikační úrovně, která přijímá od odesílajícího aplikačního procesu, na pakety transportní úrovně zvané segmenty.

To se provádí rozdělením (pokud je to nutné) zpráv aplikační úrovně na fragmenty a přidáním hlavičky transportní úrovně ke každému z nich.

Dále transportní úroveň předává segment síťové úrovni odesílatele, kde je segment zapouzdřen do paketu síťové úrovně (datagramu) a odeslán. Na přijímající straně síťová úroveň extrahuje segment transportní úrovně z datagramu a předává jej zpět transportní úrovni. Následně transportní úroveň zpracovává přijatý segment tak, aby jeho data byla přístupná cílové aplikaci.

Principy spolehlivého přenosu dat
Spolehlivý přenos dat přes zcela spolehlivý kanál
Nejjednodušší případ. Odesílající strana jednoduše přijímá data z vyšší úrovně, vytváří balíček, který je obsahuje, a odesílá jej do kanálu.
Server
package main
import (
"log"
"net"
)
func main() {
// IP adresa serveru a port
serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:12000")
if err != nil {
log.Fatal(err)
}
// vytvoření socketu na portu
serverConn, err := net.ListenUDP("udp", serverAddr)
if err != nil {
log.Fatal(err)
}
// odložené uzavření spojení
defer serverConn.Close()
// vytvoření bufferu pro data
buf := make([]byte, 1024)
// čekání na spojení
for {
// čtení požadavku
n, addr, err := serverConn.ReadFromUDP(buf)
// předání dat na VĚTŠÍ úroveň: v našem případě stdout
println(string(buf[0:n]), " form ", addr.IP.String())
if err != nil {
log.Fatal(err)
}
// odpověď není, protože jde o UDP + spolehlivý kanál
}
}Zákazník
package main
import (
"fmt"
"log"
"net"
"time"
)
func main() {
// IP adresa serveru a port
serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:12000")
if err != nil {
log.Fatal(err)
}
// lokální IP adresa a port
localAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
if err != nil {
log.Fatal(err)
}
// nastavení spojení
conn, err := net.DialUDP("udp", localAddr, serverAddr)
if err != nil {
log.Fatal(err)
}
// odložené uzavření spojení
defer conn.Close()
for {
// příjem dat z VĚTŠÍ úrovně
fmt.Print("Zadejte řetězec > ")
var msg string
_, err := fmt.Scanf("%s", &msg)
if err != nil {
log.Fatal(err)
}
// přenáší se proud bytů, nikoliv řetězec
buf := []byte(msg)
// zápis (přenášení) do spojení
_, err = conn.Write(buf)
if err != nil {
log.Fatal(err)
}
// 1 vteřina
time.Sleep(time.Second * 1)
}
}Spolehlivý přenos dat přes kanál s možnými chybami
Dalším krokem je předpoklad, že všechny přenášené pakety byly přijaty v pořadí, v jakém byly odeslány, ale bity v nich mohou být poškozeny, protože kanál občas přenáší data s deformacemi.

V takovém případě se aplikují mechanismy:
- detekce chyb;
- zpětná vazba;
- opakované přenosy.
Protokoly pro spolehlivý přenos dat, které mají podobné mechanismy opakovaného přenosu, se nazývají protokoly s automatickou žádostí o opakovaný přenos (Automatic Repeat reQuest, ARQ).
Dále je dobré zohlednit možnost chyb i v potvrzeních, kdy přijímající strana nedostane žádné informace o výsledcích přenosu posledního paketu.
Řešení tohoto úkolu, používané včetně TCP, spočívá v přidání nového pole pro sekvenční číslo paketu do datového paketu.

Spolehlivý přenos dat přes nespolehlivý kanál, který může způsobit ztrátu a zkreslení paketů
Bohužel, při zkreslení dochází také ke ztrátě paketů.
A pro vyřešení tohoto problému jsou zapotřebí mechanismy:
- určení ztráty paketu;
- opětovné odeslání ztracených paketů přijímající straně.
Dále je třeba kromě ztráty paketu zohlednit možnost ztráty potvrzení nebo, pokud nebylo nic ztraceno, jeho doručení se značným zpožděním. V každém případě se provádí to samé: opětovné odeslání paketu. Pro sledování času se v tomto mechanismu používá odpočítávací časovač, který umožňuje určit konec čekacího intervalu. Tak v paketu parametr TCPKeepAlive je ve výchozím nastavení nastaven na 15 sekund:
// defaultTCPKeepAlive is a default constant value for TCPKeepAlive times
// See golang.org/issue/31510
const (
defaultTCPKeepAlive = 15 * time.Second
)Odesílající straně je třeba spustit časovač pokaždé při odesílání paketu (jak při prvním, tak při opakovaném odeslání), zpracovávat přerušení od časovače a zastavit jej.
Tak jsme se seznámili s klíčovými pojmy protokolů spolehlivého přenosu dat:
- kontrolními součty;
- pořadovými čísly paketů;
- časovači;
- kladnými a zápornými potvrzeními.
Ale to není všechno!
Protokol spolehlivého přenosu dat s pipeliningem
V variantě, kterou jsme již probrali, je protokol spolehlivé dopravy velmi neefektivní. Při zvyšující se RTT začíná 'brzdit' přenos zajišťovaný komunikačním kanálem. Pro zvýšení jeho efektivity, lepšího využití šířky pásma komunikačního kanálu se používá pipelining.

Použití pipeliningu vede k:
- zvýšení rozsahu pořadových čísel, protože všechny odesílané pakety (kromě opakovaných) musí být jednoznačně identifikovatelné;
- potřebě větších vyrovnávacích pamětí na odesílající a přijímající straně.
Rozsah pořadových čísel a požadavky na velikosti vyrovnávacích pamětí závisí na akcích, které protokol podniká na základě zkreslení, ztráty a zpoždění paketu. V případě pipeliningu existují dvě metody opravy chyb:
- návrat o N paketů zpět;
- výběrové opakování.
Návrat o N paketů zpět — protokol klouzavého okna

odesílatel musí sledovat tři typy událostí:
- Volání protokolem vyšší úrovně. Když je funkce odesílání dat volána „zhora“, odesílající strana nejprve zkontroluje stupeň zaplnění okna (to znamená přítomnost N odeslaných zpráv čekajících na potvrzení). Pokud se okno ukáže jako nezaplněné, je vytvořen a odeslán nový paket a hodnoty proměnných jsou aktualizovány. V opačném případě odesílající strana vrací data vyšší úrovni, což je implicitní pokyn, že je okno zaplněno. Obvykle se vyšší úroveň pokouší znovu odeslat data po nějakém čase. V reálné aplikaci by odesílatel pravděpodobně buď data vyrovnal (místo okamžitého odesílání), nebo měl mechanismus synchronizace (například semafor nebo příznak), který by umožnil vyšší úrovni volat funkci odesílání dat pouze při nezaplněném okně.
- Potvrzení přijetí. V protokolu je pro paket s pořadovým číslem N vydávána obecná potvrzení, která ukazují, že všechny pakety s pořadovými čísly předcházejícími N byly úspěšně přijaty.
- Vypršení časového intervalu. Pro stanovení ztrát a zpoždění paketů a potvrzení protokol používá časovač. Pokud časový interval vyprší, odesílající strana znovu odešle všechny odeslané nepotvrzené pakety.
Výběrové opakování
Když je velikost okna a produkt propustnosti s prodlevou vysoký, může být v potrubí velké množství paketů. V takovém případě chyba jednotlivého paketu může způsobit znovuodeslání velkého množství paketů, z nichž většina nebyla potřebná.
Příklad
Nejlepší praktiky jsou shromážděny v praktické implementaci . A pokud někdo ví, jak lépe — .
Server
package main
import (
"bufio"
"fmt"
"log"
"net"
"strings"
)
func main() {
// vytváříme socket s portem
ln, err := net.Listen("tcp", ":8081")
if err != nil {
log.Fatalln(err)
}
// čekání na volání
conn, _ := ln.Accept()
for {
// čtení dat
msg, err := bufio.NewReader(conn).ReadString('n')
if err != nil {
log.Fatalln(err)
}
// výpis zprávy na stdout
fmt.Print("Přijata zpráva:", string(msg))
// převod řetězce na velká písmena
newMsg := strings.ToUpper(msg)
// odesílání dat
conn.Write([]byte(newMsg + "n"))
}
}Zákazník
balíček hlavní
import (
"bufio"
"fmt"
"log"
"net"
"os"
)
funkce main() {
// nastavení spojení
conn, err := net.Dial("tcp", "127.0.0.1:8081")
if err != nil {
log.Fatalln(err)
}
for {
// čtení dat z stdin
reader := bufio.NewReader(os.Stdin)
fmt.Print("Text k odeslání: ")
// po řádcích
text, err := reader.ReadString('n')
if err != nil {
log.Fatalln(err)
}
// odeslání
fmt.Fprintf(conn, text+"n")
// příjem
msg, err := bufio.NewReader(conn).ReadString('n')
if err != nil {
log.Fatalln(err)
}
// výstup přijaté odpovědi
fmt.Print("Zpráva ze serveru: " + msg)
}
}Výstup
Mechanismy zajišťující spolehlivý přenos dat a jejich využití
Mechanismus
Aplikace, komentář
Kontrolní součet
Používá se k odhalení bitových chyb v přeneseném balíčku
Časovač
Odpočet čekacího intervalu a indikace jeho vypršení. To poslední znamená, že s vysokou pravděpodobností byl balíček nebo jeho potvrzení ztraceno během přenosu. Pokud je balíček dodán se zpožděním, ale není ztracen (předčasné vypršení čekacího intervalu), nebo dojde ke ztrátě potvrzení, opakovaný přenos vede k duplikaci balíčku na přijímající straně
Pořadové číslo
Používá se pro pořadovou numeraci datových balíčků přenášených od odesílatele k příjemci. Přerušení v pořadových číslech přijatých balíčků umožňují příjemci odhalit ztrátu balíčku. Stejná pořadová čísla balíčků znamenají, že balíčky se navzájem duplikují
Potvrzení
Generováno přijímající stranou a signalizuje odesílající straně, že příslušný balíček nebo skupina balíčků byla úspěšně přijata. Obvykle potvrzení obsahuje pořadová čísla úspěšně přijatých balíčků. V závislosti na protokolu se rozlišují individuální a skupinová potvrzení
Negativní potvrzení
Používá se příjemcem k oznámení odesílateli, že balíček byl přijat nesprávně. Negativní potvrzení obvykle obsahuje pořadové číslo balíčku, který nebyl správně přijat
Okno, konvejování
Omezuje rozsah pořadových čísel, která mohou být použita pro přenos paketů. Skupinový přenos a handshake výrazně zvyšují propustnost protokolů v porovnání s režimem čekání na potvrzení. Jak uvidíme, velikost okna může být vypočítána na základě schopností příjmu a vyrovnávací paměti přijímající strany, stejně jako úrovně zatížení sítě.
Další příklady použití Go pro práci se sítí
V .
Zdroj: habr.com
