Til dem, der
Kort
Artiklen diskuterer det grundlæggende i pålidelig datatransmission, implementerer eksempler på
Transportlagsprotokol
Giver en logisk forbindelse mellem applikationsprocesser, der kører på forskellige værter. Fra et applikationsperspektiv ligner en logisk forbindelse en kanal, der direkte forbinder processer.
Dette gøres ved at opdele (om nødvendigt) applikationslagsmeddelelserne i fragmenter og tilføje en transportlagsheader til hver af dem.
Transportlaget sender derefter segmentet videre til afsenderens netværkslag, hvor segmentet indkapsles i en netværkslagspakke (datagram) og sendes. I den modtagende ende udtrækker netværkslaget transportlagsegmentet fra datagrammet og sender det op til transportlaget. Dernæst behandler transportlaget det modtagne segment, så dets data bliver tilgængelige for den modtagende applikation.
Principper for pålidelig datatransmission
Pålidelig dataoverførsel over en fuldstændig sikker kanal
Den enkleste sag. Afsendersiden modtager simpelthen dataene fra det øverste lag, opretter en pakke, der indeholder dem, og sender dem til kanalen.
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 + надежный канал
}
}
Kunde
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)
}
}
Pålidelig datatransmission over en kanal med mulige fejl
Det næste trin er at antage, at alle transmitterede pakker modtages i den rækkefølge, de blev sendt i, men bits i dem kan være beskadiget på grund af det faktum, at kanalen nogle gange transmitterer data med forvrængninger.
I dette tilfælde anvendes følgende mekanismer:
- fejlfinding;
- feedback;
- genudsendelse.
Pålidelige dataoverførselsprotokoller, der har lignende mekanismer til at gentage transmission flere gange, kaldes Automatic Repeat ReQuest (ARQ) protokoller.
Derudover er det værd at overveje muligheden for fejl i kvitteringer, når den modtagende part ikke vil modtage nogen information om resultaterne af overførslen af den sidste pakke.
Løsningen på dette problem, som også bruges i TCP, er at tilføje et nyt felt til datapakken, der indeholder pakkens sekvensnummer.
Pålidelig datatransmission over en upålidelig kanal med forbehold for pakkeforvrængning og tab
Sammen med forvrængning er der desværre pakketab i netværket.
Og for at løse dette problem kræves mekanismer:
- bestemmelse af pakketab;
- genlevering af mistede pakker til den modtagende part.
Derudover er det, ud over tabet af pakken, nødvendigt at sørge for muligheden for tab af kvitteringen eller, hvis intet går tabt, leveringen med en betydelig forsinkelse. I alle tilfælde gøres det samme: pakken gentransmitteres. For at styre tiden bruger denne mekanisme en nedtællingstimer, som giver dig mulighed for at bestemme slutningen af venteintervallet. Altså i pakken
// defaultTCPKeepAlive is a default constant value for TCPKeepAlive times
// See golang.org/issue/31510
const (
defaultTCPKeepAlive = 15 * time.Second
)
Afsendersiden skal starte en timer hver gang en pakke sendes (både første og anden gang), håndtere afbrydelser fra timeren og stoppe den.
Så vi er blevet fortrolige med nøglebegreberne for pålidelige dataoverførselsprotokoller:
- kontrolsummer;
- sekvensnumre på pakker;
- timere;
- positive og negative kvitteringer.
Men det er ikke alt!
Pålidelig dataoverførselsprotokol med pipelining
I den variant, som vi allerede har overvejet, er den pålidelige leveringsprotokol meget ineffektiv. Det begynder at "sænke" transmissionen fra kommunikationskanalen, efterhånden som RTT stiger. For at øge dens effektivitet og bedre udnytte kommunikationskanalkapaciteten anvendes pipelining.
Brugen af pipelining fører til:
- forøgelse af rækken af sekvensnumre, da alle sendte pakker (undtagen gentransmissioner) skal identificeres entydigt;
- behovet for at øge buffere på sende- og modtagesiden.
Kravene til sekvensnummerområdet og bufferstørrelsen afhænger af de handlinger, protokollen udfører som reaktion på pakkekorruption, -tab og -forsinkelse. I tilfælde af pipelining er der to metoder til at rette fejl:
- returnere N pakker tilbage;
- selektiv gentagelse.
Går tilbage N pakker - glidende vinduesprotokol
Afsenderen skal understøtte tre typer begivenheder:
- opkald med en protokol på højere niveau. Når dataafsendelsesfunktionen kaldes "ovenfra", kontrollerer afsendersiden først udfyldningsgraden af vinduet (det vil sige tilstedeværelsen af N sendte beskeder, der afventer modtagelse af kvitteringer). Hvis vinduet er tomt, genereres og transmitteres en ny pakke, og variabelværdierne opdateres. Ellers returnerer afsendersiden data til det øverste lag, og dette er en implicit indikation af, at vinduet er fuldt. Typisk vil det øverste lag forsøge at overføre dataene igen efter nogen tid. I en rigtig applikation vil afsenderen sandsynligvis enten buffere dataene (i stedet for at sende dem med det samme) eller have en synkroniseringsmekanisme (såsom en semafor eller flag), der vil tillade det øverste lag kun at kalde sendefunktionen, når vinduet er tomt .
- modtager bekræftelse. I protokollen, for en pakke med sekvensnummer N, udstedes en generel bekræftelse, der indikerer, at alle pakker med sekvensnumre forud for N blev modtaget med succes.
- venteintervallet er udløbet. For at bestemme fakta om tab og forsinkelser af pakker og kvitteringer, bruger protokollen en timer. Hvis timeout-intervallet udløber, sender afsendersiden alle sendte ikke-bekræftede pakker igen.
Selektiv gentagelse
Når vinduesstørrelsen og gennemløbs-udbredelsesforsinkelsesproduktet er store, kan et stort antal pakker være i pipelinen. I et sådant tilfælde kan en enkelt pakkefejl forårsage, at et stort antal pakker bliver gentransmitteret, hvoraf de fleste ikke var påkrævet.
Eksempel
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"))
}
}
Kunde
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)
}
}
Output
Mekanismer til at sikre pålidelig dataoverførsel og brug
mekanisme
Ansøgning, kommentar
Tjek sum
Bruges til at detektere bitfejl i en transmitteret pakke
timer
Tæller timeout-intervallet ned og angiver, hvornår det er udløbet. Sidstnævnte betyder, at med en høj grad af sandsynlighed går pakken eller dens kvittering tabt under transmissionen. Hvis en pakke leveres med forsinkelse, men ikke går tabt (for tidligt udløb af timeout-intervallet), eller en kvittering går tabt, fører retransmission til en duplikatpakke på modtagersiden
Serienummer
Bruges til sekventiel nummerering af datapakker transmitteret fra afsender til modtager. Huller i sekvensnumrene for modtagne pakker gør det muligt for modtageren at detektere pakketab. De samme pakkesekvensnumre betyder, at pakkerne er dubletter af hinanden
Bekræftelse
Genereret af den modtagende ende og angiver til afsenderenden, at den tilsvarende pakke eller gruppe af pakker er blevet modtaget. Typisk indeholder bekræftelsen sekvensnumrene for vellykket modtagne pakker. Afhængigt af protokollen skelnes individuelle og gruppebekræftelser
Negativ bekræftelse
Bruges af modtageren til at informere afsenderen om, at pakken er modtaget forkert. En negativ bekræftelse inkluderer normalt sekvensnummeret på den pakke, der ikke blev modtaget korrekt
Vindue, transportør
Begræns rækken af sekvensnumre, der kan bruges til at sende pakker. Multicast og håndtryk kan øge protokolgennemstrømningen markant sammenlignet med at vente på bekræftelser. Som vi vil se, kan vinduesstørrelsen beregnes baseret på modtage- og bufferfunktionerne i den modtagende ende såvel som netværksbelastningsniveauet
Flere eksempler på brug af Go til netværk
В
Kilde: www.habr.com