Relativement aux conditions énoncées dans la section précédente (Tableau 2, « Protocole de couche réseau utilisé suivant les conditions de tests »), l'objectif ici est de présenter un programme pour chaque protocole de la couche transport qui fonctionne indifféremment dans les deux modes : dual stack avec IPv4 + IPv6 et single stack avec IPv4 uniquement.
Dans ce but, on reprend le code donné dans la Section 3,
« Utilisation de getaddrinfo() ». Suivant le contenu
des enregistrements de type addrinfo
renseignés par l'appel à
getaddrinfo()
, le choix du protocole
de couche réseau se fait automatiquement à partir de la
configuration système. Sur un système dual
stack l'enregistrement IPv6
est en première position alors que sur un système single stack, seul l'enregistrement
IPv4 est disponible. Par
conséquent, la boucle de scrutation des enregistrements s'arrête à
l'ouverture de la première prise réseau (socket) valide.
Sur un système GNU/Linux, on consulte l'indicateur d'état
disable_ipv6
à l'aide de l'instruction
suivante.
$
cat /proc/sys/net/ipv6/conf/all/disable_ipv6
0
Dans l'infrastructure de test utilisée pour ce document, on a
créé le fichier /etc/sysctl.d/disableipv6.conf
sur le système
vm3.fake.domain
pour désactiver
l'utilisation du protocole IPv6.
Le contenu de ce fichier est le suivant.
$
cat /etc/sysctl.d/disableipv6.conf
net.ipv6.conf.all.disable_ipv6=1
net.ipv6.conf.default.disable_ipv6=1
Boucle de parcours des enregistrements addrinfo
<snipped/> p = servinfo; while((p != NULL) && !sockSuccess) { // Identification de la famille d'adresse if (p->ai_family == AF_INET) puts("Open IPv4 socket"); else puts("Open IPv6 socket"); if ((socketDescriptor = socket (p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { perror("socket:"); sockSuccess = false; // Echec ouverture socket p = p->ai_next; // Enregistrement d'adresse suivant } else // La prise réseau est valide sockSuccess = true; } if (p == NULL) { fputs("Création de socket impossible", stderr); return 2; }
Dès que l'adresse du pointeur |
|
La variable booléenne |
|
Si, en sortie de la boucle de scrutation des enregistrements de
type |
Les autres particularités du programme client UDP ou udp-talker
sont décrites dans le support Initiation au développement
C sur les sockets. Il faut simplement noter que la
fonction select()
est utilisée ici
pour gérer une temporisation d'attente de la réponse du serveur. Le
protocole de couche transport UDP n'est pas orienté connexion et n'offre
aucun service de fiabilisation. Il incombe donc à la couche
application d'assurer une forme de détection d'erreur. Dans notre
cas, le client attend la réponse du traitement effectué par le
serveur pendant une seconde. Si aucune réponse n'a été reçue dans
le temps imparti, il faut considérer que le message émis a été
perdu.
Code source complet du programme udp-talker.c
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> #include <netdb.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #define MAX_PORT 5 #define PORT_ARRAY_SIZE (MAX_PORT+1) #define MAX_MSG 80 #define MSG_ARRAY_SIZE (MAX_MSG+1) // Utilisation d'une constante x dans la définition // du format de saisie #define str(x) # x #define xstr(x) str(x) int main() { int socketDescriptor, status; unsigned int msgLength; struct addrinfo hints, *servinfo, *p; struct timeval timeVal; fd_set readSet; char msg[MSG_ARRAY_SIZE], serverPort[PORT_ARRAY_SIZE]; bool sockSuccess = false; puts("Entrez le nom du serveur ou son adresse IP : "); memset(msg, 0, sizeof msg); // Mise à zéro du tampon scanf("%"xstr(MAX_MSG)"s", msg); puts("Entrez le numéro de port du serveur : "); memset(serverPort, 0, sizeof serverPort); // Mise à zéro du tampon scanf("%"xstr(MAX_PORT)"s", serverPort); memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; if ((status = getaddrinfo(msg, serverPort, &hints, &servinfo)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status)); exit(EXIT_FAILURE); } // Scrutation des résultats et création de socket // Sortie après création de la première prise réseau valide p = servinfo; while((p != NULL) && !sockSuccess) { // Identification de la famille d'adresse if (p->ai_family == AF_INET) puts("Open IPv4 socket"); else puts("Open IPv6 socket"); if ((socketDescriptor = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { perror("socket:"); sockSuccess = false; // Echec ouverture socket p = p->ai_next; // Enregistrement d'adresse suivant } else // La prise réseau est valide sockSuccess = true; } if (p == NULL) { fputs("Création de socket impossible", stderr); return 2; } puts("\nEntrez quelques caractères au clavier."); puts("Le serveur les modifiera et les renverra."); puts("Pour sortir, entrez une ligne avec le caractère '.' uniquement."); puts("Si une ligne dépasse "xstr(MAX_MSG)" caractères,"); puts("seuls les "xstr(MAX_MSG)" premiers caractères seront utilisés.\n"); // Invite de commande pour l'utilisateur et lecture des caractères jusqu'à la // limite MAX_MSG. Puis suppression du saut de ligne en mémoire tampon. puts("Saisie du message : "); memset(msg, 0, sizeof msg); // Mise à zéro du tampon scanf(" %"xstr(MAX_MSG)"[^\n]%*c", msg); // Arrêt lorsque l'utilisateur saisit une ligne ne contenant qu'un point while (strcmp(msg, ".")) { if ((msgLength = strlen(msg)) > 0) { // Envoi de la ligne au serveur if (sendto(socketDescriptor, msg, msgLength, 0, p->ai_addr, p->ai_addrlen) == -1) { perror("sendto"); close(socketDescriptor); exit(EXIT_FAILURE); } // Attente de la réponse pendant une seconde. FD_ZERO(&readSet); FD_SET(socketDescriptor, &readSet); timeVal.tv_sec = 1; timeVal.tv_usec = 0; if (select(socketDescriptor+1, &readSet, NULL, NULL, &timeVal)) { // Lecture de la ligne modifiée par le serveur. memset(msg, 0, sizeof msg); // Mise à zéro du tampon if (recv(socketDescriptor, msg, sizeof msg, 0) == -1) { perror("recv:"); close(socketDescriptor); exit(EXIT_FAILURE); } printf("Message traité : %s\n", msg); } else { puts("Pas de réponse dans la seconde."); } } // Invite de commande pour l'utilisateur et lecture des caractères jusqu'à la // limite MAX_MSG. Puis suppression du saut de ligne en mémoire tampon. // Comme ci-dessus. puts("Saisie du message : "); memset(msg, 0, sizeof msg); // Mise à zéro du tampon scanf(" %"xstr(MAX_MSG)"[^\n]%*c", msg); } close(socketDescriptor); freeaddrinfo(servinfo); return 0; }
Code source du correctif tcp-talker.c.patch
Le passage du protocole de couche transport UDP au protocole TCP ne présente aucune singularité relativement
au document initial : Initiation au développement
C sur les sockets. Le protocole TCP est orienté connexion et assure la
fiabilisation des échanges de bout en bout. Le correctif suivant
fait apparaître l'appel à la fonction connect()
qui correspond à la phase
d'établissement de la connexion. De plus, le recours à la
temporisation d'attente de réponse devient inutile. Les variables
de gestion du temps et l'appel à select()
est donc supprimé.
--- udp-talker.c 2017-10-01 19:38:37.000000000 +0200 +++ tcp-talker.c 2016-10-25 16:22:31.000000000 +0200 @@ -22,8 +22,6 @@ int socketDescriptor, status; unsigned int msgLength; struct addrinfo hints, *servinfo, *p; - struct timeval timeVal; - fd_set readSet; char msg[MSG_ARRAY_SIZE], serverPort[PORT_ARRAY_SIZE]; bool sockSuccess = false; @@ -37,7 +35,7 @@ memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_DGRAM; + hints.ai_socktype = SOCK_STREAM; if ((status = getaddrinfo(msg, serverPort, &hints, &servinfo)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status)); @@ -69,6 +67,12 @@ return 2; } + if (connect(socketDescriptor, p->ai_addr, p->ai_addrlen) == -1) { + perror("connect"); + close(socketDescriptor); + exit(EXIT_FAILURE); + } + puts("\nEntrez quelques caractères au clavier."); puts("Le serveur les modifiera et les renverra."); puts("Pour sortir, entrez une ligne avec le caractère '.' uniquement."); @@ -92,13 +96,6 @@ exit(EXIT_FAILURE); } - // Attente de la réponse pendant une seconde. - FD_ZERO(&readSet); - FD_SET(socketDescriptor, &readSet); - timeVal.tv_sec = 1; - timeVal.tv_usec = 0; - - if (select(socketDescriptor+1, &readSet, NULL, NULL, &timeVal)) { // Lecture de la ligne modifiée par le serveur. memset(msg, 0, sizeof msg); // Mise à zéro du tampon if (recv(socketDescriptor, msg, sizeof msg, 0) == -1) { @@ -106,13 +103,10 @@ close(socketDescriptor); exit(EXIT_FAILURE); } + } printf("Message traité : %s\n", msg); - } - else { - puts("Pas de réponse dans la seconde."); - } - } + // Invite de commande pour l'utilisateur et lecture des caractères jusqu'à la // limite MAX_MSG. Puis suppression du saut de ligne en mémoire tampon. // Comme ci-dessus.