
à ceux qui s'intéressent aux réseaux et aux protocoles.
En résumé
Cet article aborde les bases de la transmission fiable des données, avec des exemples sur , y compris UDP et TCP. Inspiré par , , et le livre "Réseaux informatiques. Approche descendante", car tout le monde parle seulement de Tanenbaum et d'Olifer.
Le protocole de transport
assure une connexion logique entre les processus applicatifs s'exécutant sur différents hÎtes. La connexion logique du point de vue des applications apparaßt comme un canal reliant directement les processus.

sont pris en charge par les systĂšmes finaux, mais pas par les routeurs de rĂ©seau (sauf pour â ). Du cĂŽtĂ© de l'Ă©metteur, le niveau de transport convertit les donnĂ©es du niveau applicatif, reçues du processus applicatif Ă©metteur, en paquets de transport, appelĂ©s segments.

Cela se fait en fragmentant (si nĂ©cessaire) les messages du niveau applicatif en morceaux et en ajoutant un en-tĂȘte de niveau de transport Ă chacun d'eux.

Ensuite, le niveau de transport transmet le segment au niveau rĂ©seau de l'Ă©metteur, oĂč le segment est encapsulĂ© dans un paquet de niveau rĂ©seau (datagramme) et envoyĂ©. Du cĂŽtĂ© rĂ©cepteur, le niveau rĂ©seau extrait le segment de niveau transport du datagramme et le transmet au niveau de transport. Ensuite, le niveau de transport traite le segment reçu de maniĂšre Ă rendre ses donnĂ©es accessibles Ă l'application rĂ©ceptrice.

Principes de transmission fiable des données
Transmission fiable des données sur un canal parfaitement fiable
C'est le cas le plus simple. La partie émettrice reçoit simplement les données du niveau supérieur, crée un paquet contenant ces données et l'envoie dans le canal.
Serveur
package main
import (
"log"
"net"
)
func main() {
// Adresse IP du serveur et port
serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:12000")
if err != nil {
log.Fatal(err)
}
// créer un socket avec le port
serverConn, err := net.ListenUDP("udp", serverAddr)
if err != nil {
log.Fatal(err)
}
// fermeture différée de la connexion
defer serverConn.Close()
// créer un buffer pour les données
buf := make([]byte, 1024)
// attendre la connexion
for {
// lire la demande
n, addr, err := serverConn.ReadFromUDP(buf)
// transmettre les donnĂ©es au NIVEAU SUPĂRIEUR : dans notre cas stdout
println(string(buf[0:n]), " form ", addr.IP.String())
if err != nil {
log.Fatal(err)
}
// pas de réponse, car c'est UDP + canal fiable
}
}Client
package main
import (
"fmt"
"log"
"net"
"time"
)
func main() {
// Adresse IP du serveur et port
serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:12000")
if err != nil {
log.Fatal(err)
}
// Adresse IP locale et port
localAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
if err != nil {
log.Fatal(err)
}
// Ătablissement de la connexion
conn, err := net.DialUDP("udp", localAddr, serverAddr)
if err != nil {
log.Fatal(err)
}
// Fermeture décalée de la connexion
defer conn.Close()
for {
// RĂ©ception de donnĂ©es de la COUCHE SUPĂRIEURE
fmt.Print("Entrez une phrase en clair > ")
var msg string
_, err := fmt.Scanf("%s", &msg)
if err != nil {
log.Fatal(err)
}
// Un flux de bytes est transmis, pas une chaĂźne
buf := []byte(msg)
// Ăcriture (transmission) dans la connexion
_, err = conn.Write(buf)
if err != nil {
log.Fatal(err)
}
// Une seconde
time.Sleep(time.Second * 1)
}
}Transmission fiable des données via un canal avec d'éventuelles erreurs
L'Ă©tape suivante consiste Ă supposer que tous les paquets transmis sont reçus dans l'ordre dans lequel ils ont Ă©tĂ© envoyĂ©s, mais que des bits peuvent ĂȘtre endommagĂ©s, car le canal transmet parfois des donnĂ©es de maniĂšre altĂ©rĂ©e.

Dans ce cas, des mécanismes sont appliqués :
- détection des erreurs ;
- retour d'information ;
- retransmission.
Les protocoles de transmission fiable des données qui possÚdent de tels mécanismes de retransmission sont appelés protocoles à demande de retransmission automatique (Automatic Repeat reQuest, ARQ).
De plus, il convient de prévoir la possibilité d'erreurs dans les accusés de réception, lorsque la partie réceptrice ne reçoit aucune information sur les résultats de la transmission du dernier paquet.
La solution à ce problÚme, utilisée notamment dans TCP, consiste à ajouter un nouveau champ contenant le numéro de séquence du paquet dans le paquet de données.

Transmission fiable des données sur un canal non fiable, permettant la distorsion et la perte de paquets
Malheureusement, en plus de ces distorsions, la perte de paquets est également présente sur le réseau.
Et pour résoudre ce problÚme, des mécanismes sont nécessaires :
- détection de la perte de paquets ;
- retransmission des paquets perdus à la partie réceptrice.
En plus de la perte de paquets, il est nĂ©cessaire de prĂ©voir la possibilitĂ© de la perte d'accusĂ©s de rĂ©ception ou, si rien n'est perdu, de leur livraison avec un retard significatif. Dans tous les cas, la mĂȘme action est effectuĂ©e : la retransmission du paquet. Un minuteur est utilisĂ© pour contrĂŽler le temps dans ce mĂ©canisme, permettant de dĂ©terminer la fin de l'intervalle d'attente. Ainsi, dans le paquet le paramĂštre TCPKeepAlive est dĂ©fini sur 15 secondes par dĂ©faut :
// defaultTCPKeepAlive is a default constant value for TCPKeepAlive times
// See golang.org/issue/31510
const (
defaultTCPKeepAlive = 15 * time.Second
)Le cĂŽtĂ© Ă©metteur doit dĂ©marrer le minuteur chaque fois qu'il transmet un paquet (que ce soit lors de la premiĂšre transmission ou de la retransmission), gĂ©rer les interruptions du minuteur et l'arrĂȘter.
Ainsi, nous avons examiné les concepts clés des protocoles de transmission de données fiables :
- les sommes de contrĂŽle ;
- les numéros de séquence des paquets ;
- les minuteurs ;
- les accusés de réception positifs et négatifs.
Mais ce n'est pas tout !
Le protocole de transmission de données fiables avec mise en pipeline
Dans la version que nous avons déjà examinée, le protocole de livraison fiable est trÚs inefficace. Il commence à « freiner » la transmission assurée par le canal de communication lorsque le RTT augmente. Pour améliorer son efficacité et mieux utiliser la bande passante du canal de communication, la mise en pipeline est appliquée.

L'application de la mise en pipeline conduit Ă :
- une augmentation de la plage des numĂ©ros de sĂ©quence, car tous les paquets envoyĂ©s (Ă l'exception des retransmissions) doivent ĂȘtre identifiables de maniĂšre unique ;
- un besoin accru de buffers du cÎté de l'émetteur et du récepteur.
La plage des numéros de séquence et les exigences concernant la taille des buffers dépendent des actions entreprises par le protocole en réponse à la distorsion, à la perte et au retard des paquets. Dans le cas de la mise en pipeline, il existe deux méthodes de correction des erreurs :
- retourner Ă N paquets en arriĂšre ;
- répétition sélective.
Retourner Ă N paquets en arriĂšre â le protocole Ă fenĂȘtre glissante

L'expéditeur doit prendre en charge trois types d'événements :
- appel via un protocole de niveau supĂ©rieur. Lorsque la fonction d'envoi de donnĂ©es est appelĂ©e « d'en haut », la partie Ă©mettrice vĂ©rifie d'abord le degrĂ© de remplissage de la fenĂȘtre (c'est-Ă -dire la prĂ©sence de N messages envoyĂ©s en attente d'accusĂ©s de rĂ©ception). Si la fenĂȘtre n'est pas remplie, un nouveau paquet est formĂ© et transmis, et les valeurs des variables sont mises Ă jour. Dans le cas contraire, la partie Ă©mettrice renvoie les donnĂ©es au niveau supĂ©rieur, ce qui implique de maniĂšre implicite que la fenĂȘtre est remplie. En gĂ©nĂ©ral, le niveau supĂ©rieur tentera de retransmettre les donnĂ©es aprĂšs un certain temps. Dans une application rĂ©elle, l'expĂ©diteur aurait probablement soit mis en mĂ©moire tampon les donnĂ©es (au lieu de les envoyer immĂ©diatement), soit disposĂ© d'un mĂ©canisme de synchronisation (comme un sĂ©maphore ou un drapeau) permettant au niveau supĂ©rieur d'appeler la fonction d'envoi de donnĂ©es uniquement lorsque la fenĂȘtre n'est pas remplie.
- accusé de réception. Dans le protocole pour un paquet avec le numéro de séquence N, un accusé général est émis, indiquant que tous les paquets avec des numéros de séquence précédant N ont été acceptés avec succÚs.
- expiration du délai d'attente. Pour déterminer les pertes et les retards de paquets et d'accusés de réception, le protocole utilise un minuteur. Si le délai d'attente expire, la partie émettrice renvoie tous les paquets non confirmés.
répétition sélective
Lorsque la taille de la fenĂȘtre et le produit de la bande passante par le dĂ©lai de propagation sont Ă©levĂ©s, un grand nombre de paquets peuvent ĂȘtre en transit. Dans ce cas, une erreur d'un paquet particulier peut entraĂźner la retransmission d'un grand nombre de paquets, dont la plupart n'Ă©taient pas nĂ©cessaires.
Exemple
Meilleures pratiques rassemblĂ©es dans une mise en Ćuvre pratique . Et si quelqu'un sait comment faire mieux â .
Serveur
package main
import (
"bufio"
"fmt"
"log"
"net"
"strings"
)
func main() {
// créer un socket sur le port
ln, err := net.Listen("tcp", ":8081")
if err != nil {
log.Fatalln(err)
}
// attente d'un appel
conn, _ := ln.Accept()
for {
// lecture des données
msg, err := bufio.NewReader(conn).ReadString('n')
if err != nil {
log.Fatalln(err)
}
// affichage du message dans stdout
fmt.Print("Message reçu:", string(msg))
// conversion de la chaĂźne en majuscules
newMsg := strings.ToUpper(msg)
// envoi des données
conn.Write([]byte(newMsg + "n"))
}
}Client
package main
import (
"bufio"
"fmt"
"log"
"net"
"os"
)
func main() {
// établissant la connexion
conn, err := net.Dial("tcp", "127.0.0.1:8081")
if err != nil {
log.Fatalln(err)
}
for {
// lecture des données depuis stdin
reader := bufio.NewReader(os.Stdin)
fmt.Print("Texte Ă envoyer : ")
// ligne par ligne
text, err := reader.ReadString('n')
if err != nil {
log.Fatalln(err)
}
// envoi
fmt.Fprintf(conn, text+"n")
// réception
msg, err := bufio.NewReader(conn).ReadString('n')
if err != nil {
log.Fatalln(err)
}
// affichage de la réponse reçue
fmt.Print("Msg du serveur : " + msg)
}
}Sortie
Mécanismes garantissant une transmission fiable des données et leur utilisation
Le mécanisme
Application, commentaire
Somme de contrĂŽle
Utilisée pour détecter les erreurs de bits dans le paquet transmis
ChronomĂštre
DĂ©lai d'attente et indication de son expiration. Cela signifie qu'il est trĂšs probable que le paquet ou son accusĂ© de rĂ©ception ont Ă©tĂ© perdus lors de la transmission. Dans le cas oĂč le paquet est livrĂ© avec retard mais n'est pas perdu (expiration prĂ©maturĂ©e du dĂ©lai d'attente), ou s'il y a une perte de l'accusĂ© de rĂ©ception, une retransmission entraĂźne un doublon du paquet du cĂŽtĂ© du rĂ©cepteur
Numéro de séquence
Utilisé pour la numérotation séquentielle des paquets de données transmis de l'expéditeur au destinataire. Les écarts dans les numéros de séquence des paquets reçus permettent au destinataire de détecter la perte de paquet. Des numéros de séquence identiques signifient que les paquets se dupliquent
Accusé de réception
Généré par le récepteur et indique à l'expéditeur que le paquet ou le groupe de paquets correspondant a été reçu avec succÚs. En général, l'accusé de réception contient les numéros de séquence des paquets reçus avec succÚs. Selon le protocole, on distingue les accusés de réception individuels et groupés
Accusé de réception négatif
Utilisé par le récepteur pour informer l'expéditeur que le paquet a été reçu de maniÚre incorrecte. L'accusé de réception négatif inclut généralement le numéro de séquence du paquet qui n'a pas été correctement reçu
FenĂȘtre, pipeline
Ils limitent la plage des numĂ©ros de sĂ©quence qui peuvent ĂȘtre utilisĂ©s pour la transmission de paquets. La diffusion groupĂ©e et la poignĂ©e de main permettent d'augmenter considĂ©rablement la capacitĂ© des protocoles par rapport au mode d'attente des accusĂ©s de rĂ©ception. Comme nous le verrons, la taille de la fenĂȘtre peut ĂȘtre calculĂ©e en fonction des capacitĂ©s de rĂ©ception et de mise en tampon du cĂŽtĂ© rĂ©cepteur, ainsi que du niveau de congestion du rĂ©seau.
D'autres exemples d'utilisation de Go pour le travail en réseau.
Dans .
Source : habr.com
