XII. Les fonctions réseau de Python▲
Nous abordons maintenant les fonctions réseau de Python qui nous permettent de faire de la programmation TCP / IP (Transfer Control Protocol / Internet Protocol).
XII-A. Obtenir le nom ou l'adresse IP d'une machine de l'Internet▲
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
import
sys, socket
#------------------------------------------------
def
getIPandName
(
nomMachine):
#nomMachine : nom de la machine dont on veut l'adresse IP
# nomMachine-->adresse IP
try
:
ip=
socket.gethostbyname
(
nomMachine)
print
"ip[
%s
]=
%s
"
%
(
nomMachine,ip)
except
socket.error, erreur:
print
"ip[
%s
]=
%s
"
%
(
nomMachine,erreur)
return
# adresse IP --> nomMachine
try
:
name=
socket.gethostbyaddr
(
ip)
print
"name[
%s
]=
%s
"
%
(
ip,name[0
])
except
socket.error, erreur:
print
"name[
%s
]=
%s
"
%
(
ip,erreur)
return
# ---------------------------------------- main
# constantes
HOTES=
["istia.univ-angers.fr"
,"www.univ-angers.fr"
,"www.ibm.com"
,"localhost"
,""
,"xx"
]
# adresses IP des machines de HOTES
for
i in
range(
len(
HOTES)):
getIPandName
(
HOTES[i])
# fin
sys.exit
(
)
Notes :
- ligne 1 : les fonctions réseau de Python sont encapsulées dans le module socket.
2.
3.
4.
5.
6.
7.
8.
9.
ip[istia.univ-angers.fr]=193.49.146.171
name[193.49.146.171]=istia.istia.univ-angers.fr
ip[www.univ-angers.fr]=193.49.144.40
name[193.49.144.40]=ametys-fo.univ-angers.fr
ip[www.ibm.com]=129.42.58.216
name[129.42.58.216]=[Errno 11004] host not found
ip[localhost]=127.0.0.1
name[127.0.0.1]=Gportpers3.ad.univ-angers.fr
ip[xx]=[Errno 11004] getaddrinfo failed
XII-B. Un client Web▲
Un script permettant d'avoir le contenu de la page index d'un site Web.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
import
sys,socket
#-----------------------------------------------------------------------
def
getIndex
(
site):
# lit l'URL site/ et la stocke dans le fichier site.html
# au départ pas d'erreur
erreur=
""
# création du fichier site.html
try
:
html=
open(
"
%s
.html"
%
(
site),"w"
)
except
IOError
, erreur:
pass
# erreur ?
if
erreur:
return
"Erreur (
%s
) lors de la création du fichier
%s
.html"
%
(
erreur,site)
# ouverture d'une connexion sur le port 80 de site
try
:
connexion=
socket.create_connection
((
site,80
))
except
socket.error, erreur:
pass
# retour si erreur
if
erreur:
return
"Echec de la connexion au site (
%s
,80) :
%s
"
%
(
site,erreur)
# connexion représente un flux de communication bidirectionnel
# entre le client (ce programme) et le serveur Web contacté
# ce canal est utilisé pour les échanges de commandes et d'informations
# le protocole de dialogue est HTTP
# le client envoie la commande get pour demander l'URL /
# syntaxe get URL HTTP/1.0
# les entêtes (headers) du protocole HTTP doivent se terminer par une ligne vide
connexion.send
(
"GET / HTTP/1.0
\n\n
"
)
# le serveur va maintenant répondre sur le canal connexion. Il va envoyer toutes
# ses données puis fermer le canal. Le client lit tout ce qui arrive de connexion
# jusqu'à la fermeture du canal
ligne=
connexion.recv
(
1000
)
while
(
ligne):
html.write
(
ligne)
ligne=
connexion.recv
(
1000
)
# le client ferme la connexion à son tour
connexion.close
(
)
# fermeture du fichier html
html.close
(
)
# retour
return
"Transfert reussi de la page index du site
%s
"
%
(
site)
# --------------------------------------------- main
# obtenir le texte HTML d'URL
# liste de sites Web
SITES=(
"istia.univ-angers.fr"
,"www.univ-angers.fr"
,"www.ibm.com"
,"xx"
)
# lecture des pages index des sites du tableau SITES
for
i in
range(
len(
SITES)):
# lecture page index du site SITES[i]
resultat=
getIndex
(
SITES[i])
# affichage résultat
print
resultat
# fin
sys.exit
(
)
Notes :
- ligne 57 : la liste des URL des sites Web dont on veut la page index. Celle-ci est stockée dans le fichier texte [nomsite.html] ;
- ligne 62 : la fonction getIndex fait le travail ;
- ligne 4 : la fonction getIndex ;
- ligne 20 : la méthode create_connection((site,port)) permet de créer une connexion avec un service TCP / IP travaillant sur le port port de la machine site ;
- ligne 35 : la méthode send permet d'envoyer des données au travers d'une connexion TCP / IP. Ici, c'est du texte qui est envoyé. Ce texte obéit au protocole HTTP (HyperText Transfer Protocol) ;
- ligne 40 : la méthode recv permet de recevoir des données au travers d'une connexion TCP / IP. Ici, la réponse du serveur Web est lue par blocs de 1000 caractères et enregistrée dans le fichier texte [nomsite.html].
2.
3.
4.
Transfert reussi de la page index du site istia.univ-angers.fr
Transfert reussi de la page index du site www.univ-angers.fr
Transfert reussi de la page index du site www.ibm.com
Echec de la connexion au site (xx,80) : [Errno 11001] getaddrinfo failed
Le fichier reçu pour le site [www.ibm.com] :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
HTTP/1.1 302 Found
Date: Wed, 08 Jun 2011 15:43:56 GMT
Server: IBM_HTTP_Server
Content-Type: text/html
Expires: Fri, 01 Jan 1990 00:00:00 GMT
Pragma: no-cache
Cache-Control: no-cache, must-revalidate
Location: http://www.ibm.com/us/en/
Content-Length: 209
Kp-eeAlive: timeout=10, max=14
Connection: Keep-Alive
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>302 Found</title>
</head><body>
<h1>Found</h1>
<p>The document has moved <a href="http://www.ibm.com/us/en/">here</a>.</p>
</body></html>
- les lignes 1-11 sont les entêtes HTTP de la réponse du serveur ;
- ligne 1 : le serveur demande au client de se rediriger vers l'URL indiquée ligne 8 ;
- ligne 2 : date et heure de la réponse ;
- ligne 3 : identité du serveur Web ;
- ligne 4 : contenu envoyé par le serveur. Ici une page HTML qui commence ligne 13 ;
- ligne 12 : la ligne vide qui termine les entêtes HTTP ;
- lignes 13-19 : la page HTML envoyée par le serveur Web.
XII-C. Un client SMTP▲
Parmi les protocoles TCP / IP, SMTP Simple Mail Transfer Protocol) est le protocole de communication du service d'envoi de messages.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
# -*- coding=utf-8 -*-
import
sys,socket
#-----------------------------------------------------------------------
def
getInfos
(
fichier):
# rend les informations (smtp,expéditeur,destinataire,message) prises dans le fichier texte [fichier]
# ligne 1 : smtp, expéditeur, destinataire
# lignes suivantes : le texte du message
# ouverture de [fichier]
erreur=
""
try
:
infos=
open(
fichier,"r"
)
except
IOError
, erreur:
return
(
"Le fichier
%s
n'a pu etre ouvert en lecture :
%s
"
%
(
fichier, erreur))
# lecture de la 1re ligne
ligne=
infos.readline
(
)
# suppression de la marque de fin de ligne
ligne=
cutNewLineChar
(
ligne)
# récupération des champs smtp,expéditeur,destinataire
champs=
ligne.split
(
","
)
# a-t-on le bon nombre de champs ?
if
len(
champs)!=
3
:
return
(
"La ligne 1 du fichier
%s
(serveur smtp, expediteur, destinataire) a un nombre de champs incorrect"
%
(
fichier))
# "traitement" des informations récupérées
# on enlève à chacun des 3 champs les "blancs" qui précèdent ou suivent l'information utile
for
i in
range(
3
):
champs[i]=
champs[i].strip
(
)
# récupération des champs
(
smtpServer,expediteur,destinataire)=
champs
message=
""
# lecture reste du message
ligne=
infos.readline
(
)
while
ligne!=
''
:
message+=
ligne
ligne=
infos.readline
(
)
infos.close
(
)
# retour
return
(
""
,smtpServer,expediteur,destinataire,message)
#-----------------------------------------------------------------------
def
sendmail
(
smtpServer,expediteur,destinataire,message,verbose):
# envoie message au serveur smtp smtpserver de la part de expéditeur
# pour destinataire. Si verbose=True, fait un suivi des échanges client-serveur
# on récupère le nom du client
try
:
client=
socket.gethostbyaddr
(
socket.gethostbyname
(
"localhost"
))[0
]
except
socket.error, erreur:
return
"Erreur IP / Nom du client :
%s
"
%
(
erreur)
# ouverture d'une connexion sur le port 25 de smtpServer
try
:
connexion=
socket.create_connection
((
smtpServer,25
))
except
socket.error, erreur:
return
"Echec de la connexion au site (
%s
,25) :
%s
"
%
(
smtpServer,erreur)
# connexion représente un flux de communication bidirectionnel
# entre le client (ce programme) et le serveur smtp contacté
# ce canal est utilisé pour les échanges de commandes et d'informations
# après la connexion le serveur envoie un message de bienvenue qu'on lit
erreur=
sendCommand
(
connexion,""
,verbose,1
)
if
(
erreur) :
connexion.close
(
)
return
erreur
# cmde ehlo:
erreur=
sendCommand
(
connexion,"EHLO
%s
"
%
(
client),verbose,1
)
if
erreur :
connexion.close
(
)
return
erreur
# cmde mail from:
erreur=
sendCommand
(
connexion,"MAIL FROM: <
%s
>"
%
(
expediteur),verbose,1
)
if
erreur :
connexion.close
(
)
return
erreur
# cmde rcpt to:
erreur=
sendCommand
(
connexion,"RCPT TO: <
%s
>"
%
(
destinataire),verbose,1
)
if
erreur :
connexion.close
(
)
return
erreur
# cmde data
erreur=
sendCommand
(
connexion,"DATA"
,verbose,1
)
if
erreur :
connexion.close
(
)
return
erreur
# préparation message à envoyer
# il doit contenir les lignes
# From: expéditeur
# To: destinataire
# ligne vide
# Message
# .
data=
"From:
%s\r\n
To:
%s\r\n%s\r\n
.
\r\n
"
%
(
expediteur,destinataire,message)
# envoi message
erreur=
sendCommand
(
connexion,data,verbose,0
)
if
erreur :
connexion.close
(
)
return
erreur
# cmde quit
erreur=
sendCommand
(
connexion,"QUIT"
,verbose,1
)
if
erreur :
connexion.close
(
)
return
erreur
# fin
connexion.close
(
)
return
"Message envoye"
# --------------------------------------------------------------------------
def
sendCommand
(
connexion,commande,verbose,withRCLF):
# envoie commande dans le canal connexion
# mode verbeux si verbose=1
# si withRCLF=1, ajoute la séquence RCLF à commande
# données
RCLF=
"
\r\n
"
if
withRCLF else
""
# envoie cmde si commande non vide
if
commande:
connexion.send
(
"
%s%s
"
%
(
commande,RCLF))
# écho éventuel
if
verbose:
affiche
(
commande,1
)
# lecture réponse de moins de 1000 caractères
reponse=
connexion.recv
(
1000
)
# écho éventuel
if
verbose:
affiche
(
reponse,2
)
# récupération code erreur
codeErreur=
reponse[0
:3
]
# erreur renvoyée par le serveur ?
if
int(
codeErreur) >=
500
:
return
reponse[4
:]
# retour sans erreur
return
""
# --------------------------------------------------------------------------
def
affiche
(
echange,sens):
# affiche échange ? l'écran
# si sens=1 affiche -->echange
# si sens=2 affiche <-- échange sans les 2 derniers caractères RCLF
if
sens==
1
:
print
"--> [
%s
]"
%
(
echange)
return
elif
sens==
2
:
l=
len(
echange)
print
"<-- [
%s
]"
%
echange[0
:l-
2
]
return
# --------------------------------------------------------------------------
def
cutNewLineChar
(
ligne):
# on supprime la marque de fin de ligne de [ligne] si elle existe
l=
len(
ligne)
while
(
ligne[l-
1
]==
"
\n
"
or
ligne[l-
1
]==
"
\r
"
):
l-=
1
return
(
ligne[0
:l])
# main ----------------------------------------------------------------
# client SMTP (SendMail Transfer Protocol) permettant d'envoyer un message
# les infos sont prises dans un fichier INFOS contenant les lignes suivantes
# ligne 1 : smtp, expéditeur, destinataire
# lignes suivantes : le texte du message
# expéditeur:email expéditeur
# destinataire: email destinataire
# smtp: nom du serveur smtp à utiliser
# protocole de communication SMTP client-serveur
# -> client se connecte sur le port 25 du serveur smtp
# <- serveur lui envoie un message de bienvenue
# -> client envoie la commande EHLO: nom de sa machine
# <- serveur répond OK ou non
# -> client envoie la commande mail from: <expéditeur>
# <- serveur répond OK ou non
# -> client envoie la commande rcpt to: <destinataire>
# <- serveur répond OK ou non
# -> client envoie la commande data
# <- serveur répond OK ou non
# -> client envoie toutes les lignes de son message et termine avec une ligne contenant le seul caractère .
# <- serveur répond OK ou non
# -> client envoie la commande quit
# <- serveur répond OK ou non
# les réponses du serveur ont la forme xxx texte où xxx est un nombre à 3 chiffres. Tout nombre xxx >=500
# signale une erreur. La réponse peut comporter plusieurs lignes commençant toutes par xxx- sauf la dernière
# de la forme xxx(espace)
# les lignes de texte échangées doivent se terminer par les caractères RC(#13) et LF(#10)
# # les paramètres de l'envoi du courrier
MAIL=
"mail2.txt"
# on récupère les paramètres du courrier
res=
getInfos
(
MAIL)
# erreur ?
if
res[0
]:
print
"
%s
"
%
(
erreur)
sys.exit
(
)
# envoi du courrier en mode verbeux
(
smtpServer,expediteur,destinataire,message)=
res[1
:]
print
"Envoi du message [
%s
,
%s
,
%s
]"
%
(
smtpServer,expediteur,destinataire)
resultat=
sendmail
(
smtpServer,expediteur,destinataire,message,True
)
print
"Resultat de l'envoi :
%s
"
%
(
resultat)
# fin
sys.exit
(
)
Notes :
- sur une machine Windows possédant un antivirus, ce dernier empêchera peut-être le script Python de se connecter au port 25 d'un serveur SMTP. Il faut alors désactiver l'antivirus. Pour McAfee par exemple, on peut procéder ainsi :
- en [1], on active la console VirusScan ;
- en [2], on arrête le service [Protection lors de l'accès] ;
- en [3], il est arrêté.
Résultats
Le fichier infos.txt :
2.
3.
4.
5.
6.
7.
smtp.univ-angers.fr, serge.tahe@univ-angers.fr , serge.tahe@univ-angers.fr
Subject: test
ligne1
ligne2
ligne3
Les résultats écran :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
Envoi du message [smtp.univ-angers.fr,serge.tahe@univ-angers.fr,serge.tahe@univ-angers.fr]
--> [EHLO Gportpers3.ad.univ-angers.fr]
<-- [220 smtp.univ-angers.fr ESMTP Postfix
250-smtp.univ-angers.fr
250-PIPELINING
250-SIZE 20480000
250-VRFY
250-ETRN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN]
--> [MAIL FROM: <serge.tahe@univ-angers.fr>]
<-- [250 2.1.0 Ok]
--> [RCPT TO: <serge.tahe@univ-angers.fr>]
<-- [250 2.1.5 Ok]
--> [DATA]
<-- [354 End data with <CR><LF>.<CR><LF>]
--> [From: serge.tahe@univ-angers.fr
To: serge.tahe@univ-angers.fr
Subject: test
ligne1
ligne2
ligne3
.
]
<-- [250 2.0.0 Ok: queued as 63412114203]
--> [QUIT]
<-- [221 2.0.0 Bye]
Resultat de l'envoi : Message envoye
Le message lu par le lecteur de courrier Thunderbird :
XII-D. Un second client SMTP▲
Ce second script fait la même chose que le précédent mais en utilisant les fonctionnalités du module [smtplib].
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
# -*- coding=utf-8 -*-
import
sys,socket, smtplib
#-----------------------------------------------------------------------
def
getInfos
(
fichier):
# rend les informations (smtp,expéditeur,destinataire,message) prises dans le fichier texte [fichier]
# ligne 1 : smtp, expéditeur, destinataire
# lignes suivantes : le texte du message
# ouverture de [fichier]
erreur=
""
try
:
infos=
open(
fichier,"r"
)
except
IOError
, erreur:
return
(
"Le fichier
%s
n'a pu etre ouvert en lecture :
%s
"
%
(
fichier, erreur))
# lecture de la 1re ligne
ligne=
infos.readline
(
)
# suppression de la marque de fin de ligne
ligne=
cutNewLineChar
(
ligne)
# récupération des champs smtp,expéditeur,destinataire
champs=
ligne.split
(
","
)
# a-t-on le bon nombre de champs ?
if
len(
champs)!=
3
:
return
(
"La ligne 1 du fichier
%s
(serveur smtp, expediteur, destinataire) a un nombre de champs incorrect"
%
(
fichier))
# "traitement" des informations récupérées - on leur enlève les "blancs" qui les précèdent ou les suivent
for
i in
range(
3
):
champs[i]=
champs[i].strip
(
)
# récupération des champs
(
smtpServer,expediteur,destinataire)=
champs
# lecture message à envoyer
message=
""
ligne=
infos.readline
(
)
while
ligne!=
''
:
message+=
ligne
ligne=
infos.readline
(
)
infos.close
(
)
# retour
return
(
""
,smtpServer,expediteur,destinataire,message)
#-----------------------------------------------------------------------
def
sendmail
(
smtpServer,expediteur,destinataire,message,verbose):
# envoie message au serveur smtp smtpserver de la part de expéditeur
# pour destinataire. Si verbose=True, fait un suivi des échanges client-serveur
# on utilise la bibliothéque smtplib
try
:
server =
smtplib.SMTP
(
smtpServer)
if
verbose:
server.set_debuglevel
(
1
)
server.sendmail
(
expediteur, destinataire, message)
server.quit
(
)
except
Exception
, erreur:
return
"Erreur envoi du message :
%s
"
%
(
erreur)
# fin
return
"Message envoye"
# --------------------------------------------------------------------------
def
cutNewLineChar
(
ligne):
# on supprime la marque de fin de ligne de [ligne] si elle existe
l=
len(
ligne)
while
(
ligne[l-
1
]==
"
\n
"
or
ligne[l-
1
]==
"
\r
"
):
l-=
1
return
(
ligne[0
:l])
# main ----------------------------------------------------------------
# client SMTP (SendMail Transfer Protocol) permettant d'envoyer un message
# les infos sont prises dans un fichier INFOS contenant les lignes suivantes
# ligne 1 : smtp, expéditeur, destinataire
# lignes suivantes : le texte du message
# expéditeur:email expéditeur
# destinataire: email destinataire
# smtp: nom du serveur smtp à utiliser
# protocole de communication SMTP client-serveur
# -> client se connecte sur le port 25 du serveur smtp
# <- serveur lui envoie un message de bienvenue
# -> client envoie la commande EHLO: nom de sa machine
# <- serveur répond OK ou non
# -> client envoie la commande mail from: <expéditeur>
# <- serveur répond OK ou non
# -> client envoie la commande rcpt to: <destinataire>
# <- serveur répond OK ou non
# -> client envoie la commande data
# <- serveur répond OK ou non
# -> client envoie toutes les lignes de son message et termine avec une ligne contenant le seul caractère .
# <- serveur répond OK ou non
# -> client envoie la commande quit
# <- serveur répond OK ou non
# les réponses du serveur ont la forme xxx texte où xxx est un nombre à 3 chiffres. Tout nombre xxx >=500
# signale une erreur. La réponse peut comporter plusieurs lignes commençant toutes par xxx- sauf la dernière
# de la forme xxx(espace)
# les lignes de texte échangées doivent se terminer par les caractères RC(#13) et LF(#10)
# # les paramètres de l'envoi du courrier
MAIL=
"mail2.txt"
# on récupère les paramètres du courrier
res=
getInfos
(
MAIL)
# erreur ?
if
res[0
]:
print
"
%s
"
%
(
erreur)
sys.exit
(
)
# envoi du courrier en mode verbeux
(
smtpServer,expediteur,destinataire,message)=
res[1
:]
print
"Envoi du message [
%s
,
%s
,
%s
]"
%
(
smtpServer,expediteur,destinataire)
resultat=
sendmail
(
smtpServer,expediteur,destinataire,message,True
)
print
"Resultat de l'envoi :
%s
"
%
(
resultat)
# fin
sys.exit
(
)
Notes :
- ce script est identique au précédent à la fonction sendmail près. Cette fonction utilise désormais les fonctionnalités du module [smtplib] (ligne 3).
2.
3.
4.
5.
6.
7.
8.
9.
smtp.univ-angers.fr, serge.tahe@univ-angers.fr , serge.tahe@univ-angers.fr
From: serge.tahe@univ-angers.fr
To: serge.tahe@univ-angers.fr
Subject: test
ligne1
ligne2
ligne3
Les résultats écran :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
Envoi du message [smtp.univ-angers.fr,serge.tahe@univ-angers.fr,serge.tahe@univ-
angers.fr]
send: 'ehlo Gportpers3.ad.univ-angers.fr\r\n'
reply: '250-smtp.univ-angers.fr\r\n'
reply: '250-PIPELINING\r\n'
reply: '250-SIZE 20480000\r\n'
reply: '250-VRFY\r\n'
reply: '250-ETRN\r\n'
reply: '250-ENHANCEDSTATUSCODES\r\n'
reply: '250-8BITMIME\r\n'
reply: '250 DSN\r\n'
reply: retcode (250); Msg: smtp.univ-angers.fr
PIPELINING
SIZE 20480000
VRFY
ETRN
ENHANCEDSTATUSCODES
8BITMIME
DSN
send: 'mail FROM:<serge.tahe@univ-angers.fr> size=99\r\n'
reply: '250 2.1.0 Ok\r\n'
reply: retcode (250); Msg: 2.1.0 Ok
send: 'rcpt TO:<serge.tahe@univ-angers.fr>\r\n'
reply: '250 2.1.5 Ok\r\n'
reply: retcode (250); Msg: 2.1.5 Ok
send: 'data\r\n'
reply: '354 End data with <CR><LF>.<CR><LF>\r\n'
reply: retcode (354); Msg: End data with <CR><LF>.<CR><LF>
data: (354, 'End data with <CR><LF>.<CR><LF>')
send: 'From: serge.tahe@univ-angers.fr\r\nTo: serge.tahe@univ-angers.fr\r\nSubje
ct: test\r\n\r\nligne1\r\nligne2\r\n\r\nligne3\r\n.\r\n'
reply: '250 2.0.0 Ok: queued as D4977114358\r\n'
reply: retcode (250); Msg: 2.0.0 Ok: queued as D4977114358
data: (250, '2.0.0 Ok: queued as D4977114358')
send: 'quit\r\n'
reply: '221 2.0.0 Bye\r\n'
reply: retcode (221); Msg: 2.0.0 Bye
Resultat de l'envoi : Message envoye
XII-E. Client / serveur d'écho▲
On crée un service d'écho. Le serveur renvoie en majuscules toutes les lignes de texte que lui envoie le client. Le service peut servir plusieurs clients à la fois grâce à l'utilisation de threads.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
# -*- coding=utf-8 -*-
# serveur tcp générique sur Windows
# chargement des fichiers d'en-tête
import
re,sys,SocketServer,threading
# serveur tcp multithreadé
class
ThreadedTCPRequestHandler
(
SocketServer.StreamRequestHandler):
def
handle
(
self):
# thread courant
cur_thread =
threading.currentThread
(
)
# données du client
self.data=
"on"
# arrêt sur chaine vide
while
self.data:
# les lignes de texte du client sont lues avec la méthode readline
self.data =
self.rfile.readline
(
).strip
(
)
# suivi console
print
"client
%s
:
%s
(
%s
)"
%
(
self.client_address[0
],self.data,cur_thread.getName
(
))
# envoi réponse au client
response =
"
%s
:
%s
"
%
(
cur_thread.getName
(
), self.data.upper
(
))
# self.wfile est le flux d'écriture vers client
self.wfile.write
(
response)
class
ThreadedTCPServer
(
SocketServer.ThreadingMixIn, SocketServer.TCPServer):
pass
# ------------------------------------------------------ main
# syntaxe d'appel : argv[0] port
# le serveur est lancé sur le port nommé
# Données
syntaxe=
"syntaxe :
%s
port"
%
(
sys.argv[0
])
#------------------------------------- on vérifie l'appel
# il doit y avoir un argument et un seul
nbArguments=
len(
sys.argv)
if
nbArguments!=
2
:
print
syntaxe
sys.exit
(
1
)
# le port doit être numérique
port=
sys.argv[1
]
modele=
r"^\s*\d+\s*$"
if
not
re.match
(
modele,port):
print
"Le port
%s
n'est pas un nombre entier positif
\n
"
%
(
port)
sys.exit
(
2
)
# lancement du serveur - sert chaque client sur un thread
host=
"localhost"
server =
ThreadedTCPServer
((
host,int(
port)), ThreadedTCPRequestHandler)
# le serveur est lancé dans un thread
# chaque client sera servi dans un thread supplémentaire
server_thread =
threading.Thread
(
target=
server.serve_forever)
# lance le serveur - boucle infinie d'attente des clients
server_thread.start
(
)
# suivi
print
"Serveur d'echo a l'ecoute sur le port % s"
%
(
port)
Notes :
- ligne 39 : sys.argv représente les paramètres du script. Ils doivent être ici de la forme : nom_du_script port. Il doit donc y en avoir deux. sys.argv[0] sera alors nom_du_script et sys.argv[1] sera port ;
- ligne 53 : le serveur d'écho est une instance de la classe ThreadedTcpServer. Le constructeur de cette classe attend deux paramètres :
- paramètre 1 : un tuple de deux éléments (host,port) qui fixe la machine et le port d'écoute du serveur ;
- paramètre 2 : le nom de la classe chargée de traiter les requêtes d'un client.
- ligne 57 : un thread est créé (mais pas encore lancé). Ce thread exécute la méthode [serve_forever] du serveur TCP. Cette méthode est une boucle d'écoute des connexions clientes. Dès qu'une connexion cliente est détectée, une instance de la classe ThreadedTCPRequestHandler sera créée. Sa méthode handle est chargée du dialogue avec le client ;
- ligne 59 : le thread du service d'écho est lancé. À partir de ce moment des clients peuvent se connecter au service ;
- ligne 27 : la classe du serveur d'écho. Elle dérive de deux classes : SocketServer.ThreadingMixIn et SocketServer.TCPServer. Cela en fait un serveur TCP multithreadé : le serveur s'exécute dans un thread et chaque client est servi dans un thread supplémentaire ;
- ligne 9 : la classe qui traite les demandes des clients. Elle dérive de la classe SocketServer.StreamRequestHandler. Elle hérite alors de deux attributs :
- rfile : qui est le flux de lecture des données envoyées par le client - peut être traité comme un fichier texte,
- wfile : qui est le flux d'écriture qui permet d'envoyer des données au client - peut être traité comme un fichier texte ;
- ligne 11 : la méthode handle traite les demandes des clients ;
- ligne 13 : le thread qui exécute cette méthode handle ;
- ligne 17 : boucle de traitement des demandes du client. La boucle se termine lorsque le client envoie une ligne vide ;
- ligne 19 : lecture de la demande du client ;
- ligne 21 : self.client_address[0] représente l'adresse IP du client. cur_thread.getName() est le nom du thread qui exécute la méthode handle ;
- ligne 23 : la réponse au client a deux composantes - le nom du thread qui sert le client et la commande qu'il a envoyée, passée en majuscules.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
# -*- coding=utf-8 -*-
import
re,sys,socket
# ------------------------------------------------------ main
# client tcp générique
# syntaxe d'appel : argv[0] hote port
# le client se connecte au service d'écho (hote,port)
# le serveur renvoie les lignes tapées par le client
# syntaxe
syntaxe=
"syntaxe :
%s
hote port"
%
(
sys.argv[0
])
#------------------------------------- on vérifie l'appel
# il doit y avoir deux arguments
nbArguments=
len(
sys.argv)
if
nbArguments!=
3
:
print
syntaxe
sys.exit
(
1
)
# on récupère les arguments
hote=
sys.argv[1
]
# le port doit être numérique
port=
sys.argv[2
]
modele=
r"^\s*\d+\s*$"
if
not
re.match
(
modele,port):
print
"Le port
%s
foit être un nombre entier positif"
%
(
port)
sys.exit
(
2
)
try
:
# connexion du client au serveur
connexion=
socket.create_connection
((
hote,int(
port)))
except
socket.error, erreur:
print
"Echec de la connexion au site (
%s
,
%s
) :
%s
"
%
(
hote,port,erreur)
sys.exit
(
3
)
try
:
# boucle de saisie
ligne=
raw_input(
"Commande (rien pour arreter): "
).strip
(
)
while
ligne!=
""
:
# on envoie la ligne au serveur
connexion.send
(
"
%s\n
"
%
(
ligne))
# on attend la reponse
reponse=
connexion.recv
(
1000
)
print
"<--
%s
"
%
(
reponse)
ligne=
raw_input(
"Commande (rien pour arreter): "
)
except
socket.error, erreur:
print
"Echec de la connexion au site (
%s
,
%s
) :
%s
"
%
(
hote,port,erreur)
sys.exit
(
3
)
finally
:
# on clôt la connexion
connexion.close
(
)
Le serveur est lancé dans une première fenêtre de commandes :
2.
cmd>%python% inet_08.py 100
Serveur d'echo a l'ecoute sur le port 100
Un premier client est lancé dans une seconde fenêtre :
2.
3.
4.
5.
6.
7.
cmd>%python% inet_06.py localhost 100
Commande (rien pour arreter): cmde 1 du client 1
[cmde 1 du client 1]
<-- Thread-2: CMDE 1 DU CLIENT 1
Commande (rien pour arreter): cmde 2 du client 1
<-- Thread-2: CMDE 2 DU CLIENT 1
Commande (rien pour arreter):
Le client reçoit bien en réponse à la commande qu'il envoie au serveur, cette même commande est mise en majuscules. Un second client est lancé dans une troisième fenêtre :
2.
3.
4.
5.
6.
cmd>%python% inet_06.py localhost 100
Commande (rien pour arreter): cmde 1 du client 2
<-- Thread-3: CMDE 1 DU CLIENT 2
Commande (rien pour arreter): cmde 2 du client 2
<-- Thread-3: CMDE 2 DU CLIENT 2
Commande (rien pour arreter):
La console du serveur est alors la suivante :
2.
3.
4.
5.
6.
cmd>%python% inet_08.py 100
Serveur d'echo a l'ecoute sur le port 100
client 127.0.0.1 : cmde 1 du client 1 (Thread-2)
client 127.0.0.1 : cmde 2 du client 1 (Thread-2)
client 127.0.0.1 : cmde 1 du client 2 (Thread-3)
client 127.0.0.1 : cmde 2 du client 2 (Thread-3)
Les clients sont bien servis dans des threads différents. Pour arrêter un client, il suffit de taper une commande vide.
XII-F. Serveur Tcp générique▲
Nous nous proposons d'écrire un script Python qui
- serait un serveur Tcp capable de servir un client à la fois,
- acceptant des lignes de texte envoyées par le client,
- acceptant des lignes de texte venant du clavier et qui sont envoyées en réponse au client.
Ainsi c'est l'utilisateur au clavier qui fait office de serveur :
- il voit sur sa console les lignes de texte envoyées par le client ;
- il répond à celui-ci en tapant la réponse au clavier.
Il peut ainsi s'adapter à toute sorte de clients. C'est pour cela qu'on l'appellera "serveur Tcp générique". C'est un outil pratique pour découvrir des protocoles de communication Tcp. Dans l'exemple qui suit, le client Tcp sera un navigateur Web ce qui nous permettra de découvrir le protocole HTTP utilisé par les clients Web.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
# -*- coding=utf-8 -*-
# serveur tcp générique
# chargement des fichiers d'en-tête
import
re,sys,SocketServer,threading
# serveur tcp générique
class
MyTCPHandler
(
SocketServer.StreamRequestHandler):
def
handle
(
self):
# on affiche le client
print
"client
%s
"
%
(
self.client_address[0
])
# on crée un thread de lecture des commandes du client
thread_lecture =
threading.Thread
(
target=
self.lecture)
thread_lecture.start
(
)
# arrêt sur cmde 'bye'
# lecture cmde tapée au clavier
cmde=
raw_input(
"--> "
)
while
cmde!=
"bye"
:
# envoi cmde au client. self.wfile est le flux d'écriture vers le client
self.wfile.write
(
"
%s\n
"
%
(
cmde))
# lecture cmde suivante
cmde=
raw_input(
"--> "
)
def
lecture
(
self):
# on affiche toutes les lignes envoyées par le client jusqu'à recevoir la commande bye
ligne=
""
while
ligne!=
"bye"
:
# les lignes de texte du client sont lues avec la méthode readline
ligne =
self.rfile.readline
(
).strip
(
)
# suivi console
print
"<---
%s
:
%s
"
%
(
self.client_address[0
], ligne)
# ------------------------------------------------------ main
# syntaxe d'appel : argv[0] port
# le serveur est lancé sur le port nommé
# Données
syntaxe=
"syntaxe :
%s
port"
%
(
sys.argv[0
])
#------------------------------------- on vérifie l'appel
# il doit y avoir un argument et un seul
nbArguments=
len(
sys.argv)
if
nbArguments!=
2
:
print
syntaxe
sys.exit
(
1
)
# le port doit être numérique
port=
sys.argv[1
]
modele=
r"^\s*\d+\s*$"
if
not
re.match
(
modele,port):
print
"Le port
%s
n'est pas un nombre entier positif
\n
"
%
(
port)
sys.exit
(
2
)
# lancement du serveur
host=
"localhost"
server =
SocketServer.TCPServer
((
host, int(
port)), MyTCPHandler)
print
"Service tcp generique lance sur le port
%s
. Arret par Ctrl-C"
%
(
port)
server.serve_forever
(
)
Notes :
- ligne 57 : le serveur Tcp sera une instance de la classe SocketServer.TCPServer. Le constructeur de celle-ci admet deux paramètres :
- le 1er paramètre est un tuple de deux éléments (host, port) où host est la machine sur laquelle opère le service (généralement localhost) et port, le port sur lequel le service attend (écoute) les demandes des clients,
- le second paramètre précise la classe de service d'un client. Lorsqu'un client se connecte, une instance de la classe de service est créée et c'est sa méthode handle qui doit gérer la connexion avec le client.
Le serveur Tcp SocketServer.TCPServer n'est pas multithreadé. Il sert un client à la fois ;
- ligne 59 : la méthode serve_forever du serveur Tcp est exécutée. C'est une boucle sans fin d'attente des clients ;
- ligne 9 : le serveur Tcp est ici une classe dérivée de la classe SocketServer.StreamRequestHandler. Cela permet de considérer les flux de données échangées avec le client comme des fichiers texte. Nous avons déjà rencontré cette classe. Nous disposons des méthodes :
- readline pour lire une ligne de texte venant du client,
- write pour lui renvoyer des lignes de texte ;
- ligne 12 : self.client_address[0] est l'adresse Ip du client ;
- ligne 14 : le serveur Tcp va échanger avec le client à l'aide de deux threads :
- un thread de lecture des lignes du client,
- un thread d'écriture de lignes au client ;
- ligne 14 : le thread de lecture des lignes du client est créé. Son paramètre target fixe la méthode exécutée par le thread. C'est celle définie ligne 25 ;
- ligne 25 : le thread de lecture est lancé ;
- lignes 25-32 : la méthode exécutée par le thread de lecture ;
- ligne 28 : le thread de lecture lit toutes les lignes de texte envoyées par le client jusqu'à la réception de la ligne "bye" ;
- ligne 18 : on est là dans le thread d'écriture au client. Le principe est d'envoyer au client toutes les lignes de texte tapées au clavier par l'utilisateur.
Les résultats
2.
dos>%python% inet_10.py 100
Service tcp generique lance sur le port 100. Arret par Ctrl-C
Le navigateur client :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
dos>%python% inet_10.py 100
Service tcp generique lance sur le port 100. Arret par Ctrl-C
client 127.0.0.1
<--- 127.0.0.1 : GET / HTTP/1.1
<--> --- 127.0.0.1 : Host: localhost:100
<--- 127.0.0.1 : User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10 GTB7.1 ( .NET CLR 3.5.30729)
<--- 127.0.0.1 : Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
<--- 127.0.0.1 : Accept-Language: fr,fr-fr;q=0.8,en;q=0.5,en-us;q=0.3
<--- 127.0.0.1 : Accept-Encoding: gzip,deflate
<--- 127.0.0.1 : Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
<--- 127.0.0.1 : Keep-Alive: 115
<--- 127.0.0.1 : Connection: keep-alive
<--- 127.0.0.1 : Cache-Control: max-age=0
<--- 127.0.0.1 :
La réponse du serveur tapée par l'utilisateur au clavier (sans le signe -->)
2.
3.
4.
5.
6.
7.
HTTP/1.1 200 OK
--> Server: serveur tcp generique
--> Connection: close
--> Content-Type: text/html
-->
--> <html><body><h2>Serveur tcp generique</h2></body></html>
--> bye
- lignes 1-5 : réponse HTTP envoyée au client ;
- ligne 6 : la page HTML envoyée au client ;
- ligne 7 : fin du dialogue avec le client. Le service au client va se terminer et la connexion va être fermée, ce qui va interrompre brutalement le thread de lecture des lignes de texte envoyées par le client ;
- les lignes 1-5 de la réponse Http faite au client ont la signification suivante :
- ligne 1 : la ressource demandée par le client a été trouvée,
- ligne 2 : identification du serveur,
- ligne 3 : le serveur va fermer la connexion après envoi de la ressource,
- ligne 4 : nature de la ressource envoyée par le serveur : un document HTML,
- ligne 5 : une ligne vide.
La page affichée par le navigateur [1] :
Si on fait afficher le code source reçu par le navigateur [2], on s'aperçoit que le code HTML reçu par le navigateur Web est bien celui qu'on lui avait envoyé.