Skip to content

11. Le funzioni di rete di Python

Passiamo ora alle funzioni di rete di Python che ci consentono di programmare in TCP / IP (Transfer 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: nome del computer di cui si desidera l'indirizzo IP
    # nomMachine-->indirizzo IP
    try:
        ip=socket.gethostbyname(nomMachine)
        print "ip[%s]=%s" % (nomMachine,ip)
    except socket.error, erreur:
        print "ip[%s]=%s" % (nomMachine,erreur)
        return

    # indirizzo 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

# costanti
HOTES=["istia.univ-angers.fr","www.univ-angers.fr","www.ibm.com","localhost","","xx"]

# indirizzi IP delle macchine di HOTES
for i in range(len(HOTES)):
    getIPandName(HOTES[i])
# fine
sys.exit()

Note:

  • riga 1: le funzioni di rete di Python sono incapsulate nel modulo socket.

Risultati

1
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

11.2. Un client web

Uno script che consente di ottenere il contenuto del paragrafo "index" di un sito web.


Programma (inet_02)


import sys,socket

  #-----------------------------------------------------------------------
def getIndex(site):
    # legge il sito URL e lo salva nel file site.html
  
    # inizialmente nessun errore
    erreur=""
    # creazione del file site.html
    try:
        html=open("%s.html" % (site),"w")
    except IOError, erreur:
        pass
    # errore?
    if erreur:
        return "Erreur (%s) lors de la création du fichier %s.html" % (erreur,site)

    # apertura di una connessione sulla porta 80 del sito
    try:
        connexion=socket.create_connection((site,80))
    except socket.error, erreur:
        pass
    # ritorno in caso di errore
    if erreur:
        return "Echec de la connexion au site (%s,80) : %s" % (site,erreur)

    # la connessione rappresenta un flusso di comunicazione bidirezionale
    # tra il client (questo programma) e il server web contattato
    # questo canale viene utilizzato per lo scambio di comandi e informazioni
    # il protocollo di comunicazione è HTTP

    # il client invia il comando GET per richiedere l'URL /
    # sintassi GET URL HTTP/1.0
    # le intestazioni (header) del protocollo HTTP devono terminare con una riga vuota
    connexion.send("GET / HTTP/1.0\n\n")

    # il server risponderà ora sul canale di connessione. Invierà tutti
    # i propri dati, quindi chiuderà il canale. Il client legge tutto ciò che arriva dal canale di connessione
    # fino alla chiusura del canale
    ligne=connexion.recv(1000)
    while(ligne):
        html.write(ligne)
        ligne=connexion.recv(1000)

    # il client chiude a sua volta la connessione
    connexion.close()
    # chiusura del file HTML
    html.close()
    # ritorno
    return "Transfert reussi du paragraphe index du site %s" % (site)

# --------------------------------------------- main    

# recupera il testo HTML da URL

# elenco dei siti web
SITES=("istia.univ-angers.fr","www.univ-angers.fr","www.ibm.com","xx")

# lettura delle pagine indice dei siti della tabella SITES
for i in range(len(SITES)):
      # lettura della pagina indice del sito SITES[i]
      resultat=getIndex(SITES[i])
      # visualizzazione del risultato
      print resultat

# fine
sys.exit()

Note:

  • riga 57: l'elenco degli URL dei siti web di cui si desidera la pagina iniziale. Questo elenco è memorizzato nel file di testo [nomsite.html];
  • riga 62: la funzione getIndex esegue l'operazione;
  • 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 del computer site;
  • riga 35: il metodo send consente di inviare dati tramite una connessione TCP / IP. In questo caso viene inviato del testo. Tale testo segue il protocollo HTTP (HyperText Transfer Protocol);
  • riga 40: il metodo recv consente di ricevere dati tramite una connessione TCP / IP. In questo caso, la risposta del server web viene letta in blocchi da 1000 caratteri e salvata nel file di testo [nomsite.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 richiede al client di reindirizzarsi all'indirizzo URL indicato alla 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 (SendMail Transfer Protocol) è il protocollo di comunicazione del servizio di invio dei messaggi.


Programma (inet_03)


# -*- coding=utf-8 -*-

import sys,socket

#-----------------------------------------------------------------------
def getInfos(fichier):
    # restituisce le informazioni (smtp, mittente, destinatario, messaggio) ricavate dal file di testo [fichier]
    # riga 1: smtp, mittente, destinatario
    # righe seguenti: il testo del messaggio

    # apertura di [fichier]
    erreur=""
    try:
        infos=open(fichier,"r")
    except IOError, erreur:
        return ("Le fichier %s n'a pu etre ouvert en lecture : %s" % (fichier, erreur))
  
    # lettura della prima riga
    ligne=infos.readline()
    # eliminazione del carattere di fine riga
    ligne=cutNewLineChar(ligne)
    # recupero dei campi smtp, mittente, destinatario
    champs=ligne.split(",")
    # il numero di campi è corretto?
    if len(champs)!=3 :
        return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
    # "elaborazione" delle informazioni recuperate
    # rimozione degli "spazi" che precedono o seguono le informazioni utili in ciascuno dei 3 campi
    for i in range(3):
        champs[i]=champs[i].strip()
    # recupero dei campi
    (smtpServer,expediteur,destinataire)=champs
    message=""
    # lettura del resto del messaggio
    ligne=infos.readline()
    while ligne!='':
        message+=ligne
        ligne=infos.readline()
    infos.close()
    # ritorno
    return ("",smtpServer,expediteur,destinataire,message)

#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
    # invia il messaggio al server SMTP smtpserver a nome del mittente
    # per il destinatario. Se verbose=True, tiene traccia degli scambi client-server

    # si recupera il nome del client
    try:
        client=socket.gethostbyaddr(socket.gethostbyname("localhost"))[0]
    except socket.error, erreur:
        return "Erreur IP / Nom du client : %s" % (erreur)
    
    # apertura di una connessione sulla porta 25 di smtpServer
    try:
        connexion=socket.create_connection((smtpServer,25))
    except socket.error, erreur:
        return "Echec de la connexion au site (%s,25) : %s" % (smtpServer,erreur)

    # la connessione rappresenta un flusso di comunicazione bidirezionale
    # tra il cliente (questo programma) e il server SMTP contattato
    # questo canale viene utilizzato per lo scambio di comandi e informazioni

    # dopo la connessione, il server invia un messaggio di benvenuto che viene letto
    erreur=sendCommand(connexion,"",verbose,1)
    if(erreur) :
        connexion.close()
        return erreur
    # comando ehlo:
    erreur=sendCommand(connexion,"EHLO %s" % (client),verbose,1)
    if erreur :
        connexion.close()
        return erreur
    # comando mail from:
    erreur=sendCommand(connexion,"MAIL FROM: <%s>" % (expediteur),verbose,1)
    if erreur :
        connexion.close()
        return erreur
    # comando rcpt to:
    erreur=sendCommand(connexion,"RCPT TO: <%s>" % (destinataire),verbose,1)
    if erreur :
        connexion.close()
        return erreur
    # comando data
    erreur=sendCommand(connexion,"DATA",verbose,1)
    if erreur :
        connexion.close()
        return erreur
    # preparazione del messaggio da inviare
    # deve contenere le righe
    # Da: mittente
    # A: destinatario
    # riga vuota
    # Messaggio
    # .
    data="From: %s\r\nTo: %s\r\n%s\r\n.\r\n" % (expediteur,destinataire,message)
    # invio del messaggio
    erreur=sendCommand(connexion,data,verbose,0)
    if erreur :
        connexion.close()
        return erreur
    # comando di uscita
    erreur=sendCommand(connexion,"QUIT",verbose,1)
    if erreur :
        connexion.close()
        return erreur
    # fine
    connexion.close()
    return "Message envoye"

# --------------------------------------------------------------------------
def sendCommand(connexion,commande,verbose,withRCLF):
    # invia comando nel canale di connessione
    # modalità verbosa se verbose=1
    # se withRCLF=1, aggiunge la sequenza RCLF al comando

    # dati
    RCLF="\r\n" if withRCLF else ""
    # invio comando se il comando non è vuoto
    if commande:
        connexion.send("%s%s" % (commande,RCLF))
        # eventuale eco
        if verbose:
            affiche(commande,1)
        # lettura della risposta con meno di 1000 caratteri
        reponse=connexion.recv(1000)
        # eventuale eco
        if verbose:
            affiche(reponse,2)
        # recupero codice di errore
        codeErreur=reponse[0:3]
        # errore restituito dal server?
        if int(codeErreur) >=500:
            return reponse[4:]
        # risposta senza errori
        return ""

# --------------------------------------------------------------------------
def affiche(echange,sens):
    # visualizza lo scambio sullo schermo?
    # se sens=1 visualizza -->scambio
    # se sens=2 visualizza <-- scambio senza gli ultimi 2 caratteri RCLF
    if sens==1:
        print "--> [%s]" % (echange)
        return
    elif sens==2:
        l=len(echange)
        print "<-- [%s]" % echange[0:l-2]
        return

      
# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
    # elimina il carattere di fine riga da [ligne] se presente
    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) che consente di inviare un messaggio
# le informazioni vengono prelevate da un file INFOS contenente le seguenti righe
# riga 1: smtp, mittente, destinatario
# righe successive: il testo del messaggio

# mittente: email del mittente
# destinatario: email del destinatario
# SMTP: nome del server SMTP da utilizzare


# protocollo di comunicazione client-server SMTP
# -> il client si connette alla porta 25 del server SMTP
# <- il server gli invia un messaggio di benvenuto
# -> il client invia il comando EHLO: nome del proprio computer
# <- il server risponde con OK oppure no
# -> il client invia il comando mail from: <mittente>
# <- il server risponde con OK oppure no
# -> il client invia il comando rcpt to: <destinatario>
# <- il server risponde con OK oppure no
# -> il client invia il comando data
# <- il server risponde con OK oppure no
# -> il client invia tutte le righe del proprio messaggio e termina con una riga contenente un solo carattere .
# <- il server risponde con OK oppure no
# -> il client invia il comando quit
# <- il server risponde con OK oppure no

# le risposte del server hanno il formato xxx testo, dove xxx è un numero a 3 cifre. Qualsiasi numero xxx >=500
# segnala un errore. La risposta può contenere più righe, tutte inizianti con xxx-, tranne l'ultima
# del formato xxx(spazio)

# le righe di testo scambiate devono terminare con i caratteri RC(#13) e LF(#10)

# # i parametri di invio dell’e-mail
MAIL="mail2.txt" 
# si recuperano i parametri dell’e-mail
res=getInfos(MAIL)
# errore?
if res[0]:
      print "%s" % (erreur)
      sys.exit()
# invio della posta in modalità verbosa
(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)
# fine
sys.exit()

Note:

  • su un computer Windows dotato di antivirus, quest’ultimo potrebbe impedire allo script Python di connettersi alla porta 25 di un server SMTP. In tal caso è necessario disattivare l’antivirus. Per McAfee, ad esempio, è possibile procedere come segue:
  • in [1], si attiva la console VirusScan
  • da [2], si arresta il servizio [Protection lors de l'accès]
  • in [3], il servizio è 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 sullo 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 visualizzato dal client di posta Thunderbird:

  

11.4. Un secondo client SMTP

Questo secondo script fa la stessa cosa del precedente, ma utilizzando le funzionalità del modulo [smtplib].


Programma (inet_04)


# -*- coding=utf-8 -*-

import sys,socket, smtplib

#-----------------------------------------------------------------------
def getInfos(fichier):
    # restituisce le informazioni (SMTP, mittente, destinatario, messaggio) ricavate dal file di testo [fichier]
    # riga 1: SMTP, mittente, destinatario
    # righe successive: il testo del messaggio

    # apertura di [fichier]
    erreur=""
    try:
        infos=open(fichier,"r")
    except IOError, erreur:
        return ("Le fichier %s n'a pu etre ouvert en lecture : %s" % (fichier, erreur))
  
    # lettura della prima riga
    ligne=infos.readline()
    # rimozione del carattere di fine riga
    ligne=cutNewLineChar(ligne)
    # recupero dei campi smtp, mittente, destinatario
    champs=ligne.split(",")
    # il numero di campi è corretto?
    if len(champs)!=3 :
        return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
    # "elaborazione" delle informazioni recuperate - rimozione degli "spazi" che le precedono o le seguono
    for i in range(3):
        champs[i]=champs[i].strip()
    # recupero dei campi
    (smtpServer,expediteur,destinataire)=champs
    # lettura del messaggio da inviare
    message=""
    ligne=infos.readline()
    while ligne!='':
        message+=ligne
        ligne=infos.readline()
    infos.close()
    # ritorno
    return ("",smtpServer,expediteur,destinataire,message)

#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
    # invia il messaggio al server SMTP smtpserver a nome del mittente
    # per il destinatario. Se verbose=True, tiene traccia degli scambi client-server

    # si utilizza la libreria 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)
    # fine
    return "Message envoye"
      
# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
    # si rimuove il carattere di fine riga da [ligne] se presente
    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) che consente di inviare un messaggio
# le informazioni vengono prelevate da un file INFOS contenente le seguenti righe
# riga 1: smtp, mittente, destinatario
# righe successive: il testo del messaggio

# mittente: email del mittente
# destinatario: email del destinatario
# SMTP: nome del server SMTP da utilizzare


# protocollo di comunicazione client-server SMTP
# -> il client si connette alla porta 25 del server SMTP
# <- il server gli invia un messaggio di benvenuto
# -> il client invia il comando EHLO: nome del proprio computer
# <- il server risponde con OK oppure no
# -> il client invia il comando mail from: <mittente>
# <- il server risponde con OK oppure no
# -> il client invia il comando rcpt to: <destinatario>
# <- il server risponde con OK oppure no
# -> il client invia il comando data
# <- il server risponde con OK oppure no
# -> il client invia tutte le righe del proprio messaggio e termina con una riga contenente un solo carattere .
# <- il server risponde con OK oppure no
# -> il client invia il comando quit
# <- il server risponde con OK oppure no

# le risposte del server hanno il formato xxx testo, dove xxx è un numero a 3 cifre. Qualsiasi numero xxx >=500
# segnala un errore. La risposta può contenere più righe, tutte inizianti con xxx-, tranne l'ultima
# del formato xxx(spazio)

# le righe di testo scambiate devono terminare con i caratteri RC(#13) e LF(#10)

# # i parametri di invio dell’e-mail
MAIL="mail2.txt" 
# si recuperano i parametri dell’e-mail
res=getInfos(MAIL)
# errore?
if res[0]:
      print "%s" % (erreur)
      sys.exit()
# invio dell'e-mail in modalità verbosa
(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)
# fine
sys.exit()

Note:

  • questo script è identico al precedente, fatta eccezione per la funzione sendmail. Questa funzione utilizza ora 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 sullo 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. Client/server di eco

Si crea un servizio di eco. Il server restituisce in maiuscolo tutte le righe di testo che gli vengono inviate dal client. Il servizio può servire più client contemporaneamente grazie all'utilizzo di thread.


Il programma del server (inet_08)


# -*- coding=utf-8 -*-

# server TCP generico su Windows

# caricamento dei file di intestazione
import re,sys,SocketServer,threading

# server TCP multithread
class ThreadedTCPRequestHandler(SocketServer.StreamRequestHandler):

  def handle(self):
    # thread corrente
    cur_thread = threading.currentThread()
    # dati del client
    self.data="on"
    # interruzione su stringa vuota
    while self.data:
      # le righe di testo del client vengono lette con il metodo readline
      self.data =self.rfile.readline().strip()
      # monitoraggio della console
      print "client %s : %s (%s)" % (self.client_address[0],self.data,cur_thread.getName())
      # invio della risposta al cliente
      response = "%s: %s" % (cur_thread.getName(), self.data.upper())
      # self.wfile è il flusso di scrittura verso il client
      self.wfile.write(response)                    

class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass

# ------------------------------------------------------ main        
# sintassi di chiamata: argv[0] porta
# il server viene avviato sulla porta indicata

# Dati
syntaxe="syntaxe : %s port" % (sys.argv[0])

#------------------------------------- si verifica la chiamata
# deve esserci un solo argomento
nbArguments=len(sys.argv)
if nbArguments!=2:
    print syntaxe
    sys.exit(1)

# la porta deve essere un numero
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)

# avvio del server - serve ogni client su un thread
host="localhost"
server = ThreadedTCPServer((host,int(port)), ThreadedTCPRequestHandler)

# il server viene avviato in un thread
# ogni client verrà servito in un thread aggiuntivo
server_thread = threading.Thread(target=server.serve_forever)
# avvia il server - ciclo infinito di attesa dei clienti
server_thread.start()
# monitoraggio
print "Serveur d'echo a l'ecoute sur le port % s" % (port)

Note:

  • riga 39: sys.argv rappresenta i parametri dello script. Devono avere la forma: nom_du_script porta. Devono quindi essercene due. sys.argv[0] diventerà quindi nom_du_script e sys.argv[1] diventerà port ;
  • riga 53: il server di eco è un'istanza della classe ThreadedTcpServer. Il costruttore di questa classe richiede due parametri:
    • parametro 1: una tupla di due elementi (host,port) che specifica il computer e la porta di ascolto del server;
    • parametro 2: il nome della classe incaricata di elaborare le richieste di un 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 di ascolto delle connessioni dei client. Non appena viene rilevata una connessione client, verrà creata un'istanza della classe ThreadedTCPRequestHandler. Il suo metodo handle è incaricato del dialogo con il client;
  • riga 59: viene avviato il thread del servizio di eco. Da questo momento in poi i client possono connettersi al servizio;
  • riga 27: la classe del server di eco. Deriva da due classi: SocketServer.ThreadingMixIn e SocketServer.TCPServer. Ciò lo rende un server TCP multithread: il server viene eseguito in un thread e ogni cliente viene servito in un thread aggiuntivo;
  • riga 9: la classe che gestisce le richieste dei clienti. Deriva dalla classe SocketServer.StreamRequestHandler. Eredita quindi due attributi:
    • rfile: che rappresenta il flusso di lettura dei dati inviati dal cliente – può essere trattato come un file di testo;
    • wfile: che rappresenta il flusso di scrittura che consente di inviare dati al cliente; può essere trattato come un file di testo.
  • riga 11: il metodo handle gestisce le richieste dei clienti;
  • riga 13: il thread che esegue questo metodo;
  • riga 17: ciclo di elaborazione delle richieste del cliente. Il ciclo termina quando il cliente invia una riga vuota;
  • riga 19: lettura della richiesta del cliente;
  • riga 21: self.client_address[0] rappresenta l’indirizzo IP del cliente. cur_thread.getName() è il nome del thread che esegue il metodo handle;
  • riga 23: la risposta al cliente è composta da due parti: il nome del thread che serve il cliente e il comando che questi ha inviato, scritto in maiuscolo.

Il programma del cliente (inet_06)


# -*- coding=utf-8 -*-
import re,sys,socket

# ------------------------------------------------------ main        
# client TCP generico
# sintassi della chiamata: argv[0] host porta
# il client si connette al servizio echo (host, porta)
# il server restituisce le righe digitate dal cliente

# sintassi
syntaxe="syntaxe : %s hote port" % (sys.argv[0])

#------------------------------------- si verifica la chiamata
# devono esserci due argomenti
nbArguments=len(sys.argv)
if nbArguments!=3:
    print syntaxe
    sys.exit(1)

# si recuperano gli argomenti
hote=sys.argv[1]
# la porta deve essere un numero
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:
  # connessione del client al 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:
    # ciclo di immissione
    ligne=raw_input("Commande (rien pour arreter): ").strip()
    while ligne!="":
        # si invia la riga al server
        connexion.send("%s\n" % (ligne))
        # si attende la risposta
        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:
    # si chiude la connessione
    connexion.close()

I risultati

Il server viene avviato in una prima finestra di comando:

cmd>%python% inet_08.py 100
Serveur d'echo a l'ecoute sur le port 100

Un primo client viene avviato in una seconda finestra:

1
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):

Il client riceve correttamente, in risposta al comando inviato al server, lo stesso comando scritto in maiuscolo. Viene avviato un secondo client in una terza finestra:

1
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 del server appare quindi come segue:

1
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)

I client vengono effettivamente gestiti in thread diversi. Per terminare un client, è sufficiente digitare un comando vuoto.

11.6. Server TCP generico

Ci proponiamo di scrivere uno script Python che

  • funga da server TCP in grado di servire un solo client alla volta,
  • accetti righe di testo inviate dal client,
  • accetti righe di testo provenienti dalla tastiera e che vengano inviate in risposta al client.

In questo modo è l'utente alla tastiera a fungere da server:

  • vede sulla propria console le righe di testo inviate dal cliente;
  • risponde a quest'ultimo digitando la risposta sulla tastiera.

In questo modo può adattarsi a qualsiasi tipo di client. Per questo motivo lo chiameremo "server TCP generico". Si tratta di uno strumento utile per esplorare i protocolli di comunicazione TCP. Nell'esempio che segue, il client TCP sarà un browser web, il che ci consentirà di esplorare il protocollo HTTP utilizzato dai client web.


Il programma (inet_10)


# -*- coding=utf-8 -*-

# server TCP generico

# caricamento dei file di intestazione
import re,sys,SocketServer,threading

# server TCP generico
class MyTCPHandler(SocketServer.StreamRequestHandler):
    def handle(self):
        # visualizzazione del client
        print "client %s" % (self.client_address[0])
        # si crea un thread per la lettura dei comandi del client
        thread_lecture = threading.Thread(target=self.lecture)
        thread_lecture.start()
        # interruzione al comando 'bye'
        # lettura del comando digitato dalla tastiera
        cmde=raw_input("--> ")
        while cmde!="bye":
            # invio del comando al client. self.wfile è il flusso di scrittura verso il client
            self.wfile.write("%s\n" % (cmde))
            # lettura del comando successivo
            cmde=raw_input("--> ")

    def lecture(self):
        # vengono visualizzate tutte le righe inviate dal client fino alla ricezione del comando "bye"
        ligne=""
        while ligne!="bye":
            # le righe di testo del client vengono lette con il metodo readline
            ligne = self.rfile.readline().strip()
            # monitoraggio della console
            print "<--- %s : %s" % (self.client_address[0], ligne)
    
# ------------------------------------------------------ main        
# sintassi di chiamata: argv[0] porta
# il server viene avviato sulla porta specificata

# Dati
syntaxe="syntaxe : %s port" % (sys.argv[0])

#------------------------------------- si verifica la chiamata
# deve esserci un solo argomento
nbArguments=len(sys.argv)
if nbArguments!=2:
    print syntaxe
    sys.exit(1)

# la porta deve essere un numero
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)

# avvio del server
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 di due elementi (host, porta) in cui host è la macchina su cui opera il servizio (generalmente localhost) e port, la porta su cui il servizio attende (ascolta) le richieste dei client;
    • il secondo parametro specifica la classe di servizio di un client. Quando un client si connette, viene creata un’istanza della classe di servizio ed è il suo metodo handle che deve gestire la connessione con il client.

Il server TCP SocketServer.TCPServer non è multithread. Serve un cliente alla volta;

  • riga 59: viene eseguito il metodo serve_forever del server TCP. Si tratta di un ciclo infinito di attesa dei clienti;
  • riga 9: il server TCP è qui una classe derivata dalla classe SocketServer.StreamRequestHandler. Ciò consente di considerare i flussi di dati scambiati con il client come file di testo. Abbiamo già incontrato questa classe. Abbiamo a disposizione i metodi:
    • readline per leggere una riga di testo proveniente dal client;
    • write per inviargli delle righe di testo.
  • riga 12: self.client_address[0] è l’indirizzo IP del cliente;
  • riga 14: il server TCP comunicherà con il client tramite due thread
    • un thread per la lettura delle righe dal cliente;
    • un thread per la scrittura delle righe al client.
  • riga 14: viene creato il thread di lettura delle righe del client. Il suo parametro target definisce 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 alla ricezione della riga "bye";
  • riga 18: ci troviamo nel thread di scrittura verso il client. Il principio è quello 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 client

 

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 :

La risposta del server digitata dall'utente sulla tastiera (senza il segno -->)

1
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
  • righe 1-5: risposta HTTP inviata al client;
  • riga 6: la pagina HTML inviata al client;
  • riga 7: fine del dialogo con il cliente. Il servizio al cliente terminerà e la connessione verrà chiusa, il che interromperà bruscamente il thread di lettura delle righe di testo inviate dal cliente;
  • le righe 1-5 della risposta HTTP inviata al cliente hanno il seguente significato:
  • riga 1: la risorsa richiesta dal cliente è 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 si visualizza il codice sorgente ricevuto dal browser [2], si nota che il codice HTML ricevuto dal browser web è effettivamente quello che gli era stato inviato.