1.
Installation et test de l'application
rdict
1.1.
Installation de l'archive
rdict
Récupérez l'archive
td01.zip
qui se trouve sur la page web du cours
http://www-sop.inria.fr/oasis/Denis.Caromel/ProgRpt/
et installez-la dans le répertoire de votre choix selon la
procédure habituelle
unzip td01.zip
L'application client-serveur
rdict
a été initialement conçue par Comer et Stevens pour servir
d'exemple dans leur livre
Internetworking with TCP/IP Vol 3: Client-Server
programming and Applications
, Prentice-Hall puis a été par la suite successivement modifiée par
Michel Syska, Julien Vayssière (qui a également produit la première
version de ce TD) et Denis Caromel.
1.2. Création des programmes client et serveur
L'archive contient tous les fichiers source en C, le fichier RPCL
rdict.x
ainsi qu'un fichier
Makefile
.
-
Commencez par regarder le
Makefile
afin de comprendre l'appel à
rpcgen
, et la création des exécutables du serveur et du client.
-
Il ne reste plus qu'à lancer
make
, ce qui a pour effet de
-
faire générer par
rpcgen
(avec le fichier RPCL
rdict.x
)
-
le stub côté client
rdict_clnt.c
,
-
le skeleton côté serveur
rdict_svc.c
,
-
un fichier annexe
rdict_xdr.c
dont on ne se souciera pas pour ce premier exercice.
-
compiler le programme client
rdict
et le programme serveur
rdictd
.
Correction |
Option -N pour avoir des procédures à plus d'un argument +
passage de variables C ANSI + C++
Compilation:
(171) dcaromel@lo .../RPCGEN> make
rpcgen -C -N rdict.x
gcc -ansi -D_GNU_SOURCE -g -c -o rdict_clnt.o rdict_clnt.c
gcc -ansi -D_GNU_SOURCE -g -c -o rdict_cif.o rdict_cif.c
gcc -ansi -D_GNU_SOURCE -g -c -o rdict.o rdict.c
gcc -ansi -D_GNU_SOURCE -g -c -o rdict_xdr.o rdict_xdr.c
gcc -ansi -D_GNU_SOURCE -g -lnsl -o rdict rdict_clnt.o rdict_cif.o rdict.o rdict_xdr.o
gcc -ansi -D_GNU_SOURCE -g -c -o rdict_svc.o rdict_svc.c
gcc -ansi -D_GNU_SOURCE -g -c -o rdict_server.o rdict_server.c
gcc -ansi -D_GNU_SOURCE -g -c -o rdict_srp.o rdict_srp.c
gcc -ansi -D_GNU_SOURCE -g -lnsl -o rdictd rdict_svc.o rdict_server.o rdict_srp.o rdict_xdr.o
|
1.3. Test
Pour les besoins du TD, nous ferons tourner le serveur sur la machine
locale. Le client pourra être lancé depuis n'importe quelle machine, y
compris la machine locale.
Pour lancer le serveur :
> rdictd
et pour lancer le client :
> rdict <nom de la machine sur laquelle tourne le serveur>
- Vérifiez qu'un programme RPC a bien été enregistré à votre nom sur la machine.
- Dans le cas de plusieurs serveurs
rdictd
lequel est choisi ?
- Vous pouvez ensuite entrer les commandes suivantes dans
rdict
:
-
I
pour vider le dictionnaire
-
i
pour insérer un mot
-
s
pour supprimer un mot
-
c
pour chercher si un mot existe
-
q
pour quitter
- Utilisez à plusieurs le même serveur, et voyez l'effet de vos requêtes concurrentes.
Correction |
-
Trouver son UID:
rpcinfo (sous Linux: /usr/sbin/rpcinfo -p ) :
program vers proto port
100000 2 tcp 111 portmapper
100000 2 udp 111 portmapper
100007 2 udp 944 ypbind
100007 2 tcp 946 ypbind
300019 1 tcp 976 amd
300019 1 udp 977 amd
100011 1 udp 1012 rquotad
100011 2 udp 1012 rquotad
100005 1 udp 600 mountd
100005 1 tcp 602 mountd
100005 2 udp 605 mountd
100005 2 tcp 607 mountd
100003 2 udp 2049 nfs
100021 1 udp 1030 nlockmgr
100021 3 udp 1030 nlockmgr
100021 1 tcp 1024 nlockmgr
100021 3 tcp 1024 nlockmgr
100024 1 udp 652 status
100024 1 tcp 654 status
100001 5 udp 702 rstatd
100001 3 udp 702 rstatd
100001 2 udp 702 rstatd
100001 1 udp 702 rstatd
390113 1 tcp 7937
805898569 1 udp 1992
805898569 1 tcp 3419
rpcinfo -d rdictd 1
Sorry. You are not root
en fait plusieurs
program version netid address service owner
100000 4 ticots clio.rpc rpcbind superuser
100000 3 ticots clio.rpc rpcbind superuser
100000 4 ticotsord clio.rpc rpcbind superuser
...
100249 1 ticots 000000021( - superuser
805898569 1 udp 0.0.0.0.165.212 - 25250
805898569 1 tcp 0.0.0.0.239.6 - 25250
805898569 1 ticlts 000000026G - 25250
805898569 1 ticotsord 000000026J - 25250
805898569 1 ticots 000000026M - 25250
Voir aussi le Fichier
/etc/rpc
:
RPC program number database The rpc file contains user-readable
names that can be used in place of rpc program numbers. Each line has
the following information: name_of_server_for_rpc_program
rpc_program_number aliases Items are separated by any number of blanks
or tabs, or both.
Note: Attention, sous Solaris, rdictd se détache automatiquement du tty !
-
Le premier ayant le bon numéro de Programme et de Version sera choisi.
|
2. Programmation
2.1. Comprendre ce qu'il se passe
Pour la suite du TD, il est important de bien comprendre la manière
dont les différentes parties de l'application collaborent
entre-elles. En particulier, il est important de faire la différence
entre les fichiers générés par
rpcgen
et ceux fournis dans l'archive
rdict.tar
.
- Du côté client, on pourra par exemple suivre la suite des appels de fonction entre l'entrée d'une commande par l'utilisateur (le code correspondant se trouve dans
rdict.c
et l'appel à la procédure distante correspondante, au niveau du stub client.
- Du côté serveur, on pourra suivre ce qu'il se passe depuis la réception d'un appel de procédure au niveau du stub serveur jusqu'à l'exécution de la fonction demandée.
Correction |
Liste des fichiers et leur utilisation
Nom du fichier |
role |
fichiers non générés |
rdict.x
|
description des méthodes et types accessibles à distance |
rdict.c
|
implémentation du client |
rdict_srp.c
|
implémentation du serveur |
rdict_cif.c
|
client convention (glue avec le stub) |
rdict_server.c
|
serveur convention (glue avec le squelette) |
fichiers générés |
rdict.h
|
fichier header définissant les structures et méthodes générées |
rdict_clnt.c
|
stub client |
rdict_svc.c
|
squelette serveur |
rdict.xdr
|
définition des structures complexes pour le marshalling/unmarshalling |
Généré: rdict.h rdict_clnt.c rdict_svc.c rdict_xdr.c
Source: rdict.x rdict.c rdict_cif.c rdict_server.c rdict_srp.c
Client:
-------
rdict.c:
----main ():
----insertion (Mot)
------------rdict_cif.c: (Convention )
---------------insertion() (Convention )
---------------insertion_1(Mot, handle) (Convention )
-------------------------rdict_clnt.c: ( Genere )
-------------------------------insertion_1(Mot, handle) ( Genere )
----------------------------------clnt_call ( ---, --- ) ( Genere )
--------------------------------------- Run-Time
Serveur:
--------
rdict_svc.c :
------void insertion_1 (arg, reqstp)(Genere)
-----------------rdict_server.c: (Convention )
----------------------insertion_1_svc (arg, reqstp) (Convention )
--------------------------------rdict_srp.c:
---------------------------------------insertion (argp) (Implémentation)
Autre fonctions:
suppress_1_svc (argp->arg1, argp->arg2, reqstp) ( Genere )
|
2.2. Ajout de fonctions simples
Ajoutez quelques fonctions à rdict
, par exemple:
- ajouter une fonction qui retourne le nombre de mots dans le dictionnaire,
- ajouter des traces du côté serveur,
- ajouter une fonction qui retourne le mot suivant un mot donné,
- ...
Correction |
Il faut modifier les 5 fichiers non générés pour ajouter une fonction.
Attention, le nom de la fonction defini dans rdict.x doit être en majuscules.
Il est ensuite converti en minuscules puis la fonction est recherchée dans le code.
Une fonction NBMOTS definie dans rdict.x doit par consequent être
definie comme nbmots dans le code (et non pas nbMots qui ne
fonctionnerait pas, C etant sensible a la casse).
On donne ici une solution possible de ces deux fonctions.
rdict_srp.c
|
/*------------------------------------------------------------------------
* nombre : retourne le nombre de mots dans le Dico
*------------------------------------------------------------------------
*/
int
nombre ()
{
return CompteurMots;
}
/*------------------------------------------------------------------------
* suivant : retourne le mot suivant un mot donne dans le dictionnaire
*------------------------------------------------------------------------
*/
char *
suivant (Mot)
char *Mot;
{
int i;
for (i=0 ; i<CompteurMots; i++)
if (strcmp(Mot, Dictionnaire[i]) == 0) {
if (i+1 < CompteurMots)
return Dictionnaire[i+1];
else
return "Aucun, dernier mot";
}
return "Aucun, Mot non trouve";
}
|
rdict_server.c
|
int* nombre_1_svc(struct svc_req *rqstp)
{
static int resultat;
resultat = nombre();
return(&resultat);
}
char ** suivant_1_svc(char *argp, struct svc_req *rqstp)
{
static char * resultat;
resultat = (char *) suivant(argp);
/* Cast (char *) sinon: Warning: assignment makes pointer from integer without a cast */
return(&resultat);
}
|
rdict_cif.c
|
/*------------------------------------------------------------------------
* nombre - procedure interface client qui appelle nombre_1
*------------------------------------------------------------------------
*/
int nombre()
{
return *nombre_1(handle);
}
/*------------------------------------------------------------------------
* suivant - procedure interface client qui appelle suivant_1
*------------------------------------------------------------------------
*/
char * suivant(char* Mot)
{
return *suivant_1(Mot, handle);
}
|
rdict.c
|
case 'n': /* "nombre" */
printf("Nombre de mots actuels dans le Dico: %d \n", nombre ());
break;
case 'S': /* Mot suivant un mot */
printf("Le mot suivant %s est : %s.\n", Mot, suivant(Mot));
break;
|
rdict.x
|
int NOMBRE(void) = 5; /* 5ieme procedure NEW */
string SUIVANT(string) = 6; /* 6eme procedure NEW */
|
|
2.3. Passage de types complexes dans l'application rdict
Le but de cet exercice est de montrer comment passer un type complexe en résultat d'une fonction RPC-C. Il est intéressant de voir que cela n'est pas simple alors que d'autres langages comme Java font cela de manière transparente.
Nous voulons écrire une fonction tous()
qui retourne tous les mots du dictionnaire. Le résultat de cette fonction sera une liste chaînée de mots. Une liste chaînée n'étant pas un type simple connu de RPCGen. Il faut la décrire dans le fichier rdict.x
. Comme la syntaxe n'est pas facile nous la donnons ici
/* Pour lister tous les mots, Exemple de structure de données complexes */
const MAXMOTLONG = 255; /* Taille maxi mot */
typedef string chaine_mot<MAXMOTLONG>; /* Un mot pour chaînage, indispensable */
typedef struct list_mots* suivant_list; /* Suivant dans la liste */
struct list_mots {
chaine_mot mot; /* Le mot */
suivant_list mot_suivant; /* Pointeur vers le suivant */
};
Ajoutez maintenant dans le fichier rdict.x
la fonction TOUS
qui
retourne un résultat de type suivant_list
.
Modifiez ensuite les autres fichiers (rdict.c
, rdict_cif.c
,
rdict_server.c
et rdict_srp.c
) pour terminer l'implémentation.
/*------------------------------------------------------------------------
* tous : retourne tous les mots du dictionnaire
*------------------------------------------------------------------------
*/
suivant_list
tous ()
{
...
}
Tester que ca fonctionne.
Correction |
rdict_srp.c
|
/*------------------------------------------------------------------------
* tous : retourne tous les mots du dictionnaire
*------------------------------------------------------------------------
*/
suivant_list
tous () {
int i;
suivant_list tmp, result = NULL;
for (i=CompteurMots-1 ; i>=0 ; i--) {
tmp = (suivant_list) malloc (sizeof(list_mots));
tmp->mot = Dictionnaire[i];
tmp->mot_suivant = result;
result = tmp;
}
return result;
}
|
rdict_server.c
|
suivant_list* tous_1_svc(struct svc_req *rqstp)
{
static suivant_list resultat;
resultat = (suivant_list) tous();
return(&resultat);
}
|
rdict_cif.c
|
/*------------------------------------------------------------------------
* tous - procedure interface client qui tous_1
*------------------------------------------------------------------------
*/
suivant_list tous()
{
return *tous_1(handle);
}
|
rdict.c
|
case 't': /* Tous les mots */
printf("Les mots du dictionnaire sont: \n");
lm = (suivant_list) tous();
for ( ; lm != NULL; ) {
printf ("%s \n", lm->mot);
lm = lm->mot_suivant;
}
break;
|
rdict.x
|
suivant_list TOUS () = 7; /* 7eme procedure NEW */
|
|