
Celor care doresc să înțeleagă rețelele și protocoalele, le este dedicat acest material.
Pe scurt
Articolul abordează principiile fundamentale ale transmisiei de date fiabile, oferind exemple pe , inclusiv UDP și TCP. Inspirat din , , și din cartea "Rețelele de calculatoare. Abordarea de sus în jos", deoarece toată lumea discută doar despre Tannenbaum și Olifer.
Protocolul de transport
asigură o conexiune logică între procesele aplicațiilor care rulează pe gazde diferite. Conexiunea logică, din punctul de vedere al aplicațiilor, arată ca un canal care leagă direct procesele.

sunt susținute de sistemele finale, dar nu de routerele de rețea (cu excepția — ). Pe partea expeditorului, nivelul de transport transformă datele de la nivelul aplicației primite de la procesul aplicației de expediere în pachete de transport numite segmente.

Aceasta se realizează prin fragmentarea (după necesitate) mesajelor la nivelul aplicației în fragmente și adăugarea unui antet de transport fiecărui fragment.

Apoi, nivelul de transport trimite segmentul la nivelul de rețea al expeditorului, unde segmentul este încapsulat într-un pachet de nivel de rețea (datagramă) și este trimis. Pe partea receptorului, nivelul de rețea extrage segmentul de transport din datagramă și îl transmite înapoi nivelului de transport. Apoi, nivelul de transport procesesează segmentul primit astfel încât datele sale să devină disponibile aplicației receptor.

Principiile transmiterii fiabile a datelor
Transmisia fiabilă a datelor printr-un canal complet fiabil
Cel mai simplu caz. Partea expeditorului pur și simplu primește datele de la nivelul superior, creează un pachet care le conține și îl trimite prin canal.
Server
package main
import (
"log"
"net"
)
func main() {
// Adresa IP a serverului și portul
serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:12000")
if err != nil {
log.Fatal(err)
}
// creăm un socket cu portul
serverConn, err := net.ListenUDP("udp", serverAddr)
if err != nil {
log.Fatal(err)
}
// închidere amânată a conexiunii
defer serverConn.Close()
// creăm un buffer pentru date
buf := make([]byte, 1024)
// așteptăm conexiunea
for {
// citim cererea
n, addr, err := serverConn.ReadFromUDP(buf)
// transmitem datele către NIVELUL SUPERIOR: în cazul nostru stdout
println(string(buf[0:n]), " form ", addr.IP.String())
if err != nil {
log.Fatal(err)
}
// nu există răspuns, deoarece este UDP + canal fiabil
}
}Clientului
package main
import (
"fmt"
"log"
"net"
"time"
)
func main() {
// Adresa IP a serverului și portul
serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:12000")
if err != nil {
log.Fatal(err)
}
// Adresa IP locală și portul
localAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
if err != nil {
log.Fatal(err)
}
// Stabilirea conexiunii
conn, err := net.DialUDP("udp", localAddr, serverAddr)
if err != nil {
log.Fatal(err)
}
// Închidere întârziată a conexiunii
defer conn.Close()
for {
// Obținerea datelor de la nivelul SUPERIOR
fmt.Print("Introduceți o propoziție > ")
var msg string
_, err := fmt.Scanf("%s", &msg)
if err != nil {
log.Fatal(err)
}
// Se transmite un flux de octeți, nu un șir de caractere
buf := []byte(msg)
// Scrierea (transmiterea) în conexiune
_, err = conn.Write(buf)
if err != nil {
log.Fatal(err)
}
// O secundă, vă rog
time.Sleep(time.Second * 1)
}
}Transmisie fiabilă a datelor printr-un canal cu posibile erori
Următoarea etapă este presupunerea că toate pachetele transmise au fost primite în ordinea în care au fost trimise, dar biții din ele pot fi corupți, deoarece canalul transmite uneori date cu distorsiuni.

În acest caz, se aplică mecanismele:
- detectării erorilor;
- retroacțiunii;
- retransmiterii.
Protocoalele de transmisie fiabilă a datelor, care au astfel de mecanisme de retransmisie, se numesc protocoale cu cerință automată de retransmisie (Automatic Repeat reQuest, ARQ).
În plus, ar trebui să se prevadă posibilitatea erorilor și în confirmări, atunci când partea receptor nu primește nicio informație despre rezultatele transmisiei ultimului pachet.
Soluția acestei probleme, utilizată, printre altele, în TCP, constă în adăugarea într-un pachet de date a unui nou câmp care conține numărul secvențial al pachetului.

Transmisie fiabilă a datelor printr-un canal nesigur, care permite distorsiuni și pierderi de pachete
În paralel cu distorsiunile, din păcate, în rețea există și pierderi de pachete.
Și pentru a rezolva această problemă sunt necesare mecanisme:
- determinării faptului că au fost pierdute pachete;
- retransmiterii pachetelor pierdute către partea receptor.
În plus față de pierderea pachetului, trebuie să se ia în considerare posibilitatea pierderii unei confirmații sau, dacă nimic nu este pierdut, livrarea acesteia cu o întârziere semnificativă. În toate cazurile, se aplică același lucru: retransmiterea pachetului. Pentru a monitoriza timpul în acest mecanism, se utilizează un timer de contor, care permite determinarea sfârșitului intervalului de așteptare. Astfel, în pachet parametrul TCPKeepAlive este setat la 15 secunde în mod implicit:
// defaultTCPKeepAlive is a default constant value for TCPKeepAlive times
// See golang.org/issue/31510
const (
defaultTCPKeepAlive = 15 * time.Second
)Partea transmitătoare trebuie să pornească timerul de fiecare dată când transmite un pachet (atât la prima, cât și la retransmitere), să gestioneze întreruperile de la timer și să îl oprească.
Astfel, ne-am familiarizat cu conceptele cheie ale protocoalelor de transmisie de date fiabile:
- sumelor de control;
- numerelor de ordine ale pachetelor;
- timere;
- confirmărilor pozitive și negative.
Dar asta nu este tot!
Protocolul de transmisie de date fiabile cu tubulatură
În varianta pe care am analizat-o deja, protocolul de livrare fiabilă devine foarte ineficient. Acesta începe să "întârzie" transmisia, asigurată de canalul de comunicare, pe măsură ce RTT crește. Pentru a-i îmbunătăți eficiența, utilizarea mai bună a lățimii de bandă a canalului de comunicare se aplică tubulaturii.

Aplicarea tubulaturii duce la:
- creșterea intervalului numerelor de ordine, deoarece toate pachetele trimise (cu excepția retransmisiunilor) trebuie să fie clar identificabile;
- necesitatea creșterii bufferelor pe partea transmisătoare și pe cea de recepție.
Intervalul numerelor de ordine și cerințele pentru dimensiunile bufferelor depind de acțiunile întreprinse de protocol ca răspuns la distorsiuni, pierderi și întârzieri ale pachetelor. În cazul tubulaturii există două metode de corectare a erorilor:
- întoarcerea cu N pachete înapoi;
- repetiția selectivă.
Întoarcerea cu N pachete înapoi — protocolul fereastră glisantă

Expeditorul trebuie să mențină trei tipuri de evenimente:
- apelarea unui protocol de nivel superior. Când funcția de trimitere a datelor este apelată „de sus”, partea care trimite verifică mai întâi gradul de umplere al feronierului (adică numărul de mesaje trimise care așteaptă confirmarea). Dacă feronierul nu este plin, se formează un nou pachet și se transmite, iar valorile variabilelor sunt actualizate. Altfel, partea trimisă returnează datele nivelului superior, ceea ce reprezintă o indicație implicită că feronierul este plin. De obicei, nivelul superior reîncercă să transmită datele după un timp. Într-o aplicație reală, expeditorul ar fi cel mai probabil fie ar fi bufferizat datele (în loc de a le trimite imediat), fie ar avea un mecanism de sincronizare (de exemplu, un semafor sau un steag) care să permită nivelului superior să apeleze funcția de trimitere a datelor doar când feronierul nu este plin.
- obținerea unei confirmări. În protocol, pentru pachetul cu numărul de ordine N se emite o confirmare generală, indicând că toate pachetele cu numere de ordine anterioare lui N au fost primite cu succes.
- expirarea intervalului de așteptare. Pentru a determina pierderile și întârzierile pachetelor și confirmărilor, protocolul folosește un temporizator. Dacă intervalul de așteptare expiră, partea care trimite retransmite toate pachetele trimise, dar neconfirmate.
Retransmitere selectivă
Când dimensiunea feronierului și produsul capacității de transmisie cu întârzierea de propagare sunt mari, este posibil să existe un număr mare de pachete în conductă. În acest caz, o eroare a unui pachet singular poate cauza retransmisia unui număr mare de pachete, majoritatea dintre ele fiind nerevendicate.
Exemplu
Cele mai bune practici sunt reunite într-o implementare practică . Și dacă cineva știe o modalitate mai bună — .
Server
package main
import (
"bufio"
"fmt"
"log"
"net"
"strings"
)
func main() {
// creăm un socket pe portul
ln, err := net.Listen("tcp", ":8081")
if err != nil {
log.Fatalln(err)
}
// așteptăm apelul
conn, _ := ln.Accept()
for {
// citirea datelor
msg, err := bufio.NewReader(conn).ReadString('n')
if err != nil {
log.Fatalln(err)
}
// afișarea mesajului în stdout
fmt.Print("Mesaj primit:", string(msg))
// transformarea mesajului în litere mari
newMsg := strings.ToUpper(msg)
// trimiterea datelor
conn.Write([]byte(newMsg + "n"))
}
}Clientului
package main
import (
"bufio"
"fmt"
"log"
"net"
"os"
)
func main() {
// stabilirea conexiunii
conn, err := net.Dial("tcp", "127.0.0.1:8081")
if err != nil {
log.Fatalln(err)
}
for {
// citirea datelor din stdin
reader := bufio.NewReader(os.Stdin)
fmt.Print("Text de trimis: ")
// linie cu linie
text, err := reader.ReadString('n')
if err != nil {
log.Fatalln(err)
}
// trimiterea
fmt.Fprintf(conn, text+"n")
// primirea
msg, err := bufio.NewReader(conn).ReadString('n')
if err != nil {
log.Fatalln(err)
}
// afişarea răspunsului primit
fmt.Print("Msg de la Server: " + msg)
}
}Ieșire
Mecanisme care asigură transmiterea fiabilă a datelor și utilizarea acestora
Mecanismul
Aplicație, comentariu
Sumă de verificare
Se folosește pentru a detecta erorile de biți în pachetul transmis
Cronometru
Contor de timp de așteptare și indicarea expirării sale. Ultimul înseamnă că, cu un grad ridicat de probabilitate, pachetul sau recunoașterea sa au fost pierdute în timpul transmiterii. Dacă pachetul este livrat cu întârziere, dar nu se pierde (expirarea prematură a timpului de așteptare), sau se pierde recunoașterea, retransmiterea duce la duplicarea pachetului pe partea receptorului
Număr secvențial
Se folosește pentru numerotarea secvențială a pachetelor de date transmise de la expeditor la receptor. Găurile în numerele secvențiale ale pachetelor primite permit receptorului să detecteze pierderea pachetului. Numerele secvențiale identice ale pachetelor înseamnă că pachetele se duplică între ele
Confirmare
Generată de partea receptorului și indică părții transmițătoare că pachetul corespunzător sau grupul de pachete a fost primit cu succes. De obicei, confirmarea conține numerele secvențiale ale pachetelor primite cu succes. În funcție de protocol, există confirmări individuale și de grup
Confirmare negativă
Se folosește de către receptor pentru a informa expeditorul că pachetul a fost primit incorect. Confirmarea negativă include de obicei numărul secvențial al pachetului care nu a fost recepționat corect
Fereastră, pipelining
Limitează intervalul numerelor de secvență care pot fi utilizate pentru transferul pachetelor. Transmiterea în grup și handshake-ul permit o creștere semnificativă a lățimii de bandă în protocoale comparativ cu modul de așteptare a confirmărilor. Așa cum vom vedea, dimensiunea feronului poate fi calculată pe baza capacităților de primire și bufferizare ale părții receptor, precum și a nivelului de încărcare al rețelei.
Alte exemple de utilizare a Go pentru lucrul cu rețelele.
În .
Sursa: habr.com
