Skip to Content

Les échanges inter-processus

Les échanges inter-processus

Deux programmes ont besoin de communiquer entre eux.

Pour cela, ils peuvent utiliser plusieurs méthodes (je ne recence ici que les trois plus utilisées mais il en existe d’autres)


1) Fichiers d’échanges

Un ou deux fichiers servent de "boite au lettre" pour échanger l’information. Pour éviter les problèmes de concurrence, on peut :

  •  Utiliser un seul fichier et employer la technique du "verrouillage/déverrouillage" (fichier lock)
  •  Utiliser un fichier en écriture pour chaque programme. L’autre ne peut que lire le fichier de son partenaire.
    • Avantages :
      • simple à mettre en oeuvre (opérations sur des fichiers standards)
      • utilisable entre machines distantes (ntfs, smb, http ...)
    • Inconvénients :
      • lent (pas de handshake donc boucles de scrutation)
      • difficile à sécuriser (droits sur les fichiers)

    2) Les flux internes (par invocation)

    Un programme (le père) appelle un second programme (le fils) et établit deux canaux de communication (flux entrant/sortant). Le fils utilise ses entrées/sorties standards comme fichiers de communication.

    • Avantages :
      • rapidité des échanges
      • sécurité des échange (flux privés)
    • Inconvénients :
      • échanges internes uniquement (une seule machine).

    Voir : execve, fork, assignstream, shell (cf R2)

    Remarque 1 : cette méthode est très utilisé pour écrire des front-end. Ex : Apollo ou GQmpeg et mpg123

    Remarque 2 : la commande "shell" n’est qu’un simple appel. On ne peut pas parler de réelle communication par flux.


    3) Les sockets

    Un programme serveur crée un socket (point de communication), paramètre ce socket (bind) et attend (listen) la connexion d’un client.

    Le programme client crée un socket puis essaye de le connecter (connect) au socket du serveur. En l’absence de serveur, il y a échec de connexion. Le serveur accepte (accept) la demande de connexion.

    Chaque socket est vu comme deux fichiers (entrant/sortant) au travers desquels se font les échanges.

    • Avantages :
      • rapidité des échanges
      • relative sécurité des échange (flux privés)
      • clients multiples
    • Inconvénients :
      • complexe à mettre en oeuvre.

    Les sockets peuvent fonctionner en mode en interne (pf_local) ou externe (pf_inet).

  •  En mode interne, le client identifie le serveur par son nom. La connexion reste interne à la machine.
  •  En mode externe, le client identifie le serveur par son adresse IP et par son port de connexion.

    Les deux processus peuvent échanger :

  •  des lignes de texte (les lectures/écritures se font comme pour des fichiers de texte standards)
  •  des bolcs de données (grace à deux instructions spéciales recv() et send() )

    Voir : socket, bind, listen, connect, accept


    Exemples

    Pour une fois et au vu de la complexité du sujet, j’ai décidé de présenter deux exemples en FreePascal. Etant un langage très didactique et donc lisible, les programmeurs n’auront aucun mal à le traduire (les fonctions principales sont génériques).

    Par contre, je n’aborde pas ici le cas pourtant classique du multiclient car il nécessite l’utilisation d’une technique de duplication spécifique au sysex hote (fork, thread...)

    1) Communication par flux internes

    Un premier process appelé "père" invoque un second process indépendant appelé "fils". Tout ce que le fils envoie au père est affiché à l’écran par le père. Les deux process sont tués quand le fils envoie la ligne "Termine".


    Program interprocess_pere;

    { utilisation de la fonction AssignStream }

    Uses oldlinux;

    Var Si,So : Text;
       S : String;
           
    begin
     Writeln ('Pere : J''appelle le fils...');
     Assignstream (Si,So,'./fils_p');
     if linuxerror<>0 then
       begin
         writeln ('AssignStream failed !');
         halt(1);
       end;

     readln (si,S);                                 // Bloc 1
     writeln ('Pere : le fils dit : [',S,']');      //

     writeln ('Pere : Je parle au fils ;-)');

     writeln (so,'Salut fils !');
     if ioresult<>0 then writeln ('Pere : Peux pas parler au fils. :-(');

     readln (si,S);                                 // Bloc 2
     writeln ('Pere : le fils dit : [',S,']');      //

     while not (S='Termine.')  do
       begin
         readln (S);
         writeln (so,S);
         readln (si,S);                             // Bloc 3
         writeln ('Pere : le fils dit : [',S,']');  //
       end;
     
     Writeln ('Pere : Je stoppe la conversation');
    end.


    Program interprocess_fils;

    Var S : String;
           
    begin
       Writeln ('Fils : C''est le fils');   // Bloc 1
       S:='';
       While not (S='Fin') do
         begin
         readln (s);
         if pos ('Salut fils !',S)<>0
            then  Writeln ('Salut pere !')  // Bloc 2
            else  Writeln ('Recu : ',S);    // Bloc 3
         end;

     writeln ('Termine.');

     while not eof (input) do  readln (s);

     close (output);
    end.

    2) Communication par Sockets

    On crée ici un programme serveur capable d’accepter un client. Ce serveur est en écoute sur le port 6543. Il utilise l’interface réseau 127.0.0.1 (lo) ou la première carte réseau trouvée (eth0)


    Program socksvr_inet_IP;  { MODE PF_INET = communication reseau }
                              {Un seul client à la fois}

    { Programme pour tester l'unit Sockets -  Version Serveur }
    { En premier, lancer socksvr_inet_IP pour créer le socket }
    { puis lancer sockcli_inet_IP qui se connectera au socket }
    { Le serveur reste en ligne même si le client disparait.  }
    { Le serveur ne quitte que si le client envoie "FIN"      }

    uses Sockets,BaseUnix,errors;

    const Port=6543;

    function SwapWord(w:word):word;
      begin SwapWord:=((w and $ff) shl 8)+(w shr 8); end;

    Var
      Buffer   : string[255];
      Addr     : TInetSockAddr;
      S        : Longint;
      Sin,Sout : Text;

    procedure Perror (const S:string);
    begin
      writeln(S,strerror(SocketError),'(',SocketError,')');
      halt(100);
    end;

    begin
     Addr.family:=AF_INET;
     Addr.port:=SwapWord(Port);
     Addr.addr:=0;  //Le serveur ecoute sur toute la plage IP

     {création du socket - com reseau - mode connecté}
     { S va contenir le num indentification du socket}
     S:=Socket (AF_INET,SOCK_STREAM,0);
     if SocketError<>0 then Perror ('Serveur : Erreur Socket : ');

     {Fourni au socket S les infos de connexion}
     if not Bind(S,Addr,sizeof(Addr))
       then Perror ('Serveur : Erreur Bind : ');

     {mise en ecoute en mode passif}
     if not Listen (S,1) then  Perror ('Serveur : Erreur Listen : ');
     Writeln('J''attend la connection du Client (le lancer dans une autre console)');

     repeat           {accepter une connexion d'un client}
       if not Accept (S,Addr,Sin,Sout) then Perror ('Serveur : Erreur Accept :');
       Reset(Sin);
       ReWrite(Sout);

       Writeln(Sout,'Message du Serveur');
       Flush(SOut);
       repeat
         Readln(Sin,Buffer);
         Writeln('le Server a recu : ',buffer);
       until (eof(sin))or(buffer='FIN');
     until (buffer='FIN');
     closeSocket(S);
    end.

    La connection à ce serveur peut se faire :

  •  avec un “telnet IPserveur 6543”
  •  avec le petit client ci-dessous (en précisant juste l’adresse IP du serveur)

    Program sockcli_inet_IP; { MODE PF_INET = communication reseau }

    { Program pour tester l'unit Sockets  - Version Client    }
    { En premier, lancer socksvr_inet_IP pour créer le socket }
    { puis lancer sockcli_inet_IP qui se connectera au socket }
    { Le serveur reste en ligne même si le client disparait.  }
    { Le serveur ne quitte que si le client envoie "FIN"      }
    { Le client peut quitter sans tuer le serveur en tapant "QUITTE"}

    uses Sockets, errors;

    const Port=6543;

    procedure PError(const S : string);
     begin
       writeln(S,strerror(SocketError),'(',SocketError,')');
       halt(100);
     end;

    function SwapWord(w:word):word;
      begin SwapWord:=((w and $ff) shl 8)+(w shr 8); end;

    function StrToAddr(s : String) : LongInt;
     var
        r, i, p, c : LongInt;
        t : String;
     begin
        StrToAddr := 0;
        r := 0;
        for i := 0 to 3 do
        begin
           p := Pos('.', s);
           if p = 0 then p := Length(s) + 1;
           if p <= 1 then exit;
           t := Copy(s, 1, p - 1);
           Delete(s, 1, p);
           Val(t, p, c);
           if (c <> 0) or (p < 0) or (p > 255) then exit;
           r := r or p shl (i * 8);
        end;
        StrToAddr := r;
     end;

    Var
      Buffer   : string [255];
      Addr     : TInetSockAddr;
      S        : Longint;
      Sin,Sout : Text;
      i        : integer;
      msg      : string;

    begin
     Addr.family:=AF_INET;
     Addr.port:=SwapWord(Port);
     write('Entrer adresse IP (127.0.0.1 pour localhost): '); readln(msg);
     Addr.addr:=StrToAddr(msg);

     {création du socket - com reseau - mode connecté}
     S:=Socket (AF_INET,SOCK_STREAM,0);
     if SocketError<>0 then Perror('Client : Erreur Socket : ');

     {connection au serveur}
     if not Connect (S,Addr,Sin,Sout)
      then PError('Client : Erreur Connect : ');

     Reset(Sin);
     ReWrite(Sout);

     Buffer:='le Client est en ligne.';
     for i:=1 to 10 do Writeln(Sout,Buffer);

     repeat
       Readln(Buffer);
       Writeln(Sout,buffer);
       Flush(Sout);
     until (Buffer='FIN')or(Buffer='QUITTE');

     Readln(SIn,Buffer);
     WriteLn(Buffer);
     close(sout);
     closeSocket(S);
    end.


    Dans ce domaine très large qu’est la communication par sockets, je n’ai rien inventé bien sûr et vous trouverez sur le net beaucoup d’exemples et de tutoriaux.

    Pour en savoir plus, voilà quelques bonnes adresses :

  •  http://www.linux-france.org/article/man-fr/man2/Index-2.html
  •  http://www.commentcamarche.net/sockets/sockcomm.php3
  •  http://www.bastisoft.de/pascal/pasinet.html