11. Funzioni di rete di Python
Ora parleremo delle funzioni di rete di Python, che ci permettono di eseguire la programmazione TCP/IP (Transmission Control Protocol/Internet Protocol).
11.1. Ottenere il nome o l'indirizzo IP di un computer su Internet
Programma (inet_01)
| import sys, socket
#------------------------------------------------
def getIPandName(nomMachine):
# nomMachine: name of the machine whose address is required IP: name of the machine whose address is required IP: name of the machine whose address is required
# 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
# address 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
# constant
HOTES=["istia.univ-angers.fr","www.univ-angers.fr","www.ibm.com","localhost","","xx"]
# IP addresses of HOTES machines
for i in range(len(HOTES)):
getIPandName(HOTES[i])
# end
sys.exit()
|
Note:
- Riga 1: Le funzioni di rete di Python sono incapsulate nel modulo socket.
Risultati
| 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
|
11.2. Un client web
Uno script per recuperare il contenuto della pagina indice di un sito web.
Programma (inet_02)
| import sys,socket
#-----------------------------------------------------------------------
def getIndex(site):
# reads the URL site/ and stores it in the site.html file
# initially no error
erreur=""
# creation of the site.html file
try:
html=open("%s.html" % (site),"w")
except IOError, erreur:
pass
# mistake?
if erreur:
return "Erreur (%s) lors de la création du fichier %s.html" % (erreur,site)
# open a connection on site port 80
try:
connexion=socket.create_connection((site,80))
except socket.error, erreur:
pass
# return if error
if erreur:
return "Echec de la connexion au site (%s,80) : %s" % (site,erreur)
# connection represents a bidirectional communication flow
# between the client (this program) and the contacted web server
# this channel is used for the exchange of orders and information
# the dialog protocol is HTTP
# the customer sends the get command to request URL /
# syntax get URL HTTP/1.0
# protocol HTTP headers must end with an empty line
connexion.send("GET / HTTP/1.0\n\n")
# the server will now respond on the connection channel. It will send all
# then close the channel. The customer reads everything that comes in from the connection
# until the channel closes
ligne=connexion.recv(1000)
while(ligne):
html.write(ligne)
ligne=connexion.recv(1000)
# the customer in turn closes the connection
connexion.close()
# close html file
html.close()
# return
return "Transfert reussi du paragraphe index du site %s" % (site)
# --------------------------------------------- main
# get the text HTML from URL
# list of websites
SITES=("istia.univ-angers.fr","www.univ-angers.fr","www.ibm.com","xx")
# reading the index pages of the sites in the SITES table
for i in range(len(SITES)):
# read site index page SITES[i]
resultat=getIndex(SITES[i])
# result display
print resultat
# end
sys.exit()
|
Note:
- riga 57: l'elenco degli URL dei siti web di cui vogliamo le pagine indice. Questo è memorizzato nel file di testo [sitename.html];
- riga 62: la funzione getIndex esegue il lavoro;
- riga 4: la funzione getIndex;
- riga 20: il metodo create_connection((site,port)) consente di creare una connessione con un servizio TCP/IP in esecuzione sulla porta port sul computer del sito;
- riga 35: il metodo send permette di inviare dati tramite una connessione TCP/IP. In questo caso, viene inviato del testo. Questo testo segue il protocollo HTTP (HyperText Transfer Protocol);
- riga 40: il metodo recv viene utilizzato per ricevere dati tramite una connessione TCP/IP. In questo caso, la risposta del server web viene letta in blocchi di 1.000 caratteri e salvata nel file di testo [sitename.html].
Risultati
Transfert reussi du paragraphe index du site istia.univ-angers.fr
Transfert reussi du paragraphe index du site www.univ-angers.fr
Transfert reussi du paragraphe index du site www.ibm.com
Echec de la connexion au site (xx,80) : [Errno 11001] getaddrinfo failed
Il file ricevuto per il sito [www.ibm.com]:
| 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>
|
- Le righe da 1 a 11 sono le intestazioni HTTP della risposta del server;
- riga 1: il server indica al client di reindirizzarsi all'URL specificato nella riga 8;
- riga 2: data e ora della risposta;
- riga 3: identità del server web;
- riga 4: contenuto inviato dal server. In questo caso, una pagina HTML che inizia alla riga 13;
- riga 12: la riga vuota che conclude le intestazioni HTTP;
- Righe 13–19: la pagina HTML inviata dal server web.
11.3. Un client SMTP
Tra i protocolli TCP/IP, SMTP (Simple Mail Transfer Protocol) è il protocollo di comunicazione per il servizio di consegna dei messaggi.
Programma (inet_03)
| # -*- coding=utf-8 -*-
import sys,socket
#-----------------------------------------------------------------------
def getInfos(fichier):
# returns the information (smtp,sender,recipient,message) taken from the text file [file]
# line 1: smtp, sender, recipient
# next lines: message text
# open [file]
erreur=""
try:
infos=open(fichier,"r")
except IOError, erreur:
return ("Le fichier %s n'a pu etre ouvert en lecture : %s" % (fichier, erreur))
# read the 1st line
ligne=infos.readline()
# delete end-of-line mark
ligne=cutNewLineChar(ligne)
# retrieving smtp, sender and recipient fields
champs=ligne.split(",")
# do we have the right number of fields?
if len(champs)!=3 :
return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
# "processing" the recovered information
# we remove from each of the 3 fields the "blanks" that precede or follow the useful information
for i in range(3):
champs[i]=champs[i].strip()
# field recovery
(smtpServer,expediteur,destinataire)=champs
message=""
# read rest of message
ligne=infos.readline()
while ligne!='':
message+=ligne
ligne=infos.readline()
infos.close()
# return
return ("",smtpServer,expediteur,destinataire,message)
#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
# sends message to smtp server smtpserver from sender
# as recipient. If verbose=True, tracks client-server exchanges
# retrieve the customer's name
try:
client=socket.gethostbyaddr(socket.gethostbyname("localhost"))[0]
except socket.error, erreur:
return "Erreur IP / Nom du client : %s" % (erreur)
# open a connection on port 25 of smtpServer
try:
connexion=socket.create_connection((smtpServer,25))
except socket.error, erreur:
return "Echec de la connexion au site (%s,25) : %s" % (smtpServer,erreur)
# connection represents a bidirectional communication flow
# between the client (this program) and the smtp server contacted
# this channel is used for the exchange of orders and information
# after connection, the server sends a welcome message which is read as follows
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
# prepare message to send
# it must contain the lines
# From: expéditeur
# To: recipient
# empty line
# Message
# .
data="From: %s\r\nTo: %s\r\n%s\r\n.\r\n" % (expediteur,destinataire,message)
# send 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
# end
connexion.close()
return "Message envoye"
# --------------------------------------------------------------------------
def sendCommand(connexion,commande,verbose,withRCLF):
# sends command to connection channel
# verbose mode if verbose=1
# if withRCLF=1, adds sequence RCLF to command
# data
RCLF="\r\n" if withRCLF else ""
# send cmde if order not empty
if commande:
connexion.send("%s%s" % (commande,RCLF))
# possible echo
if verbose:
affiche(commande,1)
# read response of less than 1000 characters
reponse=connexion.recv(1000)
# possible echo
if verbose:
affiche(reponse,2)
# error code recovery
codeErreur=reponse[0:3]
# error returned by the server?
if int(codeErreur) >=500:
return reponse[4:]
# error-free return
return ""
# --------------------------------------------------------------------------
def affiche(echange,sens):
# displays exchange ? screen
# if sens=1 displays -->change
# if sens=2 displays <-- exchange without last 2 characters RCLF
if sens==1:
print "--> [%s]" % (echange)
return
elif sens==2:
l=len(echange)
print "<-- [%s]" % echange[0:l-2]
return
# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
# delete the [line] end-of-line mark if it exists
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) for sending a message
# information is taken from a INFOS file containing the following lines
# line 1: smtp, sender, recipient
# next lines: message text
# sender:email sender
# recipient: email recipient
# smtp: name of smtp server to use
# communication protocol SMTP client-server
# -> client connects to smtp server port 25
# <- server sends him a welcome message
# -> customer sends command EHLO: machine name
# <- server responds OK or not
# -> customer sends mail from: <exp?diteur> command
# <- server responds OK or not
# -> client sends the rcpt to command: <recipient>
# <- server responds OK or not
# -> customer sends data order
# <- server responds OK or not
# -> client sends all the lines of its message and ends with a line containing the single character .
# <- server responds OK or not
# -> customer sends quit order
# <- server responds OK or not
# server responses have the form xxx text where xxx is a 3-digit number. Any number xxx >=500
# indicates an error. The answer may consist of several lines all beginning with xxx- except the last one
# of the form xxx(space)
# exchanged text lines must end with RC(#13) and LF(#10) characters
# # Mailing parameters
MAIL="mail2.txt"
# retrieve mail parameters
res=getInfos(MAIL)
# mistake?
if res[0]:
print "%s" % (erreur)
sys.exit()
# sending mail in verbose mode
(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)
# end
sys.exit()
|
Note:
- Su un computer Windows con software antivirus, l'antivirus potrebbe impedire allo script Python di connettersi alla porta 25 di un server SMTP. È quindi necessario disattivare l'antivirus. Per McAfee, ad esempio, è possibile procedere come segue:
- In [1], aprire la console di VirusScan
- In [2], arrestare il servizio [Protezione su accesso]
- in [3], è stato arrestato
Risultati
Il file infos.txt:
smtp.univ-angers.fr, serge.tahe@univ-angers.fr , serge.tahe@univ-angers.fr
Subject: test
ligne1
ligne2
ligne3
Risultati dello schermo:
| 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
|
Il messaggio come visualizzato dal client di posta elettronica Thunderbird:
11.4. Un secondo client SMTP
Questo secondo script fa la stessa cosa del precedente, ma utilizza le funzionalità del modulo [smtplib].
Programma (inet_04)
| # -*- coding=utf-8 -*-
import sys,socket, smtplib
#-----------------------------------------------------------------------
def getInfos(fichier):
# returns the information (smtp,sender,recipient,message) taken from the text file [file]
# line 1: smtp, sender, recipient
# next lines: message text
# open [file]
erreur=""
try:
infos=open(fichier,"r")
except IOError, erreur:
return ("Le fichier %s n'a pu etre ouvert en lecture : %s" % (fichier, erreur))
# read the 1st line
ligne=infos.readline()
# delete end-of-line mark
ligne=cutNewLineChar(ligne)
# retrieve smtp, sender, recipient fields
champs=ligne.split(",")
# do we have the right number of fields?
if len(champs)!=3 :
return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
# "processing" the information recovered - removing the "blanks" that precede or follow them
for i in range(3):
champs[i]=champs[i].strip()
# field recovery
(smtpServer,expediteur,destinataire)=champs
# read message to send
message=""
ligne=infos.readline()
while ligne!='':
message+=ligne
ligne=infos.readline()
infos.close()
# return
return ("",smtpServer,expediteur,destinataire,message)
#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
# sends message to smtp server smtpserver from sender
# as recipient. If verbose=True, tracks client-server exchanges
# we use the smtplib library
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)
# end
return "Message envoye"
# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
# delete the [line] end-of-line mark if it exists
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) for sending a message
# information is taken from a INFOS file containing the following lines
# line 1: smtp, sender, recipient
# next lines: message text
# sender:email sender
# recipient: email recipient
# smtp: name of smtp server to use
# communication protocol SMTP client-server
# -> client connects to smtp server port 25
# <- server sends him a welcome message
# -> customer sends command EHLO: machine name
# <- server responds OK or not
# -> customer sends mail order from: <sender>
# <- server responds OK or not
# -> client sends the rcpt to command: <recipient>
# <- server responds OK or not
# -> customer sends data order
# <- server responds OK or not
# -> client sends all the lines of its message and ends with a line containing the single character .
# <- server responds OK or not
# -> customer sends quit order
# <- server responds OK or not
# server responses have the form xxx text where xxx is a 3-digit number. Any number xxx >=500
# indicates an error. The answer may consist of several lines all beginning with xxx- except the last one
# of the form xxx(space)
# exchanged text lines must end with RC(#13) and LF(#10) characters
# # Mailing parameters
MAIL="mail2.txt"
# retrieve mail parameters
res=getInfos(MAIL)
# mistake?
if res[0]:
print "%s" % (erreur)
sys.exit()
# sending mail in verbose mode
(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)
# end
sys.exit()
|
Note:
- Questo script è identico al precedente, tranne che per la funzione sendmail. Questa funzione ora utilizza le funzionalità del modulo [smtplib] (riga 3).
Risultati
Il file mail2.txt:
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
Risultati dello schermo:
| 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
|
11.5. Echo client/server
Creiamo un servizio echo. Il server restituisce tutte le righe di testo inviate dal client in maiuscolo. Il servizio può servire più client contemporaneamente utilizzando i thread.
Il programma server (inet_08)
| # -*- coding=utf-8 -*-
# generic tcp server on Windows
# loading header files
import re,sys,SocketServer,threading
# multi-threaded tcp server
class ThreadedTCPRequestHandler(SocketServer.StreamRequestHandler):
def handle(self):
# current thread
cur_thread = threading.currentThread()
# customer data
self.data="on"
# stop on empty chain
while self.data:
# the client's text lines are read using the readline method
self.data =self.rfile.readline().strip()
# console monitoring
print "client %s : %s (%s)" % (self.client_address[0],self.data,cur_thread.getName())
# reply to customer
response = "%s: %s" % (cur_thread.getName(), self.data.upper())
# self.wfile is the write stream to client
self.wfile.write(response)
class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
pass
# ------------------------------------------------------ main
# call syntax: argv[0] port
# the server is launched on the port named
# Data
syntaxe="syntaxe : %s port" % (sys.argv[0])
# ------------------------------------- check the call
# there must be one argument and one argument alone
nbArguments=len(sys.argv)
if nbArguments!=2:
print syntaxe
sys.exit(1)
# the port must be digital
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)
# server launch - serves each client on a thread
host="localhost"
server = ThreadedTCPServer((host,int(port)), ThreadedTCPRequestHandler)
# the server is launched in a thread
# each customer will be served in an additional thread
server_thread = threading.Thread(target=server.serve_forever)
# starts the server - infinite waiting loop for clients
server_thread.start()
# follow-up
print "Serveur d'echo a l'ecoute sur le port % s" % (port)
|
Note:
- riga 39: sys.argv rappresenta gli argomenti dello script. Qui devono essere nella forma: nome_script porta. Devono quindi essercene due. sys.argv[0] sarà quindi nome_script e sys.argv[1] sarà porta;
- riga 53: il server echo è un'istanza della classe ThreadedTcpServer. Il costruttore di questa classe richiede due parametri:
- parametro 1: una tupla a due elementi (host,port) che specifica la macchina e la porta di ascolto del server;
- parametro 2: il nome della classe responsabile della gestione delle richieste dei client.
- riga 57: viene creato un thread (ma non ancora avviato). Questo thread esegue il metodo [serve_forever] del server TCP. Questo metodo è un ciclo che ascolta le connessioni dei client. Non appena viene rilevata una connessione client, verrà creata un'istanza della classe ThreadedTCPRequestHandler. Il suo metodo handle è responsabile della comunicazione con il client;
- Riga 59: viene avviato il thread del servizio echo. Da questo momento in poi, i client possono connettersi al servizio;
- riga 27: la classe del server echo. Deriva da due classi:
*SocketServer.ThreadingMixIn* e SocketServer.TCPServer. Questo la rende un server TCP multithread: il server viene eseguito in un thread e ogni client viene servito in un thread aggiuntivo;
- Riga 9: la classe che gestisce le richieste dei client. Deriva dalla classe SocketServer.StreamRequestHandler. Eredita quindi due attributi:
- rfile: che è lo stream di lettura per i dati inviati dal client — può essere trattato come un file di testo;
- wfile: che è lo stream di scrittura utilizzato per inviare dati al client — può essere trattato come un file di testo.
- riga 11: il metodo handle elabora le richieste dei client;
- riga 13: il thread che esegue questo metodo handle;
- riga 17: ciclo per l'elaborazione delle richieste del client. Il ciclo termina quando il client invia una riga vuota;
- riga 19: legge la richiesta del client;
- riga 21: self.client_address[0] rappresenta l'indirizzo IP del client. cur_thread.getName() è il nome del thread che esegue il metodo handle;
- riga 23: la risposta al client ha due componenti: il nome del thread che serve il client e il comando inviato dal client, convertito in maiuscolo.
Il programma client (inet_06)
| # -*- coding=utf-8 -*-
import re,sys,socket
# ------------------------------------------------------ main
# generic tcp client
# call syntax: argv[0] host port
# client connects to echo service (host,port)
# the server returns the lines typed by the client
# syntax
syntaxe="syntaxe : %s hote port" % (sys.argv[0])
# ------------------------------------- check the call
# there must be two arguments
nbArguments=len(sys.argv)
if nbArguments!=3:
print syntaxe
sys.exit(1)
# we recover the arguments
hote=sys.argv[1]
# the port must be digital
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:
# client connection to server
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:
# input loop
ligne=raw_input("Commande (rien pour arreter): ").strip()
while ligne!="":
# send the line to the server
connexion.send("%s\n" % (ligne))
# we're waiting for the answer
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:
# close the connection
connexion.close()
|
Risultati
Il server viene avviato in una finestra di comando:
cmd>%python% inet_08.py 100
Serveur d'echo a l'ecoute sur le port 100
Viene avviato un primo client in una seconda finestra:
| 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):
|
Il client riceve, in risposta al comando che invia al server, lo stesso comando in maiuscolo. Viene avviato un secondo client in una terza finestra:
| 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 del server appare quindi così:
| 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)
|
I client vengono gestiti correttamente in thread diversi. Per interrompere un client, è sufficiente inserire un comando vuoto.
11.6. Server TCP generico
Proponiamo di scrivere uno script Python che
- funga da server TCP in grado di servire un cliente alla volta,
- accetti le righe di testo inviate dal client,
- accetti righe di testo dalla tastiera e le invii al cliente.
In questo modo, l'utente alla tastiera funge da server:
- vede le righe di testo inviate dal client sulla propria console;
- risponde al client digitando la risposta sulla tastiera.
Può quindi adattarsi a qualsiasi tipo di client. Ecco perché lo chiameremo "server TCP generico". È uno strumento pratico per esplorare i protocolli di comunicazione TCP. Nell'esempio seguente, il client TCP sarà un browser web, il che ci permetterà di esplorare il protocollo HTTP utilizzato dai client web.
Il programma (inet_10)
| # -*- coding=utf-8 -*-
# generic tcp server
# loading header files
import re,sys,SocketServer,threading
# generic tcp server
class MyTCPHandler(SocketServer.StreamRequestHandler):
def handle(self):
# the customer is displayed
print "client %s" % (self.client_address[0])
# create a thread for reading customer orders
thread_lecture = threading.Thread(target=self.lecture)
thread_lecture.start()
# stop on cmde 'bye
# cmde reading typed on keyboard
cmde=raw_input("--> ")
while cmde!="bye":
# send cmde to customer. self.wfile is the write flow to the customer
self.wfile.write("%s\n" % (cmde))
# next cmde reading
cmde=raw_input("--> ")
def lecture(self):
# displays all lines sent by the client until the bye command is received
ligne=""
while ligne!="bye":
# the client's text lines are read using the readline method
ligne = self.rfile.readline().strip()
# console monitoring
print "<--- %s : %s" % (self.client_address[0], ligne)
# ------------------------------------------------------ main
# call syntax: argv[0] port
# the server is launched on the port named
# Data
syntaxe="syntaxe : %s port" % (sys.argv[0])
# ------------------------------------- check the call
# there must be one argument and one argument alone
nbArguments=len(sys.argv)
if nbArguments!=2:
print syntaxe
sys.exit(1)
# the port must be digital
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)
# server launch
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()
|
Note:
- Riga 57: Il server TCP sarà un'istanza della classe SocketServer.TCPServer. Il suo costruttore accetta due parametri:
- il primo parametro è una tupla a due elementi (host, porta), dove host è la macchina su cui è in esecuzione il servizio (di solito localhost) e porta è la porta su cui il servizio attende (ascolta) le richieste dei client;
- il secondo parametro specifica la classe di servizio del client. Quando un client si connette, viene creata un'istanza della classe di servizio e il suo metodo handle deve gestire la connessione con il client.
Il Tcp SocketServer.TCPServer non è multithread. Serve un solo client alla volta;
- riga 59: viene eseguito il metodo
serve\_forever del server Tcp. Si tratta di un ciclo infinito che attende i client;
- riga 9: il server TCP qui è una classe derivata dalla classe SocketServer.StreamRequestHandler. Ciò consente di trattare i flussi di dati scambiati con il client come file di testo. Abbiamo già incontrato questa classe. Abbiamo i seguenti metodi:
- *
readline* per leggere una riga di testo dal client;
write per inviare righe di testo al client.
- Riga 12: self.client_address[0] è l'indirizzo IP del client;
- riga 14: il server TCP comunicherà con il client utilizzando due thread
- un thread per leggere le righe dal client;
- un thread per scrivere righe al client.
- riga 14: viene creato il thread per la lettura delle righe dal client. Il suo parametro
target specifica il metodo eseguito dal thread. Si tratta di quello definito alla riga 25;
- Riga 25: viene avviato il thread di lettura;
- Righe 25–32: il metodo eseguito dal thread di lettura;
- riga 28: il thread di lettura legge tutte le righe di testo inviate dal client fino a quando non viene ricevuta la riga "bye";
- riga 18: ci troviamo ora nel thread che scrive al client. L'idea è quella di inviare al client tutte le righe di testo digitate dall'utente sulla tastiera.
I risultati
Il server
dos>%python% inet_10.py 100
Service tcp generique lance sur le port 100. Arret par Ctrl-C
Il browser del cliente
La richiesta ricevuta dal server
| 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 :
|
Risposta del server digitata dall'utente sulla tastiera (senza il segno -->)
| 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
|
- Righe 1-5: risposta HTTP inviata al client;
- riga 6: pagina HTML inviata al client;
- Riga 7: Fine del dialogo con il client. Il servizio client terminerà e la connessione verrà chiusa, il che interromperà bruscamente il thread che legge le righe di testo inviate dal client;
- Le righe 1–5 della risposta HTTP inviata al client hanno i seguenti significati:
- Riga 1: La risorsa richiesta dal client è stata trovata;
- Riga 2: identificazione del server;
- Riga 3: il server chiuderà la connessione dopo aver inviato la risorsa;
- Riga 4: Tipo di risorsa inviata dal server: un documento HTML;
- Riga 5: una riga vuota.
La pagina visualizzata dal browser [1]:
Se osserviamo il codice sorgente ricevuto dal browser [2], possiamo vedere che il codice HTML ricevuto dal browser web è effettivamente quello che gli abbiamo inviato.