Master 1 Informatique Programmation Répartie et Architecture N Tiers
 

TD-TP - n° 1 : Programmation Distribuée en C avec RPC

Durée: 3H

 
Denis Caromel , Brian Amedro
Université Nice-Sophia Antipolis , Département Informatique
 
CORRECTION
 

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 .

  1. Commencez par regarder le Makefile afin de comprendre l'appel à rpcgen , et la création des exécutables du serveur et du client.
  2. Il ne reste plus qu'à lancer make , ce qui a pour effet de
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>
  1. Vérifiez qu'un programme RPC a bien été enregistré à votre nom sur la machine.
  2. Dans le cas de plusieurs serveurs rdictd lequel est choisi ?
  3. Vous pouvez ensuite entrer les commandes suivantes dans rdict :
  4. Utilisez à plusieurs le même serveur, et voyez l'effet de vos requêtes concurrentes.
Correction
  1. 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 !

  2. 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.

  1. 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.
  2. 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:
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             */