Test TP n° 1 de réseau
(21 décembre 2001 – durée : 2h)
© D. Mathieu mathieu@romarin.univ-aix.fr
M. Laporte et C.Pain-Barre
I.U.T.d'Aix en Provence - Département Informatique
Créé le17/12/2001 -
Dernière mise à jour : 17/12/2001

Tout document autorisé
Sommaire
Connexion non bloquante
Connexions multiples
I - Connexion non bloquante
Rappelons qu'une application cliente est bloquée dans l'appel de la fonction connect() pendant tout le temps que le système (plus précisément TCP) effectue la "triple poignée de mains" avec le serveur, ou plus précisément jusqu'à ce que TCP reçoive l'acquittement ACK du paquet de synchronisation SYN qu'il a émis.
Cela peut aller de quelques milli-secondes (sur un réseau local LAN) jusqu'à quelques secondes sur un grand réseau (WAN).
Comme il a été indiqué par ailleurs au cours des TPs, TCP arme lui-même un sablier, et l'appel à la fonction connect() échoue (renvoie -1 avec errno == ETIMEDOUT) lorsque le délai arrive à expiration.
Malheureusement, ce délai est souvent trop long et l'utlisateur peut vouloir le raccourcir.
Dans le TP consacré aux sockets connectées Internet, nous avons montré comment résoudre ce problème au moyen d'une alarme gérée à l'intérieur de la fonction ClientConnectSockIn().
Il existe une autre possibilité grâce à la fonction connect() non bloquante : c'est l'objet de cet exercice.
Comme pour tous les autres supports (pipes, terminaux, etc), toutes les opérations sur un descripteur peuvent être rendues non bloquantes au moyen de la fonction fcntl(), comme cela a été étudié dans l'exo_02 du TP consacré à l'étude du modèle Producteurs/Consommateurs.
Dans le cas d'un socket descriptor, l'appel à la fonction connect() n'est plus bloquante, elle renvoie 0 en cas de succès (en particulier lorsque le serveur est sur la même machine que le client), et -1 en cas d'échec, avec errno == EINPROGRESS.
D'autre part, TCP informe l'application du succès ou de l'échec de la connexion par un événement d'écriture.
En d'autres termes, si le client ajoute le descripteur de socket à un masque d'écriture et s'il appelle la fonction select(), il est débloqué par cet événement.
La fonction select() permettant l'utilisation d'un sablier, cette solution est une alternative à celle qui a été proposée.
Travail demandé
N'indiquer ni les fichiers inclus, ni les espaces de noms utilisés
Dans le fichier nsNet.cxx, réécrire le corps de la fonction nsNet::ClientConnectSockIn() (le profil n'est pas modifié) selon les indications suivantes :
-
initialiser un CTimevalBase à la valeur de TimeOut passée en paramètre,
-
positionner le flag du socket descriptor pour rendre la socket non bloquante.
Il est possible que certains flags aient déjà été positionnés avant l'appel de la fonction ClientConnectSockIn().
Lors de la construction du troisième paramètre de Fcntl(), il importe donc d'ajouter le flag O_NONBLOCK aux autres options déjà positionnées.
Cela ne peut être fait qu'en commençant par récupérer l'état courant des flags, puis en leur ajoutant O_NONBLOCK.
C'est la valeur récupérée avant cette modification qui devra être utilisée en fin de fonction pour restituer les flags dans leur état initial.
-
faire une tentative de connexion.
En cas d'échec, vérifier que la cause d'erreur est bien celle attendue.
Sinon, lever une exception CExcFctSystFile.
-
attendre dans la fonction Select().
-
en sortie, lever l'exception CException avec la constante CstTimeOut si le délai d'attente a expiré.
-
sinon, c'est qu'un événement d'écriture est arrivé sur le socket descriptor.
Mais cela peut être un succès, ou un dépassement de délai par TCP.
Pour le savoir, il faut tester l'état de la socket au moyen de la fonction système getsockopt(), dont voici quelques extraits du man :
#include <sys/socket.h>
int getsockopt (int sd, int level, int optname, void *optval,
int *optlen); |
sd est le socket descriptor dont on cherche à connaître l'état.
Pour les paramètres level et optname, utiliser respectivement les constantes SOL_SOCKET et SO_ERROR.
Dans ces conditions, la fonction getsockopt() place l'état de la socket dans la variable pointée par optval.
Comme c'est le cas pour d'autres fonctions système déjà utilisées (par exemple la fonction accept()), le paramètre optval est prévu pour pouvoir pointer sur n'importe quoi.
Avant l'appel, optlen est l'adresse d'un entier qui représente la taille mémoire pointée par optval et réservée par l'utilisateur.
En sortie, la fonction getsockopt() affecte à cet entier la taille de l'information qu'elle y a rangée.
Compte tenu de l'option choisie (SO_ERROR), la fonction getsockopt() range dans la variable pointée par optval un code d'erreur qui est un int, dont les valeurs sont expliquées dans la fin du man correspondant à la fonction connect() :
ERREURS
Voici une liste d'erreurs
générales concernant les sockets,
il peut en exister d'autres
spécifiques au domaine employé.
EBADF Mauvais descripteur.
EFAULT La structure d'adresse
de la socket pointe en dehors
de l'espace d'adressage.
ENOTSOCK
Le descripteur ne correspond pas à une socket.
EISCONN
La socket est déjà connectée.
ECONNREFUSED
La connexion est refusée par le serveur.
ETIMEDOUT
Dépassement du délai maximum pendant la connexion.
ENETUNREACH
Le réseau est inaccessible.
EHOSTUNREACH
Le correspondant ne peut pas être joint.
EADDRINUSE
L'adresse est déjà utilisée.
EINPROGRESS
La socket est non-bloquante, et la connexion ne peut
pas être établie immédiatement. Il est alors possible
d'utiliser select(2) pour attendre que la socket soit
disponible en écriture. Une fois que select
confirme la possibilité d'écrire, utilisez
getsockopt(2) pour lire l'option SO_ERROR du niveau
SOL_SOCKET.
Cette option indiquera si connect a réussi (*optval valant
zéro) ou, si une erreur s'est produite (*optval
correspondant à l'un des codes d'erreurs décrits
ci-dessus).
EALREADY
La socket est non-bloquante et une tentative de connexion
précédente ne s'est pas encore terminée. |
La fonction renvoie 0 en cas de succès et -1 en cas d'échec,
errno
contenant alors la cause d'erreur.
-
si la fonction getsockopt() a échoué, lever une
exception CExcFctSystFile.
-
si l'état de la socket renvoyé par getsockopt()
est invalide, fermer la socket et lever une exception CExcFctSystFile
comme si c'était la fonction connect() elle-même
qui avait échoué (attention, à ce moment-là,
errno
contient le compte-rendu du dernier appel système, c'est-à-dire
de getsockopt()), il faut donc exceptionnellement modifier
errno pour lui restaurer sa valeur, car c'est errno qu'utilise
le constructeur CExcFctSystFile) .
-
si la connexion a été obtenue, restaurer les flags
avant de ressortir de la fonction.
Corrigés : nsNet.cxx
Sommaire
II - Connexions multiples
Dans certaines circonstances, une application client
doit établir plusieurs connexions.
C'était le cas par exemple
d'un navigateur (avant la nouvelle version du protocole http)
qui recevait une home page Html, l'analysait, et lançait
autant de connexions qu'il y avait de liens sur des fichiers à rapatrier
(fichiers textes .html pour les frames, fichiers images
.gif,
etc.).
Plutôt que d'effectuer ces différentes
connexions séquentiellement, il est plus efficace de les lancer
en parallèle, sans bloquage, et d'attendre ensuite jusqu'à
un délai maximal. C'est le but de cet exercice.
Travail demandé
N'indiquer ni les fichiers inclus, ni les espaces de
noms utilisés
Dans le fichier nsNet.cxx, écrire le
corps de la fonction nsNet::ClientMultiConnectSockIn()de profil suivant
:
void ClientMultiConnectSockIn (int NbConnect,
int
sd [],
::in_addr Adresse
[],
unsigned short int Port [],
int
CodErr [],
unsigned TimeOut = 0); |
-
NbConnect est le nombre de connexions simultanées.
-
sd est le vecteur des socket descriptors (c'est une donnée : ils ont été initialisés avant l'appel)
-
Adresse et Port sont les vecteurs des adresses IP et des ports auxquels il faut se connecter (données)
-
CodErr est un vecteur résultat rendant compte de l'état de chaque connexion (CstNoError si la connexion a été obtenue, ou toute autre valeur en cas d'erreur).
Contrairement au premier exercice, aucune exception ne sera levée dans les cas à problème, seul un code d'erreur sera positionné :
-
errno en cas d'échec d'une fonction système, ou
-
le résultat de getsockopt() éventuel (c'est-à-dire *optval), ou
-
CstTimeOut si Select() est arrivé à expiration du délai.
Contrairement à l'exercice précédent, l'appel à la fonction Select() est dans une boucle qui se termine lorsqu'il n'y a plus de connexions en suspens, ou lorsque le délai est atteint (on supposera que le délai passé à Select() contient en sortie le reste de temps non écoulé, qui sera utilisé pour réarmer le sablier à la boucle suivante).
Le masque d'écriture utilisé dans l'appel de Select() est construit à partir des sockets descriptors dont la connexion est en suspens.
Corrigés : nsNet.cxx
Sommaire
© D. Mathieu mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique