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 :
- 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).
Les deux processus peuvent échanger :
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 :
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 :