A coloro che
brevemente
L'articolo discute le basi di una trasmissione dati affidabile, implementa esempi su
Protocollo del livello di trasporto
Fornisce una connessione logica tra i processi applicativi in esecuzione su host diversi. Dal punto di vista dell'applicazione, una connessione logica assomiglia a un canale che collega direttamente i processi.
Questo viene fatto suddividendo (se necessario) i messaggi del livello di applicazione in frammenti e aggiungendo un'intestazione del livello di trasporto a ciascuno di essi.
Il livello di trasporto passa quindi il segmento al livello di rete del mittente, dove il segmento viene incapsulato in un pacchetto del livello di rete (datagramma) e inviato. All'estremità ricevente, lo strato di rete estrae il segmento dello strato di trasporto dal datagramma e lo passa allo strato di trasporto. Successivamente, il livello di trasporto elabora il segmento ricevuto in modo che i suoi dati diventino disponibili per l'applicazione ricevente.
Principi di trasmissione affidabile dei dati
Trasmissione dati affidabile su un canale completamente sicuro
Il caso più semplice. Il lato mittente riceve semplicemente i dati dallo strato superiore, crea un pacchetto che li contiene e li invia al 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 è presumere che tutti i pacchetti trasmessi vengano ricevuti nell'ordine in cui sono stati inviati, ma i bit in essi contenuti potrebbero essere danneggiati a causa del fatto che il canale a volte trasmette i dati con distorsioni.
In questo caso vengono utilizzati i seguenti meccanismi:
- rilevamento degli errori;
- feedback;
- ritrasmissione.
I protocolli di trasferimento dati affidabili che dispongono di meccanismi simili per ripetere la trasmissione più volte sono chiamati protocolli ARQ (Automatic Repeat ReQuest).
Inoltre, vale la pena considerare la possibilità di errori nelle ricevute, quando la parte ricevente non riceverà alcuna informazione sui risultati del trasferimento dell'ultimo pacchetto.
La soluzione a questo problema, utilizzata anche in TCP, è aggiungere al pacchetto dati un nuovo campo contenente il numero di sequenza del pacchetto.
Trasmissione dati affidabile su un canale inaffidabile soggetto a distorsione e perdita di pacchetti
Oltre alla distorsione, purtroppo, si verifica anche la perdita di pacchetti nella rete.
E per risolvere questo problema, sono necessari meccanismi:
- determinare il fatto della perdita di pacchetti;
- riconsegna dei pacchetti persi alla parte ricevente.
Inoltre, oltre allo smarrimento del pacco, è necessario prevedere la possibilità di smarrimento della ricevuta o, se non viene smarrito nulla, della sua consegna con notevole ritardo. In tutti i casi si fa la stessa cosa: il pacchetto viene ritrasmesso. Per controllare il tempo, questo meccanismo utilizza un timer per il conto alla rovescia, che consente di determinare la fine dell'intervallo di attesa. Quindi nel pacchetto
// defaultTCPKeepAlive is a default constant value for TCPKeepAlive times
// See golang.org/issue/31510
const (
defaultTCPKeepAlive = 15 * time.Second
)
Il lato mittente deve avviare un timer ogni volta che un pacchetto viene trasmesso (sia la prima che la seconda volta), gestire le interruzioni del timer e fermarlo.
Quindi, abbiamo acquisito familiarità con i concetti chiave dei protocolli di trasferimento dati affidabili:
- somme di controllo;
- numeri progressivi dei colli;
- temporizzatori;
- incassi positivi e negativi.
Ma non è tutto!
Protocollo di trasferimento dati affidabile con pipeline
Nella variante che abbiamo già considerato, il protocollo di consegna affidabile è molto inefficiente. Inizia a “rallentare” la trasmissione fornita dal canale di comunicazione man mano che aumenta l'RTT. Per aumentarne l'efficienza e utilizzare meglio la capacità del canale di comunicazione, viene utilizzato il pipelining.
L’uso del pipeline porta a:
- aumentare l'intervallo dei numeri di sequenza, poiché tutti i pacchetti inviati (ad eccezione delle ritrasmissioni) devono essere identificati in modo univoco;
- la necessità di aumentare i buffer sui lati trasmittente e ricevente.
L'intervallo dei numeri di sequenza e i requisiti relativi alla dimensione del buffer dipendono dalle azioni intraprese dal protocollo in risposta alla corruzione, alla perdita e al ritardo dei pacchetti. Nel caso del pipeline ci sono due metodi per correggere gli errori:
- restituire N pacchetti;
- ripetizione selettiva.
Tornando indietro N pacchetti - protocollo a finestra scorrevole
Il mittente deve supportare tre tipi di eventi:
- chiamata tramite un protocollo di livello superiore. Quando la funzione di invio dati è detta “dall'alto”, il lato mittente verifica innanzitutto il grado di riempimento della finestra (cioè la presenza di N messaggi inviati in attesa di ricezione). Se la finestra è vuota, viene generato e trasmesso un nuovo pacchetto e i valori delle variabili vengono aggiornati. Altrimenti, il lato mittente restituisce i dati al livello superiore e questa è un'indicazione implicita che la finestra è piena. In genere il livello superiore tenterà di trasmettere nuovamente i dati dopo un po' di tempo. In un'applicazione reale, il mittente probabilmente bufferizzerebbe i dati (invece di inviarli immediatamente) o disporrebbe di un meccanismo di sincronizzazione (come un semaforo o un flag) che consentirebbe al livello superiore di chiamare la funzione di invio solo quando la finestra è vuota .
- ricevendo conferma. Nel protocollo, per un pacchetto con numero di sequenza N, viene emessa una conferma generale che indica che tutti i pacchetti con numeri di sequenza precedenti N sono stati ricevuti con successo.
- l'intervallo di attesa è scaduto. Per determinare i fatti relativi alle perdite e ai ritardi di pacchetti e ricevute, il protocollo utilizza un timer. Se l'intervallo di timeout scade, il lato mittente rinvia tutti i pacchetti inviati non riconosciuti.
Ripetizione selettiva
Quando la dimensione della finestra e il prodotto del ritardo della propagazione del throughput sono elevati, è possibile che nella pipeline sia presente un numero elevato di pacchetti. In tal caso, un singolo errore di pacchetto può causare la ritrasmissione di un gran numero di pacchetti, la maggior parte dei quali non erano necessari.
esempio
Top
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 per garantire il trasferimento e l’utilizzo affidabili dei dati
Механизм
Applicazione, commento
Controlla la somma
Utilizzato per rilevare errori di bit in un pacchetto trasmesso
Timer
Conta alla rovescia l'intervallo di timeout e indica quando è scaduto. Quest'ultimo significa che con un alto grado di probabilità il pacchetto o la sua ricevuta vengono persi durante la trasmissione. Se un pacchetto viene consegnato con un ritardo, ma non viene perso (scadenza anticipata dell'intervallo di timeout), o una ricezione viene persa, la ritrasmissione porta a un pacchetto duplicato sul lato ricevente
Numero di serie
Utilizzato per la numerazione sequenziale dei pacchetti di dati trasmessi dal mittente al destinatario. Le lacune nei numeri di sequenza dei pacchetti ricevuti consentono al ricevitore di rilevare la perdita di pacchetti. Gli stessi numeri di sequenza dei pacchetti indicano che i pacchetti sono duplicati l'uno dell'altro
Conferma
Generato dal destinatario e indica al mittente che il pacchetto o il gruppo di pacchetti corrispondente è stato ricevuto con successo. In genere la conferma contiene i numeri di sequenza dei pacchetti ricevuti con successo. A seconda del protocollo si distinguono conferme individuali e di gruppo
Conferma negativa
Utilizzato dal destinatario per informare il mittente che il pacchetto è stato ricevuto in modo errato. Una conferma negativa solitamente include il numero di sequenza del pacchetto che non è stato ricevuto correttamente
Finestra, trasportatore
Limita l'intervallo di numeri di sequenza che possono essere utilizzati per trasmettere i pacchetti. Il multicast e l'handshake possono aumentare significativamente la velocità effettiva del protocollo rispetto all'attesa dei riconoscimenti. Come vedremo, la dimensione della finestra può essere calcolata in base alle capacità di ricezione e buffering del destinatario, nonché al livello di carico della rete
Altri esempi di utilizzo di Go per il networking
В
Fonte: habr.com