A aquells que
Breument
L'article tracta els conceptes bàsics de la transmissió de dades fiable, implementa exemples
Protocol de la capa de transport
Proporciona una connexió lògica entre els processos d'aplicació que s'executen en diferents amfitrions. Des de la perspectiva de l'aplicació, una connexió lògica sembla un canal que connecta directament processos.
Això es fa dividint (si cal) els missatges de la capa d'aplicació en fragments i afegint una capçalera de capa de transport a cadascun d'ells.
A continuació, la capa de transport passa el segment a la capa de xarxa del remitent, on el segment s'encapsula en un paquet de capa de xarxa (datagrama) i s'envia. A l'extrem receptor, la capa de xarxa extreu el segment de la capa de transport del datagrama i el passa a la capa de transport. A continuació, la capa de transport processa el segment rebut perquè les seves dades estiguin disponibles per a l'aplicació receptora.
Principis de transmissió fiable de dades
Transmissió de dades fiable per un canal completament segur
El cas més senzill. El costat emissor simplement rep les dades de la capa superior, crea un paquet que les conté i les envia al canal.
Servidor
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 + надежный канал
}
}
Client
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)
}
}
Transmissió de dades fiable per un canal amb possibles errors
El següent pas és assumir que tots els paquets transmesos es reben en l'ordre en què van ser enviats, però els bits que contenen poden estar danyats a causa del fet que el canal de vegades transmet dades amb distorsions.
En aquest cas, s'utilitzen els mecanismes següents:
- detecció d'errors;
- retroalimentació;
- retransmissió.
Els protocols de transferència de dades fiables que tenen mecanismes similars per repetir la transmissió diverses vegades s'anomenen protocols de repetició automàtica (ARQ).
A més, val la pena considerar la possibilitat d'errors en els rebuts, quan la part receptora no rebrà cap informació sobre els resultats de la transferència de l'últim paquet.
La solució a aquest problema, també utilitzat en TCP, és afegir un nou camp al paquet de dades que conté el número de seqüència del paquet.
Transmissió de dades fiable per un canal poc fiable subjecte a distorsió i pèrdua de paquets
Juntament amb la distorsió, malauradament, hi ha pèrdua de paquets a la xarxa.
I per resoldre aquest problema, calen mecanismes:
- determinar el fet de la pèrdua de paquets;
- tornar a lliurar els paquets perduts a la part receptora.
Addicionalment, a més de la pèrdua del paquet, cal preveure la possibilitat de pèrdua del rebut o, si no es perd res, el seu lliurament amb un retard important. En tots els casos es fa el mateix: es retransmet el paquet. Per controlar el temps, aquest mecanisme utilitza un temporitzador de compte enrere, que permet determinar el final de l'interval d'espera. Així que al paquet
// defaultTCPKeepAlive is a default constant value for TCPKeepAlive times
// See golang.org/issue/31510
const (
defaultTCPKeepAlive = 15 * time.Second
)
El costat emissor ha d'iniciar un temporitzador cada vegada que es transmet un paquet (tant el primer com el segon), gestionar les interrupcions del temporitzador i aturar-lo.
Per tant, ens hem familiaritzat amb els conceptes clau dels protocols de transferència de dades fiables:
- sumes de control;
- números de seqüència de paquets;
- temporitzadors;
- rebuts positius i negatius.
Però això no és tot!
Protocol de transferència de dades fiable amb canalització
En la variant que ja hem considerat, el protocol de lliurament fiable és molt ineficient. Comença a "ralentir" la transmissió proporcionada pel canal de comunicació a mesura que augmenta l'RTT. Per augmentar la seva eficiència i aprofitar millor la capacitat del canal de comunicació, s'utilitza la canalització.
L'ús de canonades condueix a:
- augmentar el rang de números de seqüència, ja que tots els paquets enviats (excepte les retransmissions) s'han d'identificar de manera única;
- la necessitat d'augmentar els buffers als costats transmissor i receptor.
L'interval de nombres de seqüència i els requisits de mida de la memòria intermèdia depenen de les accions que el protocol faci en resposta a la corrupció, la pèrdua i el retard de paquets. En el cas de la canalització, hi ha dos mètodes per corregir errors:
- retornar N paquets;
- repetició selectiva.
Tornant enrere N paquets - protocol de finestra lliscant
El remitent ha de suportar tres tipus d'esdeveniments:
- trucada per un protocol de nivell superior. Quan la funció d'enviament de dades s'anomena "des de dalt", el costat d'enviament comprova primer el grau d'ompliment de la finestra (és a dir, la presència de N missatges enviats a l'espera de rebre rebuts). Si la finestra està buida, es genera i es transmet un nou paquet i s'actualitzen els valors de les variables. En cas contrari, el costat d'enviament retorna les dades a la capa superior, i això és una indicació implícita que la finestra està plena. Normalment, la capa superior intentarà transmetre les dades de nou al cap d'un temps. En una aplicació real, el remitent probablement guardaria les dades a la memòria intermèdia (en lloc d'enviar-les immediatament) o disposaria d'un mecanisme de sincronització (com ara un semàfor o una bandera) que permetria que la capa superior cridés la funció d'enviament només quan la finestra estigui buida. .
- rebent confirmació. En el protocol, per a un paquet amb el número de seqüència N, s'emet un reconeixement general que indica que tots els paquets amb números de seqüència anteriors a N s'han rebut correctament.
- l'interval d'espera ha expirat. Per determinar els fets de pèrdues i retards de paquets i rebuts, el protocol utilitza un temporitzador. Si l'interval de temps d'espera expira, el costat emissor torna a enviar tots els paquets enviats sense reconèixer.
Repetició selectiva
Quan la mida de la finestra i el producte de retard de propagació de rendiment són grans, pot ser que hi hagi un gran nombre de paquets en procés. En aquest cas, un sol error de paquet pot provocar que es retransmeti un gran nombre de paquets, la majoria dels quals no eren necessaris.
Exemple
Superior
Servidor
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"))
}
}
Client
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)
}
}
Sortida
Mecanismes per garantir la transferència i l'ús fiable de les dades
Mecanisme
Aplicació, comentari
Comprova la suma
S'utilitza per detectar errors de bits en un paquet transmès
temporitzador
Compta enrere l'interval de temps d'espera i indica quan ha expirat. Això últim significa que amb un alt grau de probabilitat el paquet o la seva recepció es perd durant la transmissió. Si un paquet s'entrega amb un retard, però no es perd (expiració prematura de l'interval de temps d'espera) o es perd un rebut, la retransmissió condueix a un paquet duplicat al costat receptor.
Número de sèrie
S'utilitza per a la numeració seqüencial dels paquets de dades transmesos del remitent al destinatari. Els buits en els números de seqüència dels paquets rebuts permeten al receptor detectar la pèrdua de paquets. Els mateixos números de seqüència de paquets signifiquen que els paquets són duplicats els uns dels altres
Confirmació
Generat per l'extrem receptor i indicant a l'extrem emissor que el paquet o grup de paquets corresponent s'ha rebut correctament. Normalment, el reconeixement conté els números de seqüència dels paquets rebuts amb èxit. Segons el protocol, es distingeixen confirmacions individuals i grupals
Confirmació negativa
Utilitzat pel destinatari per informar al remitent que el paquet s'ha rebut incorrectament. Un reconeixement negatiu sol incloure el número de seqüència del paquet que no s'ha rebut correctament
Finestra, transportador
Limiteu el rang de números de seqüència que es poden utilitzar per transmetre paquets. La multidifusió i l'encaix de mans poden augmentar significativament el rendiment del protocol en comparació amb l'espera d'avisos de recepció. Com veurem, la mida de la finestra es pot calcular en funció de les capacitats de recepció i memòria intermèdia de l'extrem receptor, així com el nivell de càrrega de la xarxa.
Més exemples d'ús de Go per a xarxes
В
Font: www.habr.com