TP Programmation réseau
Envoi de mails
Vérifier (avec la commande whereis sendmail) que votre machine comporte bien le MTA (=mail transport agent)
sendmail. Si c'est le cas, on peut alors l'utiliser dans un script comme agent d'expédition en adressant sur son
entrée standard une chaine correcte.
Rien de plus facile que d'envoyer des messages ! Pour cela il suffit d'ouvrir un fichier en écriture vers sendmail
à travers un pipe. Bien respecter le format, contraignant, du courrier !
Voici un exemple à adapter et à tester :
#!/usr/bin/perl
print "Essai d'envois de mails\n";
$destinataire="nom ";
$nom = $destinataire;
$nom =~ s/^\s*(\w+)\s+<.*$/$1/;
$expediteur="....";
$repondre="....";
# option -t pour que sendmail intégre les en-têtes fournies
open(MAIL,"|/usr/lib/sendmail -t") or die "impossible
d'expédier par sendmail !";
# attention : bien observer la syntaxe de cette chaine
# un seul passage à la ligne (ou \n) entre chaque en-tete
# un : après chaque mot-clé
$message="From: $expediteur
To: $destinataire\nReply-To: $repondre
Subject: Salut !
Bonjour $nom
Le stage n'est pas trop dur ?
Comment vas-tu aujourd'hui ?
xxx";
print MAIL $message;
close MAIL;
Exemple de session FTP
Avec le module Net::FTP, on dispose avec une notation orientée-objet de fonctions de base permettant
d'ouvrir une connexion FTP dans un programme Perl et d'y travailler de la meme façon qu'avec un client
Voici un script sommaire, permettant de récupérer la page ftp://ftp.cpan.org/pub/CPAN/CPAN.html
#!/usr/bin/perl -w
use Net::FTP;
# tenter une connexion, et obtenir un identifiant de connexion
# dans le jargon objet, il s'agit de la construction d'un objet noté ici $ftp
# tant que la session reste ouverte, on adresse des méthodes à cet objet : login,cwd,get,put ..
# documentation à consulter : perldoc Net::FTP
#####################################################
$ftp = Net::FTP -> new (
"ftp.cpan.org",
Passive =>1 )
Timeout => 30
) or die "Connexion impossible\n";
$ftp-> login("anonymous","jean") or die "connexion impossible";
$ftp-> cwd('/pub/CPAN');
$fichier= "CPAN.html";
$local = "cpan.html";
$ftp-> get($fichier, $local) or die "Impossible d'obtenir $fichier !";
# on obtient la page d'accueil : http://www.cpan.org/CPAN.html
Introduction aux sockets
Il ne s'agit ici que d'une brève introduction, en grande partie issue de l'indispensable référence :
http://www.enstimac.fr/Perl/DocFr/perlipc.html
- Les sockets sont les canaux de communications (type principaux streams et datagrammes). Ils "se
branchent" à un hote identifié par son adresse IP et à un port. Deux machines voulant échanger sur le réseau
doivent de plus choisir un protocole de communication, compatible avec le type de socket.
Des modules spécialisés Perl existent heureusement déjà pour faciliter le
dialogue client-serveur en respectant le protocole, et portent des noms comme Net::FTP
- Dialogue client-serveur
Le serveur doit être en exécution et "écoute" sur un port, en attente du client.
Un processus qui se positionne comme client d'un service d'un serveur distant, doit :
- s'adresser d'abord à la bonne machine sur le réseau (local ou Internet), et pour cela fournir son numéro IP
directement, par exemple 66.35.250.175 (ou en utilisant un service DNS pour l'obtenir à partir de son nom
public de serveur, par exemple use.perl.org (devinez qui s'est installé à www.perl.fr/)
- s'adresser au bon service en indiquant son port, numéro qui identifie ce service. L'attribution des numéros de port est
standardisé (de 0 à 65535). Sur une machine Unix, consulter le fichier /etc/services qui est une table de
correspondance (HTTP : 80, FTP : 21 ..)
- créer une socket, un support de communication en direction de ce service, dans le but d'échanger des
données
- si la socket est créée, la connexion acceptée par le serveur, celui-ci renvoie une valeur
scalaire semblable à un descripteur de fichier (ou plutot de flux). Muni de cette référence le client pourra dialoguer
avec le serveur, à la fois en lecture et en écriture.
- Bien entendu par la suite il faut utiliser le langage de commande du
service connecté, par exemple envoyer une chaine commençant par la commande GET pour une requete HTTP, comme le ferait
un client HTTP, un navigateur.
- En mode interactif, la socket du client se met généralement à l'écoute du serveur, de façon semblable à l'attente
d'une saisie clavier vis à vis de l'entrée standard <STDIN>. Si le descripteur de socket s'appelle
$sock, le processus devra s'exécuter dans une boucle du genre :
while ($ligne= <$sock> ) { ....}.
Il faut dans ces conditions faire appel à un autre processus pour pouvoir écrire en meme temps
- La présentation qui suit n'utilise pas le module de base Socket, qui prend appuie directement sur les
fonctions C, mais un module orienté-objet, de plus haut niveau pour le programmeur
Cette classe IO::Socket permet aisément de construire un objet "socket". Il faut passer les caractéristiques du service à connecter au
constructeur de socket new de cette classe pour récupérer un "handle de socket". Celui-ci identifie la
connexion et est manipulé au point de vue langage comme un descripteur de fichier .
- Paramètres principaux :
- Proto : le protocole TCP ou UDP
- PeerAddr : nom (ou adresse IP) du serveur (les requêtes DNS éventuelles sont transparentes)
- PeerPort : nom du service (/etc/services est alors interrogé) ou numéro du port
- LocalPort : nom du service ou le numéro du port sur le serveur (sous Unix, les ports en
dessous de 1024 sont réservés à root).
- Reuse : paramètre nécessaire pour pouvoir redémarrer le serveur manuellement ?
- Listen : nombre maximal de connexion acceptables avant de rejeter les clients arrivants.
TP
################ daytime.pl : client du service daytime ############
- Il s'agit de créer un client qui se connecte sur le port 13 d'un serveur daytime (vérifier si nécessaire le
paramétrage du service dans son fichier de configuration /etc/xinetd.d/daytime)
- Ce serveur interrogé se contente de répondre au client en lui envoyant le jour et l'heure de son
système, tel qu'on peut les obtenir directement par le truchement de la commande date.
- Ici le client va se contenter d'afficher cette information, mais cela pourrait faire l'objet d'un
traitement, comme la synchronisation de l'horloge du client sur celle du serveur.
- Vérifier que le module IO::Socket est bien installé sur votre machine en ligne de commande
- Tester ce script sur localhost, puis sur un serveur distant sur lequel vous vérifierez la date système
#!/usr/bin/perl -w
# appel : daytime.pl adresse-ip
use IO::Socket;
print "adresse IP du serveur (ou valider sur localhost) : ";
$adresse = <>;
chomp $adresse;
$adresse= "localhost" if $adresse eq "";
$socket = IO::Socket::INET->new(
Proto => "tcp",
PeerAddr => $adresse,
PeerPort => "daytime(13)"
)
or die "connexion impossible au service daytime sur $adresse";
print "Voici le jour et l'heure sur $adresse\n";
while ( <$socket> ) {
print;
}
###################### navig.pl : mini client HTTP #######################
Il s'agit de se connecter à un serveur WEB (port 80) dont l'adresse est demandée (ou bien passé en paramètre) et
d'obtenir la page d'accueil du serveur WEB. En particulier on expérimente le comportement bidirectionnelle de la
socket : le client émet l'instruction HTTP puis se met à l'écoute.
Remarques :
- l'adresse du document doit être absolue par rapport à la racine du web,
par exemple /index.html (ne pas oublier le /)
- la commande à adresser au serveur est "GET $document HTTP/1.0\n\n", conformément au protocole HTTP
- Compléter le source. Tester d'abord en demandant la page d'accueil du serveur apache de la machine voisine
- modifier le programme pour enregistrer sur disque la page d'accueil de Créteil
(si vous voulez l'ip : nslookup www.ac-creteil.fr)
Prolongement
Ecrire maintenant webget.pl, à partir de navig.pl, permettant d'enregistrer localement dans le
répertoire personnel, les pages dont les adresses auront été obtenues soit par dialogue, soit par passage de paramètre
sur la ligne de commande.
#!/usr/bin/perl -w
# appel : navig.pl adresse-ip
#############################
use IO::Socket;
print "nom ou adresse IP du serveur WEB (par défaut créteil) : ";
$web = <>; chomp($web);
$web= "www.ac-creteil.fr" if $web eq "";
print "Document (par défaut ..) : ";
$doc = <>; chomp($doc);
$doc= "/util/programmation/perl/cours/tp-introduction.html" if $doc eq "";
$sock = IO::Socket::INET->new(
...........
..........,
...........
)
or die "connexion impossible au port 80 sur $web";
$sock -> autoflush(1);
# envoi, par socket, de la requete HTTP habituellement envoyée par le navigateur
print $sock "GET $doc HTTP/1.0\n\n";
print "Voici le document $doc sur $web\n";
while ( <$socket> ) {
print;
}
################ Programmer un dialogue interactif client-serveur ###############
- Voici un code de base s'appuyant sur le module IO::Socket, pour créer à l'exécution des processus serveur et
client qui pourront dialoguer.
- Problème : comment gérer l'interactivité de façon générale ? en effet si les processus serveurs et clients sont
en écoute permanente avec une instruction comme $ligne = <$socket>, ils demeurent bloqués jusqu'à la
réception d'une ligne et ils ne peuvent pas "s'écrire". La solution consiste à cloner chaque processus avec fork et à
spécialiser chacun des processus soit dans la réception, soit dans l'émission.
- L'appel fork crée, on l'a vu, un
processus fils qui exécute le meme code que son père après sa création. Le script doit dès lors tester la variable
$pid renvoyée par fork, ce qui permet de différencier le code (réception ou émission) en fonction du processus auquel
il s'adresse.
- Etudier les codes commentés, serveur et client. Puis récupérer les 2 scripts serveur.pl et
client.pl, et les adapter éventuellement pour les faire communiquer sur 2 machines différentes.
Sur Unix, utiliser netstat -at pour connaitre les serveurs actifs.
- Code serveur commenté
#!/usr/bin/perl -w
# serveur.pl #
###############
use IO::Socket;
$server = IO::Socket::INET->new(
LocalPort => 1234,
Type => SOCK_STREAM,
Reuse => 1,
Listen => 5
) or die "Création du serveur impossible.\n";
print "Démarrage du serveur ..\n";
while ($client = $server->accept()) {
print $client "Le serveur dit 'bonjour' au client !\n";
$pid = fork;
die "Je ne peux pas forker !" if ! defined ($pid);
if ($pid ==0) {
# c'est le processus enfant, chargé de l'écoute des clients
#############################################################
while ($ligne = <$client> ) {
print "client> $ligne";
}
} else {
# c'est le père qui pendant ce temps répond au client #
#######################################################
while ($ligne = <>) {
print $client $ligne;
}
}
}
- Code client commenté
#!/usr/bin/perl -w
# client.pl #
##############
use IO::Socket;
print "nom ou adresse IP du serveur (localhost par défaut) ";
$adresse = <>; chomp($adresse);
$adresse= "localhost" if $adresse eq "";
$socket = IO::Socket::INET->new(
PeerAddr => $adresse,
Proto => "tcp",
PeerPort => 1234
)
or die "Connexion au serveur impossible.\n";
$pid = fork;
die "Je ne peux pas forker !" if ! defined ($pid);
if ($pid ==0) {
# c'est le processus enfant, chargé de l'émission au serveur
#############################################################
while ($ligne = <> ) {
print $socket $ligne;
}
} else {
# c'est le père qui pendant ce temps écoute le serveur
#######################################################
while ($ligne = <$socket>) {
print "serveur> $ligne";
}
}