
Til dem, der ønsker at forstå netværk og protokoller.
Kort fortalt
Artiklen omhandler grundlaget for pålidelig dataoverførsel, med eksempler på , herunder UDP og TCP. Inspireret af , , og bogen "Computer Networking. A Top-Down Approach", da alle kun diskuterer Tanenbaum og Olifer.
Transportprotokol
giver en logisk forbindelse mellem applikationsprocesser, der kører på forskellige værter. En logisk forbindelse fra applikationernes synspunkt ser ud som en kanal, der direkte forbinder processerne.

understøttes af slut-systemer, men ikke netværksroutere (undtagen — ). Ved afsenderens side omdanner transportlaget data fra applikationslaget, som er modtaget fra den sendende applikationsproces, til transportlagspakker, kaldet segmenter.

Dette opnås ved at opdele (hvis nødvendigt) applikationslagets meddelelser i fragmenter og tilføje en transportlagsoverskrift til hver af dem.

Herefter sender transportlaget segmentet til senderens netværkslag, hvor segmentet indkapsles i et netværkslagspakke (datagram) og sendes. På modtagerens side udtrækker netværkslaget transportlagsegmentet fra datagrammet og sender det op til transportlaget. Transportlaget behandler derefter det modtagne segment, så dets data bliver tilgængelige for det modtagende program.

Principper for pålidelig dataoverførsel
Pålidelig dataoverførsel over en fuldstændig pålidelig kanal
Det enkleste tilfælde. Afsenderen modtager blot data fra det øverste niveau, opretter en pakke, der indeholder dem, og sender den gennem kanalen.
Server
package main
import (
"log"
"net"
)
func main() {
// Serverens IP-adresse og port
serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:12000")
if err != nil {
log.Fatal(err)
}
// Opretter en socket med port
serverConn, err := net.ListenUDP("udp", serverAddr)
if err != nil {
log.Fatal(err)
}
// Udsat lukning af forbindelsen
defer serverConn.Close()
// Opretter en buffer til data
buf := make([]byte, 1024)
// Venter på forbindelse
for {
// Læser anmodningen
n, addr, err := serverConn.ReadFromUDP(buf)
// Sender data op til DET ØVERSTE niveau: i vores tilfælde stdout
println(string(buf[0:n]), " fra ", addr.IP.String())
if err != nil {
log.Fatal(err)
}
// Ingen svar, da dette er UDP + pålidelig kanal
}
}Kunde
package main
import (
"fmt"
"log"
"net"
"time"
)
func main() {
// Serverens IP-adresse og port
serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:12000")
if err != nil {
log.Fatal(err)
}
// Lokal IP-adresse og port
localAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
if err != nil {
log.Fatal(err)
}
// Oprettelse af forbindelse
conn, err := net.DialUDP("udp", localAddr, serverAddr)
if err != nil {
log.Fatal(err)
}
// Udsat lukning af forbindelse
defer conn.Close()
for {
// Modtagelse af data fra ØVERSTE niveau
fmt.Print("Indtast en sætning > ")
var msg string
_, err := fmt.Scanf("%s", &msg)
if err != nil {
log.Fatal(err)
}
// Der sendes en byte-strøm, ikke en streng
buf := []byte(msg)
// Skrive (transmission) til forbindelsen
_, err = conn.Write(buf)
if err != nil {
log.Fatal(err)
}
// Vent et sekund
time.Sleep(time.Second * 1)
}
}Pålidelig dataoverførsel over en kanal med mulige fejl
Det næste trin er at antage, at alle sendte pakker modtages i den rækkefølge, de blev sendt, men de enkelte bits kan være beskadiget, da kanalen nogle gange overfører data med forvrængninger.

I sådanne tilfælde anvendes mekanismer:
- fejlregistrering;
- tilbagemelding;
- genoverførsel.
Protokoller for pålidelig dataoverførsel, der har sådanne mekanismer for gentagen overførsel, kaldes automatiske gentagelsesprotokoller (Automatic Repeat reQuest, ARQ).
Derudover bør fejl i kvitteringer overvejes, når modtageren ikke modtager nogen oplysninger om resultaterne af overførslen af den seneste pakke.
Løsningen på dette problem, som også anvendes i TCP, består i at tilføje et nyt felt til datapakken, der indeholder pakkeordrenummeret.

Pålidelig dataoverførsel over en upålidelig kanal, der tillader forvrængning og pakke tab
Desværre er der samtidig, med forvrængninger, tab af pakker i netværket.
Og til at løse denne opgave kræves mekanismer:
- identifikation af pakke tab;
- genlevering af de tabte pakker til modtageren.
Derudover, udover pakettab, skal man tage højde for muligheden for at miste bekræftelser eller, hvis intet er gået tabt, en betydelig forsinkelse i leveringen af dem. I alle tilfælde bliver det samme udført: pakken sendes igen. For at kontrollere tiden i denne mekanisme anvendes en nedtællingstimer, som muliggør bestemmelse af slutningen af ventetiden. Således i pakken er TCPKeepAlive-indstillingen som standard sat til 15 sekunder:
// defaultTCPKeepAlive is a default constant value for TCPKeepAlive times
// See golang.org/issue/31510
const (
defaultTCPKeepAlive = 15 * time.Second
)Afsendersiden skal starte timeren hver gang en pakke sendes (både ved den første og ved genudsendelse), håndtere timerens afbrydelser og stoppe den.
Så vi har gjort os bekendt med de vigtigste begreber i protokoller for pålidelig datatransmission:
- kontrolsummer;
- sekvensnumre på pakker;
- timere;
- positive og negative bekræftelser.
Men det er ikke alt!
Protokol for pålidelig datatransmission med pipelining
I den version, vi allerede har set, er protokollen for pålidelig levering meget ineffektiv. Den begynder at "bremse" transmissionen, der sikres af kommunikationskanalen, når RTT øges. For at forbedre dens effektivitet og bedre udnyttelse af båndbredden på kommunikationskanalen anvendes pipelining.

Anvendelse af pipelining fører til:
- en stigning i intervallet af sekvensnumre, da alle sendte pakker (bortset fra genudsendelser) skal kunne identificeres entydigt;
- behovet for at øge buffere på afsender- og modtagersiderne.
Intervallet af sekvensnumre og kravene til bufferstørrelser afhænger af de handlinger, som protokollen udfører som svar på forvrængning, tab og forsinkelse af pakker. I tilfælde af pipelining findes der to metoder til fejlkorrigering:
- tilbagevenden N pakker tilbage;
- udvalgt genudsendelse.
Tilbagevenden N pakker tilbage - skydende vinduesprotokol

Afsenderen skal opretholde tre typer begivenheder:
- opkald på et højere protokolniveau. Når funktionen til at sende data kaldes "ovenfra", kontrollerer afsenderen først, hvor fyldt vinduet er (dvs. om der er N sendte meddelelser, der venter på bekræftelser). Hvis vinduet ikke er fyldt, dannes en ny pakke og sendes, og variablers værdier opdateres. Ellers returnerer afsenderen data til det højere niveau, hvilket implicit indikerer, at vinduet er fyldt. Normalt vil det højere niveau forsøge at sende data igen efter noget tid. I en reel anvendelse vil afsenderen sandsynligvis enten have buffret dataene (i stedet for straks at sende dem) eller have en synkroniseringsmekanisme (f.eks. et semafor eller et flag), der tillader det højere niveau at kalde funktionen til at sende data kun, når vinduet ikke er fyldt.
- modtagelse af bekræftelse. I protokollen gives der en samlet kvittering for en pakke med sekvensnummer N, som angiver, at alle pakker med sekvensnumre forud for N er blevet modtaget korrekt.
- timeout. For at bestemme tab og forsinkelser af pakker og bekræftelser anvender protokollen en timer. Hvis timeouten udløber, sender afsenderen alle sendt ubekræftede pakker igen.
Selectiv gentagelse
Når vinduets størrelse og produktet af båndbredde og forsinkelse er store, kan der være mange pakker i pipeline. I så fald kan en fejl i en enkelt pakke medføre, at mange pakker sendes igen, hvoraf de fleste ikke var nødvendige.
Eksempel
De bedste praksis er samlet i en praktisk implementering . Og hvis nogen ved, hvordan det kan gøres bedre — .
Server
package main
import (
"bufio"
"fmt"
"log"
"net"
"strings"
)
func main() {
// opretter en socket med porten
ln, err := net.Listen("tcp", ":8081")
if err != nil {
log.Fatalln(err)
}
// venter på opkald
conn, _ := ln.Accept()
for {
// læser data
msg, err := bufio.NewReader(conn).ReadString('n')
if err != nil {
log.Fatalln(err)
}
// udskriver meddelelsen i stdout
fmt.Print("Message Received:", string(msg))
// konverterer strengen til store bogstaver
newMsg := strings.ToUpper(msg)
// sender data
conn.Write([]byte(newMsg + "n"))
}
}Kunde
package main
import (
"bufio"
"fmt"
"log"
"net"
"os"
)
func main() {
// oprettelse af forbindelse
conn, err := net.Dial("tcp", "127.0.0.1:8081")
if err != nil {
log.Fatalln(err)
}
for {
// læsning af data fra stdin
reader := bufio.NewReader(os.Stdin)
fmt.Print("Tekst at sende: ")
// linje for linje
text, err := reader.ReadString('n')
if err != nil {
log.Fatalln(err)
}
// sending
fmt.Fprintf(conn, text+"n")
// modtagelse
msg, err := bufio.NewReader(conn).ReadString('n')
if err != nil {
log.Fatalln(err)
}
// udskrivning af modtaget svar
fmt.Print("Msg fra Server: " + msg)
}
}Output
Mekanismer, der sikrer pålidelig overførsel af data og deres anvendelse
Mekanisme
Anvendelse, kommentar
Kontrolsum
Bruges til at opdage bitfejl i det overførte pakke
Tæller
Nedtælling af ventetid og indikation af dens udløb. Dette betyder, at pakke eller dens kvittering med stor sandsynlighed er gået tabt under overførslen. Hvis pakken leveres med forsinkelse, men ikke går tabt (for tidlig udløb af ventetid), eller der opstår tab af kvitteringen, fører retransmissionen til dublering af pakken på modtagerens side
Kædenummer
Bruges til sekventiel nummerering af datapakker, der sendes fra afsender til modtager. Brud i kædenumre af de modtagne pakker gør det muligt for modtageren at opdage pakke tab. Ens kædenumre på pakker betyder, at pakkerne duplikerer hinanden
Bekræftelse
Genereres af modtageren og angiver til afsenderen, at den tilsvarende pakke eller gruppe af pakker er modtaget succesfuldt. Bekræftelsen indeholder normalt kædenumre for de succesfuldt modtagne pakker. Afhængigt af protokollen skelnes der mellem individuelle og gruppebekræftelser
Negativ bekræftelse
Bruges af modtageren til at informere afsenderen om, at pakken er modtaget ukorrekt. Den negative bekræftelse inkluderer normalt kædenummeret på den pakke, der ikke blev modtaget korrekt
Vindue, pipeline
Begrænser rækken af sekvensnumre, der kan bruges til at transmittere pakker. Gruppetrafik og håndtryk kan væsentligt øge kapaciteten for protokoller sammenlignet med mode med afventning på bekræftelse. Som vi vil se, kan vinduesstørrelsen beregnes baseret på modtagerens modtagelses- og bufferingskapabiliteter samt netværksbelastningsniveauet.
Andre eksempler på brug af Go til netværksarbejde.
I .
Kilde: habr.com
