
Aan degenen die zich willen verdiepen in netwerken en protocollen, opgedragen.
Kort
In dit artikel worden de basisprincipes van betrouwbare gegevensoverdracht besproken, met voorbeelden op , waaronder UDP en TCP. GeĆÆnspireerd door , , en het boek "Computer Networks. A descending approach", aangezien iedereen alleen maar over Tanenbaum en Olifer spreekt.
Transportlaagprotocol
verzorgt een logische verbinding tussen applicatieprocessen die op verschillende hosts draaien. Een logische verbinding ziet er vanuit applicaties uit als een kanaal dat de processen rechtstreeks verbindt.

worden ondersteund door eindsystemen, maar niet door netwerkroutetters (behalve ā ). Aan de verzendzijde zet de transportlaag de gegevens van de applicatielaag, die het ontvangt van het verzendende applicatieproces, om in transportlaagpakketten, die segmenten worden genoemd.

Dit gebeurt door (indien nodig) berichten van de applicatielaag in fragmenten op te breken en elk fragment van een transportlaagheader te voorzien.

Vervolgens verzendt de transportlaag het segment naar de netlaag van de verzender, waar het segment wordt ingekapseld in een netwerklaagpakket (datagram) en verzonden. Aan de ontvangende kant haalt de netlaag het transportlaagsegment uit het datagram en geeft het omhoog door aan de transportlaag. Vervolgens behandelt de transportlaag het ontvangen segment zodanig dat de gegevens beschikbaar komen voor de ontvangende applicatie.

Principes van betrouwbare gegevensoverdracht
Betrouwbare gegevensoverdracht via een volkomen betrouwbaar kanaal
De eenvoudigste zaak. De verzendende partij ontvangt eenvoudig gegevens van de bovenliggende laag, maakt een pakket aan dat deze bevat en verzendt het naar het kanaal.
Server
package main
import (
"log"
"net"
)
func main() {
// IP-adres van de server en poort
serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:12000")
if err != nil {
log.Fatal(err)
}
// maak een socket met de poort
serverConn, err := net.ListenUDP("udp", serverAddr)
if err != nil {
log.Fatal(err)
}
// uitgestelde sluiting van de verbinding
defer serverConn.Close()
// maak een buffer voor gegevens
buf := make([]byte, 1024)
// wacht op verbinding
for {
// lees het verzoek
n, addr, err := serverConn.ReadFromUDP(buf)
// stuur gegevens naar de BOVENLIGGENDE laag: in ons geval stdout
println(string(buf[0:n]), " van ", addr.IP.String())
if err != nil {
log.Fatal(err)
}
// er is geen antwoord, omdat dit UDP + betrouwbaar kanaal is
}
}Klant
package main
import (
"fmt"
"log"
"net"
"time"
)
func main() {
// IP-adres van de server en poort
serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:12000")
if err != nil {
log.Fatal(err)
}
// Lokaal IP-adres en poort
localAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
if err != nil {
log.Fatal(err)
}
// Verbinding opzetten
conn, err := net.DialUDP("udp", localAddr, serverAddr)
if err != nil {
log.Fatal(err)
}
// Vertraging bij het sluiten van de verbinding
defer conn.Close()
for {
// Ontvang gegevens van de BOVENSTE laag
fmt.Print("Voer een zin in > ")
var msg string
_, err := fmt.Scanf("%s", &msg)
if err != nil {
log.Fatal(err)
}
// Er wordt een byte-stream verzonden, geen string
buf := []byte(msg)
// Schrijven (verzenden) naar de verbinding
_, err = conn.Write(buf)
if err != nil {
log.Fatal(err)
}
// 1 seconde wachten
time.Sleep(time.Second * 1)
}
}Betrouwbare gegevensoverdracht over een kanaal met mogelijke fouten
De volgende stap is de veronderstelling dat alle verzonden pakketten zijn ontvangen in de volgorde waarin ze zijn verzonden, maar dat de bits daarin beschadigd kunnen zijn, omdat het kanaal soms gegevens met vervormingen verzendt.

In dat geval worden de mechanismen toegepast:
- foutdetectie;
- feedback;
- herverzending.
Protocollen voor betrouwbare gegevensoverdracht die dergelijke mechanismen voor herhaalde verzending hebben, worden automatische herverzoekprotocollen (Automatic Repeat reQuest, ARQ) genoemd.
Daarnaast is het belangrijk om rekening te houden met de mogelijkheid van fouten in de bevestigingen, wanneer de ontvangende partij geen informatie ontvangt over de resultaten van de overdracht van het laatste pakket.
De oplossing voor dit probleem, die ook in TCP wordt gebruikt, bestaat uit het toevoegen van een nieuw veld aan het datapakket dat het volgnummer van het pakket bevat.

Betrouwbare gegevensoverdracht over een onbetrouwbaar kanaal dat vervorming en pakketverlies toelaat
Tegelijkertijd is er helaas ook pakketverlies in het netwerk.
En om dit probleem op te lossen zijn mechanismen vereist:
- vaststellen van het feit van pakketverlies;
- herlevering van verloren pakketten aan de ontvangende partij.
Bovendien, naast pakkettenverlies, moet er rekening worden gehouden met de mogelijkheid van het verlies van bevestigingen of, als er niets verloren is, de levering ervan met aanzienlijke vertraging. In alle gevallen gebeurt hetzelfde: het opnieuw verzenden van het pakket. Voor het controleren van de tijd in dit mechanisme wordt een timer gebruikt die het einde van de wachttijd kan bepalen. Zo in het pakket is de TCPKeepAlive parameter standaard ingesteld op 15 seconden:
// defaultTCPKeepAlive is a default constant value for TCPKeepAlive times
// See golang.org/issue/31510
const (
defaultTCPKeepAlive = 15 * time.Second
)De verzendende partij moet de timer elke keer starten bij het verzenden van een pakket (zowel bij de eerste als bij een herhaalde verzending), onderbrekingen van de timer verwerken en deze stoppen.
Dus, we hebben kennis gemaakt met de belangrijkste concepten van betrouwbare gegevensoverdracht protocollen:
- checksums;
- pakketvolgnummer;
- timers;
- positieve en negatieve bevestigingen.
Maar dat is nog niet alles!
Het protocol voor betrouwbare gegevensoverdracht met pipelining
In de versie die we al hebben besproken, is het protocol voor betrouwbare levering zeer inefficiƫnt. Het begint de overdracht, die door het communic kanaal wordt geboden, te "vertragen" bij een toenemende RTT. Om de efficiƫntie te verhogen en het gebruik van de bandbreedte van het communic kanaal te verbeteren, wordt pipelining toegepast.

Het gebruik van pipelining leidt tot:
- een uitbreiding van de reeks volgnummer, omdat alle verzonden pakketten (met uitzondering van herhaalde verzendingen) eenduidig identificeerbaar moeten zijn;
- de noodzaak voor het vergroten van de buffers aan de verzendende en ontvangende zijde.
De reeks volgnummer en de eisen aan bufferformaten zijn afhankelijk van de acties die het protocol onderneemt in reactie op vervormingen, verlies en vertraging van pakketten. In het geval van pipelining zijn er twee methoden voor foutcorrectie:
- terugkeer naar N pakketten terug;
- selectieve herhaling.
Terugkeer naar N pakketten terug ā het glijdende vensterprotocol

De verzender moet drie soorten evenementen onderhouden:
- oproep via een hoger niveau protocol. Wanneer de functie voor het verzenden van gegevens van 'boven' wordt aangeroepen, controleert de verzender eerst de mate van het vullen van het venster (dat wil zeggen, het aantal verzonden berichten dat wacht op ontvangstbevestigingen). Als het venster niet vol is, wordt een nieuw pakket gevormd en verzonden, en worden de waarden van de variabelen bijgewerkt. In andere gevallen geeft de verzender de gegevens terug aan het hogere niveau, wat een impliciete aanwijzing is dat het venster vol is. Gewoonlijk probeert het hogere niveau op een later tijdstip opnieuw de gegevens te verzenden. In een echte applicatie zou de verzender waarschijnlijk de gegevens bufferen (in plaats van deze onmiddellijk te verzenden) of een synchronisatiemechanisme (zoals een semafoor of vlag) hebben dat het hogere niveau toestaat de functie voor het verzenden van gegevens alleen aan te roepen als het venster niet vol is.
- ontvangstbevestiging. In het protocol voor het pakket met volgnummer N wordt een algemene ontvangstbevestiging verstrekt, die aangeeft dat alle pakketten met volgnummers voorafgaand aan N succesvol zijn ontvangen.
- verval van de wachttijd. Om verlies- en vertragingseffecten van pakketten en ontvangstbevestigingen vast te stellen, gebruikt het protocol een timer. Als de wachttijd verstrijkt, verzendt de verzender alle verzonden, onbevestigde pakketten opnieuw.
Selectieve herhaling
Wanneer de venstergrootte en het product van bandbreedte en propagatietijd groot zijn, kunnen er veel pakketten in de pijplijn zijn. In dat geval kan de fout van een enkel pakket leiden tot de herverzending van een groot aantal pakketten, waarvan de meeste niet nodig waren.
Voorbeeld
De beste praktijken zijn verzameld in een praktische implementatie . En als iemand weet hoe het beter kan ā .
Server
package main
import (
"bufio"
"fmt"
"log"
"net"
"strings"
)
func main() {
// Maak een socket aan met poort
ln, err := net.Listen("tcp", ":8081")
if err != nil {
log.Fatalln(err)
}
// Wacht op een oproep
conn, _ := ln.Accept()
for {
// Gegevens lezen
msg, err := bufio.NewReader(conn).ReadString('n')
if err != nil {
log.Fatalln(err)
}
// Bericht afdrukken op stdout
fmt.Print("Bericht Ontvangen:", string(msg))
// Vertaal de regel naar hoofdletters
newMsg := strings.ToUpper(msg)
// Gegevens verzenden
conn.Write([]byte(newMsg + "n"))
}
}Klant
pakket hoofd
import (
"bufio"
"fmt"
"log"
"net"
"os"
)
func hoofd() {
// verbinding instellen
conn, err := net.Dial("tcp", "127.0.0.1:8081")
als err != nil {
log.Fatalln(err)
}
voor {
// gegevens lezen van stdin
lezer := bufio.NewReader(os.Stdin)
fmt.Print("Tekst om te verzenden: ")
// regel per regel
tekst, err := lezer.ReadString('n')
als err != nil {
log.Fatalln(err)
}
// verzending
fmt.Fprintf(conn, tekst+"n")
// ontvangst
msg, err := bufio.NewReader(conn).ReadString('n')
als err != nil {
log.Fatalln(err)
}
// weergegeven antwoord
fmt.Print("Bericht van Server: " + msg)
}
}Uitslag
Mechanismen die zorgen voor betrouwbare gegevensoverdracht en gebruik daarvan
Mechanisme
Toepassing, opmerking
Controlegetal
Wordt gebruikt voor het opsporen van bits fouten in het verzonden pakket
Timer
Tijdslimiet tellen en aangeven dat deze is verstreken. Laatstgenoemde betekent dat het zeer waarschijnlijk is dat het pakket of de ontvangstbevestiging verloren is gegaan tijdens de overdracht. In het geval dat het pakket met vertraging wordt afgeleverd, maar niet verloren gaat (voorbarige tijdslimiet), of dat er een verlies van de ontvangstbevestiging plaatsvindt, dan leidt een herverzending tot duplicatie van het pakket aan de ontvangende zijde.
Volgnummer
Wordt gebruikt voor de volgorde nummering van gegevenspakketten die van de zender naar de ontvanger worden verzonden. Hiaten in de volgordes van de ontvangen pakketten stellen de ontvanger in staat om het verlies van een pakket op te sporen. Identieke volgordes van pakketten betekenen dat de pakketten elkaar dupliceren.
Bevestiging
Wordt gegenereerd door de ontvangende kant en geeft de verzendende kant aan dat het bijbehorende pakket of de groep pakketten succesvol is ontvangen. Bevestiging bevat meestal de volgnummer van succesvol ontvangen pakketten. Afhankelijk van het protocol onderscheiden we individuele en groepsbevestigingen.
Negatieve bevestiging
Wordt door de ontvanger gebruikt om de zender te informeren dat het pakket onjuist is ontvangen. Negatieve bevestiging bevat meestal het volgnummer van het pakket dat niet correct is ontvangen.
Venster, pipelining
Beperken het bereik van volgordennummers die kunnen worden gebruikt voor pakketoverdracht. Groepstransmissie en handdruk maken het mogelijk de doorvoersnelheid van protocollen aanzienlijk te verhogen in vergelijking met de bevestigingsmodus. Zoals we zullen zien, kan de venstergrootte worden berekend op basis van de ontvangstcapaciteiten en buffering van de ontvangende partij, evenals het netwerkniveau van belasting.
Andere voorbeelden van het gebruik van Go voor netwerktoepassingen.
In .
Bron: habr.com
