Cours 7



next up previous
Next: Chapitre 8 Up: Cours systèmes Previous: Chapitre 6

Plan

  • Couches ISO
  • Le modèle client-serveur
  • Adresses réseaux
  • Sockets d'Unix

  • Cours 7

    Réseaux

  • le modèle ISO à 7 couches (1: Physique, 2: Liaison, 3: Réseau, 4: Transport, 5: Session, 6: Présentation, 7: Application).

  • c'est une approche modulaire qui rend indépendants les programmes traitant des diverses couches.

  • plus simple de considérer un modèle simplifié:
    1. Liaison: protocoles physiques, ethernet, token ring, SLIP, circuits (longue distance) ou à paquets (réseaux locaux).
    2. Réseau: IP, IDP, ICMP, ARP, RARP.
    3. Transport: UDP, TCP, PEX.
    4. Application: SMTP, TFTP, FTP, TELNET.

  • Cours 7

    Le modèle client -- serveur

  • Un serveur existe à une certaine ``adresse'' sur le réseau. Des clients peuvent lui émettre des requêtes.
  • Exemples de serveurs: X-windowsX, rshd, rlogind, timed, mountd, nfsd, snmpd, inetd, ...
  • Un serveur bien utile est le serveur de noms, DNS/BIND en Internet; cf. Grapevine (Alto/Dorado, Xerox Parc)
  • La communication avec les serveurs peut se faire à des niveaux d'abstraction plus haut: appel de procédures distantes (remote procedure calls, RPC). Cela pose un problème de représentation des données persistantes pour passer les arguments.
  • Cela permet de résoudre les problèmes de synchronisation sans modifier le noyau d'Unix
  • Ne marche bien que si on utilise select() pour attendre les nouveaux clients, si leur nombre est inconnu à l'avance
  • Et donc autre méthode si on a les processus légers

  • Cours 7

    Autre exemple: X-windows, MIT, Projet Athena,

    Bob Scheifler, Jim Gettys, 1986
  • Le serveur gère l'écran et les événements clavier-souris.
  • Les clients sont compilés avec une librairie Xlib qui transforme les ordres graphiques ou lectures d'événements en écritures ou lectures sur une connexion par socket
  • Le système a été relu et corrigé par la communauté académique et est devenu un standard gratuit
  • Il est compliqué car protocole élémentaire. Il y a au dessus des Toolkits encore non standard: DecWindows, HpToolkit, Interviews, Andrew, OSF-Motif
  • Pour qu'il fonctionne, le noyau Unix doit fournir un driver d'événements.
  • Le serveur envoie au client des événements Expose pour recalculer des rectangles. On minimise les bitmaps cachés.
    TIT     .X WINDOW system : C library and protocol reference
    SLA     .Robert W. Scheifler, James Gettys, Ron Newman. - [s.l.] :
            .Digital Press, 1988. - XXIX-701 p. ; 24 cm.
    
    TIT     .X WINDOW: Applications programming
    SLA     .Eric F. Johnson, Kevin Reichard
            . MIS press, 1989.
    

  • Cours 7

    Un exemple: NFS (Network File Systems) Sun Microsystems, 1984

  • les commandes /etc/mount ou df permettent de voir sur cubitus des lignes:
    poly.polytechnique.fr:/usr/users/cie1   196895  157405   19801    89%
    /usr/users/cie1
    ...
    
    Le sous-arbre /usr/users/cie1 est en fait le sous-arbre /usr/users/cie1 de la machine poly.polytechnique.fr lui même monté sur le device /dev/ra4d.

  • read(), write() sont transformés en requêtes au serveur nfsd de poly. En fait les clients sont 4 processus systèmes biod du noyau de cubitus.

  • /et{c/exports} donne la liste des droits de montage de fichiers, côté serveur. showmounts montre les clients actifs.

  • NFS est un système sans état. Donc non transitif. Il résiste donc bien aux pannes d'un serveur (modulo un risque de non fiabilité).

  • Cours 7

    Client - Serveur sur Internet

  • les adresses IP sont des entiers 32 bits écrits en décimal par octets. Exemple: 192.48.98.1 pour C0306201. Les adresses sont de 4 classes (A <= 0x70000000, B >= 0x80000000, C >= 0xC0000000, D >= 0xE0000000). Elles se trouvent dans /et{c/hosts} où on trouve aussi leur nom symbolique.

  • IP travaille avec des numéros de ports et fournit des checksums inconnus au niveau physique. Un port sert à adresser un serveur ou un processus client. Une connexion est caractérisée par un quintuplet:

    (protocole, adresse_1, port_1, adresse_2, port_2)

    Par exemple:

    (tcp, 128.10.0.3, 1500, 128.10.0.7, 21)

  • ports réservés: FTP = 21 en tcp, TFTP = 69 en udp... (cf /etc/services)

  • ports éphémères: au delà de 1024 pour les clients.

  • 2 modes de transports: datagrammes (UDP), mode connecté (TCP)

  • Cours 7

    Adresses des sockets

  • domaine AF_UNIX: nom de fichier.
    struct  sockaddr_un {
            short   sun_family;     
            char    sun_path[108-4];
    #endif
    };
    
  • domaine AF_INET: numéro internet 192.48.98.1 ou poly.polytechnique.fr. Les fonctions gethostbyaddr ou gethostbyname donnent l'adresse.
    struct in_addr {
            union {
                    struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
                    struct { u_short s_w1,s_w2; } S_un_w;
                    u_long S_addr;
            } S_un;
    
    struct sockaddr_in {
            short   sin_family;
            u_short sin_port;
            struct  in_addr sin_addr;
            char    sin_zero[8];
    };
    
  • les adresses peuvent être de longueur variables. Pour ne pas toujours réserver la taille maximale, on indique leur longueur par un argument supplémentaire dans les fonctions aux quelles on passe une adresse.

  • Cours 7

    Adresses des sockets

  • La commande netstat -a permet de voir les services courants. Exemple: 6000 pour X-windows, ...
  • kpr% date; netstat -a |head -10
    Sun Feb 23 17:27:44 MET 1992
    Active Internet connections (including servers)
    Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
    tcp        0      0  kpriss.inria.fr.1022   margaux.inria.fr.login ESTABLISHED
    tcp        0      0  kpriss.inria.fr.6000   toul.inria.fr.1503     ESTABLISHED
    tcp        0      0  kpriss.inria.fr.1018   givry.inria.fr.login   ESTABLISHED
    tcp        0      0  kpriss.inria.fr.6000   toul.inria.fr.1497     ESTABLISHED
    tcp        0      0  kpriss.inria.fr.1019   poly.polytechniq.login ESTABLISHED
    tcp        0      0  kpriss.inria.fr.1020   toul.inria.fr.login    ESTABLISHED
    ...
    tcp        0      0  *.6000                 *.*                    LISTEN
    tcp        0      0  *.printer              *.*                    LISTEN
    tcp        0      0  *.shell                *.*                    LISTEN
    tcp        0      0  *.login                *.*                    LISTEN
    ...
    udp        0      0  *.1062                 *.*
    udp        0      0  *.ntalk                *.*
    udp        0      0  *.talk                 *.*
    udp        0      0  *.biff                 *.*
    ...
    Active UNIX domain sockets
    Address  Type   Recv-Q Send-Q    Inode     Conn     Refs  Nextref Addr
    c37b8280 stream      0      0        0 c36e5200        0        0
    ...
    c3736d80 stream      0      0 8017b0a0        0        0        0 /dev/printer
    c3736c00 dgram       0      0 8017bce0        0        0        0 /dev/elcscntls
    ckt
    c3736a80 stream      0      0 8017cdb8        0        0        0 /tmp/.X11-unix
    /X0
    
    }

    Cours 7

    Les sockets d'Unix BSD

  • En 4.2BSD, les sockets sont apparues (Leffler, ...). Leurs buts:
    1. transparence: idem pour opérations locales ou à distance
    2. efficacité: cf plus tard dans le noyau
    3. compatibilité: ne pas changer les programmes existants

  • Fonctionnent selon le modèle client-serveur. Exemple: un serveur est /usr/bin/Xwd pour X-windows. Les clients sont les utilitaires X: xterm, xclock, xload, ...

  • La séquence typique d'opérations du serveur est
    fd=socket(domaine, type, protocole);
    bind(fd, adresse, longueur_adresse);
    listen(fd, max_servis);    
    fd1 = accept (fd, adresse_client, longueur_adresse_client);
    read (fd1, ..., ...); 
    write {fd1, ..., ...);
    
  • Le client lui fait
    fd = socket(domaine, type, protocole);
    connect (fd, adresse_serveur, longueur_adresse_serveur);
    read (fd, ..., ...); 
    write (fd, ..., ...);
    

  • Cours 7

    Les sockets (suite)

  • socket crée un file descriptor décrivant une domaine unix AF_UNIX ou internet AF_INET, le type datagramme SOCK_DGRAM ou circuit virtuel SOCK_STREAM, un protocole (en général 0 pour celui par défaut).

  • bind attache une adresse (port) à un socket. Les adresses >= 1024 sont réservées au super-utilisateur.

  • listen donne la longueur maximale de la queue d'attente des demandes de connexions non servies (par exemple 5). Très utile car en général le serveur fait fork() dès qu'il accepte une connexion.

  • accept est une fonction bloquante qui attend les demandes de connexion et qui retourne un nouveau file descriptor qui permettra de faire les entrées/sorties sur la nouvelle connexion.

  • connect est l'opération duale pour le client. On se sert du file descriptor existant pour la nouvelle connexion.

  • Cours 7

    Les sockets (suite)

    La séquence typique est donc ainsi côté serveur:
    int fd, newfd;
    
    if ((fd = socket(...)) < 0)
        erreur ("ouverture socket");
    if (bind(fd, ...) < 0)
        erreur ("bind");
    if (listen (fd, 5) < 0)
        erreur ("listen");
    for (; ;) {
        if ((newfd = accept (fd, ...)) < 0)     /* blocant */
            erreur ("accept");
        if (fork() == 0) {
            close (fd);
            .... /* on continue avec newfd */
            exit (0);
        }
        close (newfd);
    }
    

    Cours 7

    Exemple X-windows en AF_UNIX

    Client en X-windows pour établir une connexion
    #include <sys/un.h>
    #define X_UNIX_PATH "/tmp/.X11-unix/X"
    
    Display *XOpenDisplay (register char *display)
    {
        struct sockaddr_un unaddr;          /* UNIX socket data block */
        struct sockaddr *addr;              /* generic socket pointer */
        int addrlen;                        /* length of addr */
    ...
        unaddr.sun_family = AF_UNIX;
        sprintf (unaddr.sun_path, "%s%d", X_UNIX_PATH, idisplay);
    
        addr = (struct sockaddr *) &unaddr;
        addrlen = strlen(unaddr.sun_path) + sizeof(unaddr.sun_family);
    
        if ((fd = socket (((int) addr->sa_family, SOCK_STREAM, 0)) < 0) {
            return -1;
        }
        if (connect (fd, addr, addrlen) < 0) {
            int olderrno = errno;
            (void) close (fd);
            errno = olderrno;
            return -1;
        }
    ...
        dpy->fd = fd;
        return (dpy);
        }
    

    Cours 7

    Exemple X-windows en AF_UNIX

    2- Serveur X (établissement du service)
    #define X_UNIX_PATH     "/tmp/.X11-unix/X"
    static struct sockaddr_un unsock;
    
    static int
    open_unix_socket ()
    {
        int oldUmask;
        int request;
    
        unsock.sun_family = AF_UNIX;
        oldUmask = umask (0);
        strcpy (unsock.sun_path, X_UNIX_PATH);
        strcat (unsock.sun_path, display);
        unlink (unsock.sun_path);
        if ((request = socket (AF_UNIX, SOCK_STREAM, 0)) < 0) {
            Error ("Creating Unix socket");
            return -1;
        }
        if (bind(request, (struct sockaddr *)&unsock, 
                          strlen(unsock.sun_path)+2))
        {
            Error ("Binding Unix socket");
            close (request);
            return -1;
        }
        if (listen (request, 5))
        {
            Error ("Unix Listening");
            close (request);
            return -1;
        }
        (void)umask(oldUmask);
        return request;
    }
    

    Cours 7

    Exemple X-windows en AF_UNIX

    3- Service d'un nouveau client
    
    long WellKnownConnections;   /* Listener mask */
    long LastSelectMask[mskcnt]; /* mask returned from last select call */
    
    void
    EstablishNewConnections()
    {
        long readyconnections;     /* mask of listeners that are ready */
        int curconn;                  /* fd of listener that's ready */
        register int newconn;         /* fd of new client */
        long connect_time;
        register int i;
        register ClientPtr client;
    
        readyconnections = (LastSelectMask[0] & WellKnownConnections);
        if (!readyconnections)
            return;
        connect_time = GetTimeInMillis();
        /* kill off stragglers */
        for (i=1; i<currentMaxClients; i++) {
        ...
        }
        while (readyconnections) {
            curconn = ffs (readyconnections) - 1;
            readyconnections &= ~(1 << curconn);
            if ((newconn = accept (curconn,
                                   (struct sockaddr *) NULL, 
                                   (int *)NULL)) < 0) 
                continue;
            fcntl (newconn, F_SETFL, FNDELAY);
            ...
         }
         ...
    }
    

    Cours 7

    Le netclient de Doligez

    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netdb.h>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    
    extern int errno;
    
    char *usage = "usage : netclient   [-q]";
    
    int main (argc, argv)
         int argc;
         char *argv [];
    {
      long i;
      int s;
      struct hostent *serverhost;
      struct sockaddr_in serveraddr;
      struct servent *service;
      int service_num;
      char buffer [8192];
      int nbytes, sent;
      char addr [4];
      int ret;
      int use_direct_addr;
      int quiet = 0;
    
      if (argc != 3 && argc != 4){
        fprintf (stderr, "%s\n", usage);
        exit (2);
      }
      if (argc == 4){
        if (!strcmp (argv [3], "-q")){
          quiet = 1;
        }else{
          fprintf (stderr, "%s\n", usage);
          exit (2);
        }
      }
      s = socket (AF_INET, SOCK_STREAM, 0);
      if (s == -1){
        perror ("socket");
        exit (3);
      }
      if (s >= 8 * sizeof (int)){
        fprintf (stderr, "socket file number is too high\n");
        exit (3);
      }
      if (argv [1] [0] >= '0' && argv [1] [0] <= '9'){
        int a0, a1, a2, a3;
        sscanf (argv [1], "%d.%d.%d.%d", &a0, &a1, &a2, &a3);
        addr [0] = a0; addr [1] = a1; addr [2] = a2; addr [3] = a3;
        serverhost = gethostbyaddr (addr, 4, AF_INET);
        if (serverhost == NULL){
          use_direct_addr = 1;
        }else{
          use_direct_addr = 0;
        }
      }else{
        serverhost = gethostbyname (argv [1]);
        use_direct_addr = 0;
      }
      if (serverhost == NULL && !use_direct_addr){
        fprintf (stderr, "unknown host: %s\n", argv [1]);
        exit (3);
      }
      if (argv [2] [0] >= '0' && argv [2] [0] <= '9'){
        service_num = htons (atoi (argv [2]));
      }else{
        service = getservbyname (argv [2], NULL);
        if (service == NULL){
          fprintf (stderr, "unknown service: %s\n", argv [2]);
          exit (3);
        }
        service_num = service->s_port;
      }
      serveraddr.sin_family = serverhost->h_addrtype;
      serveraddr.sin_port = service_num;
      if (use_direct_addr){
        bcopy (addr, &serveraddr.sin_addr, 4);
      }else{
        bcopy (serverhost->h_addr, &serveraddr.sin_addr,
    	   serverhost->h_length);
      }
      if (connect (s, &serveraddr, sizeof (serveraddr)) == -1){
        if (errno == ECONNREFUSED){
          if (!quiet) perror ("connect");
          exit (100);
        }else{
          perror ("connect");
          exit (3);
        }
      }
      switch (fork ()){
      case -1:
        perror ("fork");
        exit (3);
      case 0:
        close (1);
        while (1){
          nbytes = read (0, buffer, 8192);
          if (nbytes == -1){
    	perror ("read (stdin)");
    	shutdown (s, 1);
    	exit (3);
          }
          if (nbytes == 0){
    	shutdown (s, 1);
    	exit (0);
          }
          for (sent = 0; sent < nbytes;){
            ret = write (s, buffer + sent, nbytes - sent);
    	if (ret == -1){
    	  perror ("write (socket)");
    	  exit (3);
    	}
    	sent += ret;
          }
        }
      default:
        close (0);
        while (1){
          nbytes = read (s, buffer, 8192);
          if (nbytes == -1){
    	perror ("read (socket)");
    	close (1);
    	wait (NULL);
    	exit (3);
          }
          if (nbytes == 0){
    	close (1);
    	wait (NULL);
    	exit (0);
          }
          for (sent = 0; sent < nbytes;){
    	ret = write (1, buffer + sent, nbytes - sent);
    	if (ret == -1){
    	  perror ("write (stdout)");
    	  exit (3);
    	}
    	sent += ret;
          }
        }
      }
    }
    

    Cours 7

    Datagrammes

  • Les datagrammes sont utilisés chaque fois qu'un serveur veut communiquer avec beaucoup de clients de manière épisodique. Exemple: socket de contrôle de talk. Attention: avec les datagrammes, les messages ne sont pas sûrs d'arriver, ni dans l'ordre. Le protocole est plus bas, donc plus efficace.
  • recvfrom() et sendto() sont alors utilisés.
  • les datagrammes se servent principalement du protocole udp (User Datagram Protocol)
  • talk se sert de 2 sockets: contrôle en datagrammes (avec le démon talkd), communication en mode connecté (avec l'interlocuteur qui fait talk).

  • Cours 7

    Datagrammes -- 2

    for (;;) {
        extern int errno;
    
        fromlen = sizeof(from);
        cc = recvfrom(0, (char *)&request, sizeof (request), 0,
                      &from, &fromlen);
        if (cc != sizeof(request)) {
           if (cc < 0 && errno != EINTR)
                  perror("recvfrom");
           continue;
        }
        lastmsgtime = time(0);
        swapmsg(&request);
        if (debug) print_request(&request);
        process_request(&request, &response);
        /* can block here, is this what I want? */
        cc = sendto(sockt, (char *) &response,
                    sizeof (response), 0, &request.ctl_addr,
                    sizeof (request.ctl_addr));
        if (cc != sizeof(response))
            perror("sendto");
    }
    

    Cours 7

    Datagrammes -- 3

    /* open the ctl socket */ /* client: ctl.c */
    open_ctl() 
    {
        int length;
    
        ctl_addr.sin_port = 0;
        ctl_addr.sin_addr = my_machine_addr;
        ctl_sockt = socket(AF_INET, SOCK_DGRAM, 0);
        if (ctl_sockt <= 0)
            p_error("Bad socket");
        if (bind(ctl_sockt, &ctl_addr, sizeof(ctl_addr), 0) != 0)
            p_error("Couldn't bind to control socket");
        length = sizeof(ctl_addr);
        if (getsockname(ctl_sockt, &ctl_addr, &length) == -1)
            p_error("Bad address for ctl socket");
    }
    
    kpr% talk levy@toul
    ...
    kpr% netstat -a
    Active Internet connections (including servers)
    tcp        0      0  kpriss.inria.fr.1042   toul.inria.fr.1026     ESTABLISHED
    udp        0      0  kpriss.inria.fr.1416   *.*
    
    tou% talk levy@kpriss
    tou% netstat -a
    Active Internet connections (including servers)
    Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
    tcp        0      0  toul.inria.fr.1026     kpriss.inria.fr.1042   ESTABLISHED
    udp        0      0  toul.inria.fr.1143     *.*
    

    Cours 7

    Les sockets AF_UNIX

  • ce sont les connexions entre processus sur une même machine. Le protocole est supposé plus simple. Exemples: X-windows quand on fait
        % setenv DISPLAY unix:0
    
    ou bien dans lpd.
  • #define SOCKETNAME "/dev/printer"
        /*
         * Restart all the printers.
         */
        startup();
        (void) unlink(SOCKETNAME);
        funix = socket(AF_UNIX, SOCK_STREAM, 0);
        if (funix < 0) {
            logerr("socket");
            exit(1);
        }
    #define    mask(s)    (1 << ((s) - 1))
        omask = sigblock(mask(SIGHUP)|mask(SIGINT)|mask(SIGQUIT)|mask(SIGTERM));
        signal(SIGHUP, cleanup);
        signal(SIGINT, cleanup);
        signal(SIGQUIT, cleanup);
        signal(SIGTERM, cleanup);
        sun.sun_family = AF_UNIX;
        strcpy(sun.sun_path, SOCKETNAME);
        if (bind(funix, &sun, strlen(sun.sun_path) + 2) < 0) {
            logerr("unix domain bind");
            exit(1);
        }
        sigsetmask(omask);
        defreadfds = 1 << funix;
        listen(funix, 5);
        finet = socket(AF_INET, SOCK_STREAM, 0);
    

    Cours 7

    Exercices en TD et à la maison

  • Forts: remote shell
  • Débutants: se connecter à un serveur WWW ou de news et récupérer le contenu d'un URL ou d'un article de news. Ou faire un serveur remote echo. On peut se mettre à deux pour faire ce dernier exemple.