Au niveau du client ou
talker, l'objectif est d'ouvrir une nouvelle prise réseau ou
socket ; ce qui revient à ouvrir un
canal de communication réseau avec la fonction socket()
après avoir défini les paramètres de
l'hôte à contacter avec la fonction getaddrinfo()
.
Les bibliothèques standard associées au Langage C fournissent à la fois des sous-programmes et des définitions d'enregistrements (ou structures) de données. On rejoint ici le mode opératoire des langages orientés objet dont les bibliothèques fournissent des classes comprenant respectivement les définitions des méthodes et les attributs des données à manipuler.
Ainsi, la fonction getaddrinfo()
manipule des enregistrements de type addrinfo
. Pour utiliser cette
fonction, on commence par orienter le choix du type de prise réseau
à ouvrir avant de l'appeler en affectant certains champs de
l'enregistrement hints
puis
on exploite les résultats contenus dans l'enregistrement servinfo
après l'avoir appelée.
hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM; if ((status = getaddrinfo(msg, serverPort, &hints, &servinfo)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status)); exit(EXIT_FAILURE); }
|
|
|
|
La chaîne de caractères |
L'appel à la fonction socket()
se
fait sur la base des champs renseignés par la fonction getaddrinfo()
. Le premier enregistrement de la
chaîne pointée par servinfo
contient tous les paramètres nécessaires.
if ((socketDescriptor = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol)) == -1) { perror("socket:"); exit(EXIT_FAILURE); }
Une fois le canal de communication réseau correctement ouvert,
on peut passer à l'émission des datagrammes avec la fonction
sendto
.
while (strcmp(msg, ".")) { if ((msgLength = strlen(msg)) > 0) { // Envoi de la ligne au serveur if (sendto(socketDescriptor, msg, msgLength, 0, servinfo->ai_addr, servinfo->ai_addrlen) == -1) { perror("sendto:"); close(socketDescriptor); exit(EXIT_FAILURE); }
|
|
|
|
On utilise à nouveau les champs de l'enregistrement pointé par
|
Comme le service d'échange de datagrammes entre deux hôtes, n'est pas orienté connexion, le protocole UDP de la couche transport n'offre aucune garantie sur la délivrance de ces datagrammes. En conséquence, il incombe au programme utilisateur de fournir une forme de contrôle d'erreur. La solution généralement adoptée consiste à utiliser une temporisation d'attente de réponse. Dans notre cas, si aucune réponse du serveur ou listener n'a été reçue au bout d'une seconde, on peut considérer que le datagramme émis a été perdu.
C'est la fonction select()
qui
permet de superviser des descripteurs de flux tels que les prises
réseau (socket).
// 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); }
|
|
|
|
|
Enfin, il ne reste plus que la réception du ou des datagrammes
renvoyés par le serveur à l'aide de la fonction recv()
.
Pour toute information complémentaire sur les fonctions
utilisées, consulter les pages de manuels correspondantes. Pour la
fonction socket
on peut utiliser
man 2 socket
ou
man 7 socket
par
exemple.
Code du programme udp-talker.c
:
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <netdb.h> #include <netinet/in.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; struct timeval timeVal; fd_set readSet; char msg[MSG_ARRAY_SIZE], serverPort[PORT_ARRAY_SIZE]; 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_INET; hints.ai_socktype = SOCK_DGRAM; if ((status = getaddrinfo(msg, serverPort, &hints, &servinfo)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status)); exit(EXIT_FAILURE); } if ((socketDescriptor = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol)) == -1) { perror("socket:"); 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."); 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, servinfo->ai_addr, servinfo->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); return EXIT_SUCCESS; }