
A coloro che Dedicato alla comprensione delle reti e dei protocolli.
brevemente
L'articolo discute i fondamenti della trasmissione affidabile dei dati e implementa esempi su , inclusi UDP e TCP. Basato su , , e il libro "Computer Networks: A Top-Down Approach", altrimenti tutti parlano solo di Tannenbaum e Oliferov.
Protocollo di livello di trasporto
Fornisce una connessione logica tra processi applicativi in esecuzione su host diversi. Dal punto di vista delle applicazioni, una connessione logica appare come un canale che collega direttamente i processi.

supportato dai sistemi finali ma non dai router di rete (tranne - ). Sul lato mittente, il livello di trasporto converte i dati del livello applicativo ricevuti dal processo applicativo trasmittente in pacchetti del livello di trasporto denominati segmenti.

Ciò avviene suddividendo (se necessario) i messaggi del livello applicativo in frammenti e aggiungendo a ciascuno di essi un'intestazione del livello di trasporto.

Il livello di trasporto passa quindi il segmento al livello di rete del mittente, dove viene incapsulato in un pacchetto di livello di rete (datagramma) e inviato. Sul lato ricevente, il livello di rete estrae il segmento di livello di trasporto dal datagramma e lo passa al livello di trasporto. Il livello di trasporto elabora quindi il segmento ricevuto in modo che i suoi dati siano disponibili all'applicazione ricevente.

Principi di trasmissione sicura dei dati
Trasmissione dati affidabile su un canale completamente sicuro
Il caso più semplice: il lato mittente riceve semplicemente i dati dal livello superiore, crea un pacchetto che li contiene e li invia lungo il canale.
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 + надежный канал
}
}Cliente
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)
}
}Trasmissione dati affidabile su un canale con possibili errori
Il passo successivo è supporre che tutti i pacchetti trasmessi vengano ricevuti nell'ordine in cui sono stati inviati, ma che i bit in essi contenuti possano essere corrotti perché il canale a volte trasmette dati con distorsioni.

In questo caso si applicano i seguenti meccanismi:
- rilevamento degli errori;
- feedback;
- ritrasmissione.
I protocolli di trasmissione dati affidabili che dispongono di meccanismi per ripetizioni di trasmissione multiple sono chiamati protocolli ARQ (Automatic Repeat ReQuest).
Inoltre, vale la pena considerare la possibilità di errori nelle ricevute, quando la parte ricevente non riceve alcuna informazione sui risultati dell'ultimo trasferimento del pacchetto.
La soluzione a questo problema, utilizzata tra le altre cose nel TCP, consiste nell'aggiungere un nuovo campo al pacchetto dati contenente il numero di sequenza del pacchetto.

Trasmissione dati affidabile su un canale inaffidabile che consente la distorsione e la perdita dei pacchetti
Oltre alle distorsioni, purtroppo, si verifica anche la perdita di pacchetti nella rete.
E per risolvere questo problema sono necessari dei meccanismi:
- determinare il fatto della perdita del pacchetto;
- riconsegna dei pacchetti persi alla parte ricevente.
Inoltre, oltre alla perdita del pacchetto, è necessario considerare la possibilità di una ricezione persa o, se non si perde nulla, della sua consegna con un ritardo significativo. In tutti i casi, si verifica la stessa azione: il pacchetto viene ritrasmesso. Per controllare la tempistica, questo meccanismo utilizza un timer per il conto alla rovescia, che consente di determinare la fine dell'intervallo di attesa. Pertanto, nel pacchetto TCPKeepAlive è impostato di default su 15 secondi:
// defaultTCPKeepAlive is a default constant value for TCPKeepAlive times
// See golang.org/issue/31510
const (
defaultTCPKeepAlive = 15 * time.Second
)Il lato trasmittente deve avviare il timer ogni volta che viene trasmesso un pacchetto (sia la prima che la seconda volta), gestire le interruzioni del timer e arrestarlo.
Abbiamo quindi acquisito familiarità con i concetti chiave dei protocolli di trasmissione dati affidabili:
- checksum;
- numeri di serie dei pacchi;
- timer;
- ricevute positive e negative.
Ma non è tutto!
Protocollo di trasferimento dati affidabile con pipeline
Nell'implementazione che abbiamo già considerato, il protocollo di consegna affidabile è molto inefficiente. Inizia a "rallentare" la trasmissione fornita dal canale di comunicazione all'aumentare dell'RTT. Per migliorarne l'efficienza e sfruttare al meglio la larghezza di banda del canale di comunicazione, si utilizza il pipelining.

L'uso del pipelining si traduce in:
- aumentando l'intervallo dei numeri di sequenza, poiché tutti i pacchetti inviati (ad eccezione delle ritrasmissioni) devono essere identificabili in modo univoco;
- la necessità di aumentare i buffer sui lati di trasmissione e ricezione.
L'intervallo di numeri di sequenza e i requisiti di dimensione del buffer dipendono dalla risposta del protocollo a corruzione, perdita e ritardo dei pacchetti. Nel caso del pipelining, esistono due metodi di correzione degli errori:
- restituire N pacchetti;
- ripetizione selettiva.
Pacchetti N all'indietro - protocollo a finestra scorrevole

Il mittente deve supportare tre tipi di eventi:
- Una chiamata da un protocollo di livello superiore. Quando la funzione di invio viene chiamata dall'alto, il mittente verifica innanzitutto se la finestra è piena (ovvero se ci sono N messaggi inviati in attesa di conferma). Se la finestra non è piena, viene formato e trasmesso un nuovo pacchetto e i valori delle variabili vengono aggiornati. In caso contrario, il mittente restituisce i dati al livello superiore, il che è un'indicazione implicita che la finestra è piena. In genere, il livello superiore riproverà a trasmettere i dati dopo un certo periodo di tempo. In un'applicazione reale, il mittente molto probabilmente memorizzerebbe i dati in un buffer (invece di inviarli immediatamente) o disporrebbe di un meccanismo di sincronizzazione (ad esempio, un semaforo o un flag) che consentirebbe al livello superiore di chiamare la funzione di invio solo quando la finestra non è piena.
- Ricezione di un riconoscimento. Nel protocollo, viene emesso un riconoscimento generale per un pacchetto con numero di sequenza N, a indicare che tutti i pacchetti con numeri di sequenza precedenti a N sono stati ricevuti correttamente.
- Scadenza del timeout. Il protocollo utilizza un timer per rilevare la perdita di pacchetti e la mancata ricezione di conferma, nonché eventuali ritardi. Se il timeout scade, la parte trasmittente invia nuovamente tutti i pacchetti non ricevuti.
Ripetizione selettiva
Quando le dimensioni della finestra e il prodotto throughput-ritardo sono elevati, nella pipeline potrebbe essere presente un numero elevato di pacchetti. In questo caso, un errore in un singolo pacchetto può causare la ritrasmissione di un gran numero di pacchetti, la maggior parte dei quali non necessari.
esempio
Top le pratiche sono raccolte nell'implementazione pratica E se qualcuno conosce un modo migliore - .
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"))
}
}Cliente
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)
}
}conclusione
Meccanismi che garantiscono un trasferimento e un utilizzo affidabili dei dati
Механизм
Applicazione, commento
Controlla la somma
Utilizzato per rilevare errori di bit in un pacchetto trasmesso.
Timer
Conto alla rovescia dell'intervallo di timeout e indicazione della sua scadenza. Quest'ultima indica che, con un alto grado di probabilità, il pacchetto o la sua conferma di ricezione sono andati persi durante la trasmissione. Se il pacchetto viene consegnato in ritardo ma non perso (scadenza prematura dell'intervallo di timeout), o la conferma di ricezione viene persa, la ritrasmissione genera un pacchetto duplicato sul lato ricevente.
Numero di serie
Utilizzato per numerare in sequenza i pacchetti di dati trasmessi dal mittente al destinatario. Gli spazi vuoti nei numeri di sequenza dei pacchetti ricevuti consentono al destinatario di rilevare la perdita di pacchetti. Numeri di sequenza di pacchetti identici indicano pacchetti duplicati.
Conferma
Generati dalla parte ricevente e che indicano alla parte trasmittente che il pacchetto o il gruppo di pacchetti corrispondente è stato ricevuto correttamente. Gli acknowledgment contengono in genere i numeri di sequenza dei pacchetti ricevuti correttamente. A seconda del protocollo, si distingue tra acknowledgment individuali e di gruppo.
Conferma negativa
Utilizzato dal destinatario per informare il mittente che un pacchetto non è stato ricevuto correttamente. Una conferma negativa include in genere il numero di sequenza del pacchetto non ricevuto correttamente.
Finestra, conduttura
Limitano l'intervallo di numeri di sequenza utilizzabili per la trasmissione dei pacchetti. La trasmissione multicast e l'handshake aumentano significativamente il throughput del protocollo rispetto all'attesa di conferme di ricezione. Come vedremo, la dimensione della finestra può essere calcolata in base alle capacità di ricezione e buffering del destinatario, nonché al carico di rete.
Altri esempi di utilizzo di Go per il networking
В .
Fonte: habr.com
