11. Las funciones de red de Python
Ahora abordamos las funciones de red de Python que nos permiten programar TCP / IP (Protocolo de control de transferencia / Protocolo de Internet).
![]() |
11.1. Obtener el nombre o la dirección IP de un equipo de Internet
import sys, socket
#------------------------------------------------
def getIPandName(nomMachine):
#nomMachine: nombre del equipo cuya dirección se desea obtener IP
# nomMachine-->dirección IP
try:
ip=socket.gethostbyname(nomMachine)
print "ip[%s]=%s" % (nomMachine,ip)
except socket.error, erreur:
print "ip[%s]=%s" % (nomMachine,erreur)
return
# dirección 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
# ---------------------------------------- mano
# constantes
HOTES=["istia.univ-angers.fr","www.univ-angers.fr","www.ibm.com","localhost","","xx"]
# direcciones IP de las máquinas de HOTES
for i in range(len(HOTES)):
getIPandName(HOTES[i])
# fin
sys.exit()
Notas:
- línea 1: las funciones de red de Python están encapsuladas en el módulo socket.
11.2. Un cliente web
Un script que permite obtener el contenido del párrafo «index» de un sitio web.
import sys,socket
#-----------------------------------------------------------------------
def getIndex(site):
# lee el sitio URL y lo almacena en el archivo site.html
# al principio sin errores
erreur=""
# creación del archivo site.html
try:
html=open("%s.html" % (site),"w")
except IOError, erreur:
pass
# ¿error?
if erreur:
return "Erreur (%s) lors de la création du fichier %s.html" % (erreur,site)
# apertura de una conexión en el puerto 80 del sitio
try:
connexion=socket.create_connection((site,80))
except socket.error, erreur:
pass
# retorno si hay error
if erreur:
return "Echec de la connexion au site (%s,80) : %s" % (site,erreur)
# la conexión representa un flujo de comunicación bidireccional
# entre el cliente (este programa) y el servidor web contactado
# este canal se utiliza para el intercambio de comandos e información
# el protocolo de diálogo es HTTP
# el cliente envía el comando get para solicitar el URL /
# sintaxis get URL HTTP/1.0
# los encabezados (headers) del protocolo HTTP deben terminar con una línea en blanco
connexion.send("GET / HTTP/1.0\n\n")
# el servidor responderá ahora en el canal de conexión. Enviará todos
# sus datos y luego cerrará el canal. El cliente lee todo lo que llega por la conexión
# hasta que se cierre el canal
ligne=connexion.recv(1000)
while(ligne):
html.write(ligne)
ligne=connexion.recv(1000)
# el cliente cierra la conexión a su vez
connexion.close()
# cierre del archivo html
html.close()
# retorno
return "Transfert reussi du paragraphe index du site %s" % (site)
# --------------------------------------------- main
# obtener el texto HTML de URL
# lista de sitios web
SITES=("istia.univ-angers.fr","www.univ-angers.fr","www.ibm.com","xx")
# lectura de las páginas de índice de los sitios de la tabla SITES
for i in range(len(SITES)):
# lectura de la página de índice del sitio SITES[i]
resultat=getIndex(SITES[i])
# visualización del resultado
print resultat
# fin
sys.exit()
Notas:
- línea 57: la lista de Url de los sitios web de los que se desea la página de índice. Esta se almacena en el archivo de texto [nomsite.html];
- línea 62: la función getIndex realiza la tarea;
- línea 4: la función getIndex;
- línea 20: el método create_connection((site,port)) permite crear una conexión con un servicio TCP / IP que opera en el puerto port de la máquina site;
- línea 35: el método send permite enviar datos a través de una conexión TCP / IP. En este caso, lo que se envía es texto. Este texto sigue el protocolo HTTP (HyperText Transfer Protocol);
- línea 40: el método recv permite recibir datos a través de una conexión TCP / IP. Aquí, la respuesta del servidor web se lee en bloques de 1000 caracteres y se guarda en el archivo de texto [nomsite.html].
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
El archivo recibido para el sitio [www.ibm.com]:
- las líneas 1-11 son los encabezados HTTP de la respuesta del servidor;
- línea 1: el servidor solicita al cliente que se redirija a la URL indicada en la línea 8;
- línea 2: fecha y hora de la respuesta;
- línea 3: identidad del servidor web;
- línea 4: contenido enviado por el servidor. En este caso, una página HTML que comienza en la línea 13;
- línea 12: la línea vacía que termina los encabezados HTTP;
- líneas 13-19: la página HTML enviada por el servidor web.
11.3. Un cliente SMTP
Entre los protocolos TCP / IP, SMTP (SendMail Transfer Protocol) es el protocolo de comunicación del servicio de envío de mensajes.
# -*- coding=utf-8 -*-
import sys,socket
#-----------------------------------------------------------------------
def getInfos(fichier):
# devuelve la información (smtp, remitente, destinatario, mensaje) extraída del archivo de texto [fichier]
# línea 1: smtp, remitente, destinatario
# líneas siguientes: el texto del mensaje
# apertura 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))
# lectura de la primera línea
ligne=infos.readline()
# eliminación del carácter de fin de línea
ligne=cutNewLineChar(ligne)
# recuperación de los campos smtp, remitente, destinatario
champs=ligne.split(",")
# ¿Tenemos el número correcto de campos?
if len(champs)!=3 :
return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
# «procesamiento» de la información recuperada
# se eliminan de cada uno de los 3 campos los «espacios en blanco» que preceden o siguen a la información útil
for i in range(3):
champs[i]=champs[i].strip()
# recuperación de los campos
(smtpServer,expediteur,destinataire)=champs
message=""
# lectura del resto del mensaje
ligne=infos.readline()
while ligne!='':
message+=ligne
ligne=infos.readline()
infos.close()
# retorno
return ("",smtpServer,expediteur,destinataire,message)
#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
# envía el mensaje al servidor SMTP smtpserver en nombre del remitente
# para el destinatario. Si verbose=True, realiza un seguimiento de los intercambios entre el cliente y el servidor
# se recupera el nombre del cliente
try:
client=socket.gethostbyaddr(socket.gethostbyname("localhost"))[0]
except socket.error, erreur:
return "Erreur IP / Nom du client : %s" % (erreur)
# apertura de una conexión en el puerto 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)
# la conexión representa un flujo de comunicación bidireccional
# entre el cliente (este programa) y el servidor SMTP contactado
# este canal se utiliza para el intercambio de comandos e información
# tras la conexión, el servidor envía un mensaje de bienvenida que se lee
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
# preparación del mensaje para enviar
# debe contener las líneas
# De: remitente
# Para: destinatario
# línea en blanco
# Mensaje
# .
data="From: %s\r\nTo: %s\r\n%s\r\n.\r\n" % (expediteur,destinataire,message)
# envío del mensaje
erreur=sendCommand(connexion,data,verbose,0)
if erreur :
connexion.close()
return erreur
# comando salir
erreur=sendCommand(connexion,"QUIT",verbose,1)
if erreur :
connexion.close()
return erreur
# fin
connexion.close()
return "Message envoye"
# --------------------------------------------------------------------------
def sendCommand(connexion,commande,verbose,withRCLF):
# envía comando al canal de conexión
# modo detallado si verbose=1
# si withRCLF=1, añade la secuencia RCLF al comando
# datos
RCLF="\r\n" if withRCLF else ""
# envío de comando si el comando no está vacío
if commande:
connexion.send("%s%s" % (commande,RCLF))
# posible eco
if verbose:
affiche(commande,1)
# lectura de respuesta de menos de 1000 caracteres
reponse=connexion.recv(1000)
# eco eventual
if verbose:
affiche(reponse,2)
# recuperación del código de error
codeErreur=reponse[0:3]
# ¿error devuelto por el servidor?
if int(codeErreur) >=500:
return reponse[4:]
# respuesta sin error
return ""
# --------------------------------------------------------------------------
def affiche(echange,sens):
# ¿Muestra el intercambio en pantalla?
# si sens=1 muestra -->intercambio
# si sentido=2 muestra <-- intercambio sin los 2 últimos caracteres RCLF
if sens==1:
print "--> [%s]" % (echange)
return
elif sens==2:
l=len(echange)
print "<-- [%s]" % echange[0:l-2]
return
# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
# se elimina el marcador de fin de línea de [ligne] si existe
l=len(ligne)
while(ligne[l-1]=="\n" or ligne[l-1]=="\r"):
l-=1
return(ligne[0:l])
# mano ----------------------------------------------------------------
# cliente SMTP (Protocolo de transferencia SendMail) que permite enviar un mensaje
# la información se toma de un archivo INFOS que contiene las siguientes líneas
# línea 1: smtp, remitente, destinatario
# líneas siguientes: el texto del mensaje
# remitente: correo electrónico del remitente
# destinatario: correo electrónico del destinatario
# smtp: nombre del servidor smtp que se va a utilizar
# protocolo de comunicación SMTP cliente-servidor
# -> el cliente se conecta al puerto 25 del servidor SMTP
# <- el servidor le envía un mensaje de bienvenida
# -> el cliente envía el comando EHLO: nombre de su máquina
# <- el servidor responde OK o no
# -> el cliente envía el comando mail from: <remitente>
# <- el servidor responde OK o no
# -> el cliente envía el comando rcpt to: <destinatario>
# <- el servidor responde OK o no
# -> el cliente envía el comando data
# <- el servidor responde OK o no
# -> el cliente envía todas las líneas de su mensaje y termina con una línea que contiene un solo carácter .
# <- el servidor responde OK o no
# -> el cliente envía el comando quit
# <- el servidor responde OK o no
# las respuestas del servidor tienen el formato xxx texto, donde xxx es un número de 3 dígitos. Cualquier número xxx >=500
# indica un error. La respuesta puede contener varias líneas que comienzan todas por xxx-, excepto la última
# del formato xxx(espacio)
# las líneas de texto intercambiadas deben terminar con los caracteres RC(#13) y LF(#10)
# # los parámetros del envío del correo
MAIL="mail2.txt"
# se recuperan los parámetros del correo
res=getInfos(MAIL)
# ¿error?
if res[0]:
print "%s" % (erreur)
sys.exit()
# envío de correo en modo detallado
(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()
Notas:
- en un equipo Windows que tenga un antivirus, este podría impedir que el script de Python se conecte al puerto 25 de un servidor SMTP. En ese caso, hay que desactivar el antivirus. Para McAfee, por ejemplo, se puede proceder de la siguiente manera:
![]() |
- en [1], se activa la consola VirusScan
- en [2], se detiene el servicio [Protection lors de l'accès]
- en [3], está detenido
El archivo infos.txt:
smtp.univ-angers.fr, serge.tahe@univ-angers.fr , serge.tahe@univ-angers.fr
Subject: test
ligne1
ligne2
ligne3
Resultados en pantalla:
El mensaje leído por el cliente de correo Thunderbird:
![]() |
11.4. Un segundo cliente SMTP
Este segundo script hace lo mismo que el anterior, pero utilizando las funciones del módulo [smtplib].
# -*- coding=utf-8 -*-
import sys,socket, smtplib
#-----------------------------------------------------------------------
def getInfos(fichier):
# devuelve la información (smtp, remitente, destinatario, mensaje) extraída del archivo de texto [fichier]
# línea 1: smtp, remitente, destinatario
# líneas siguientes: el texto del mensaje
# apertura 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))
# lectura de la primera línea
ligne=infos.readline()
# eliminación del carácter de fin de línea
ligne=cutNewLineChar(ligne)
# recuperación de los campos smtp, remitente, destinatario
champs=ligne.split(",")
# ¿Tenemos el número correcto de campos?
if len(champs)!=3 :
return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
# «procesamiento» de la información recuperada: se eliminan los «espacios» que la preceden o la siguen
for i in range(3):
champs[i]=champs[i].strip()
# recuperación de los campos
(smtpServer,expediteur,destinataire)=champs
# lectura del mensaje que se va a enviar
message=""
ligne=infos.readline()
while ligne!='':
message+=ligne
ligne=infos.readline()
infos.close()
# retorno
return ("",smtpServer,expediteur,destinataire,message)
#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
# envía el mensaje al servidor SMTP smtpserver en nombre del remitente
# para el destinatario. Si verbose=True, realiza un seguimiento de los intercambios entre el cliente y el servidor
# se utiliza la biblioteca 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):
# se elimina el marcador de fin de línea de [ligne] si existe
l=len(ligne)
while(ligne[l-1]=="\n" or ligne[l-1]=="\r"):
l-=1
return(ligne[0:l])
# main ----------------------------------------------------------------
# cliente SMTP (protocolo de transferencia SendMail) que permite enviar un mensaje
# la información se toma de un archivo INFOS que contiene las siguientes líneas
# línea 1: smtp, remitente, destinatario
# líneas siguientes: el texto del mensaje
# remitente: correo electrónico del remitente
# destinatario: correo electrónico del destinatario
# smtp: nombre del servidor smtp que se va a utilizar
# protocolo de comunicación SMTP cliente-servidor
# -> el cliente se conecta al puerto 25 del servidor SMTP
# <- el servidor le envía un mensaje de bienvenida
# -> el cliente envía el comando EHLO: nombre de su máquina
# <- el servidor responde OK o no
# -> el cliente envía el comando mail from: <remitente>
# <- el servidor responde OK o no
# -> el cliente envía el comando rcpt to: <destinatario>
# <- el servidor responde OK o no
# -> el cliente envía el comando data
# <- el servidor responde OK o no
# -> el cliente envía todas las líneas de su mensaje y termina con una línea que contiene un solo carácter .
# <- el servidor responde OK o no
# -> el cliente envía el comando quit
# <- el servidor responde OK o no
# las respuestas del servidor tienen el formato xxx texto, donde xxx es un número de 3 dígitos. Cualquier número xxx >=500
# indica un error. La respuesta puede contener varias líneas que comienzan todas por xxx-, excepto la última
# del formato xxx(espacio)
# las líneas de texto intercambiadas deben terminar con los caracteres RC(#13) y LF(#10)
# # los parámetros del envío del correo
MAIL="mail2.txt"
# se recuperan los parámetros del correo
res=getInfos(MAIL)
# ¿error?
if res[0]:
print "%s" % (erreur)
sys.exit()
# envío del correo en modo detallado
(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()
Notas:
- este script es idéntico al anterior, salvo por la función sendmail. Esta función utiliza ahora las funcionalidades del módulo [smtplib] (línea 3).
El archivo 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
Resultados en pantalla:
11.5. Cliente/servidor de eco
Se crea un servicio de eco. El servidor devuelve en mayúsculas todas las líneas de texto que le envía el cliente. El servicio puede atender a varios clients a la vez gracias al uso de subprocesos.
# -*- coding=utf-8 -*-
# servidor tcp genérico en Windows
# carga de archivos de encabezado
import re,sys,SocketServer,threading
# servidor tcp multihilo
class ThreadedTCPRequestHandler(SocketServer.StreamRequestHandler):
def handle(self):
# hilo actual
cur_thread = threading.currentThread()
# datos del cliente
self.data="on"
# parada en cadena vacía
while self.data:
# las líneas de texto del cliente se leen con el método readline
self.data =self.rfile.readline().strip()
# seguimiento de consola
print "client %s : %s (%s)" % (self.client_address[0],self.data,cur_thread.getName())
# envío de respuesta al cliente
response = "%s: %s" % (cur_thread.getName(), self.data.upper())
# self.wfile es el flujo de escritura hacia el cliente
self.wfile.write(response)
class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
pass
# ------------------------------------------------------ main
# sintaxis de llamada: argv[0] puerto
# El servidor se ha iniciado en el puerto denominado
# Datos
syntaxe="syntaxe : %s port" % (sys.argv[0])
#------------------------------------- se comprueba la llamada
# debe haber un único argumento
nbArguments=len(sys.argv)
if nbArguments!=2:
print syntaxe
sys.exit(1)
# el puerto debe ser numérico
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)
# inicio del servidor: atiende a cada cliente en un hilo
host="localhost"
server = ThreadedTCPServer((host,int(port)), ThreadedTCPRequestHandler)
# el servidor se inicia en un hilo
# cada cliente será atendido en un hilo adicional
server_thread = threading.Thread(target=server.serve_forever)
# inicia el servidor: bucle infinito de espera de clients
server_thread.start()
# seguimiento
print "Serveur d'echo a l'ecoute sur le port % s" % (port)
Notas:
- línea 39: sys.argv representa los parámetros del script. Deben tener aquí la forma: nom_du_script puerto. Por lo tanto, debe haber dos. sys.argv[0] será entonces nom_du_script y sys.argv[1] será el puerto;
- línea 53: el servidor de eco es una instancia de la clase ThreadedTcpServer. El constructor de esta clase espera dos parámetros:
- parámetro 1: una tupla de dos elementos (host, puerto) que establece la máquina y el puerto de escucha del servidor;
- parámetro 2: el nombre de la clase encargada de procesar las solicitudes de un cliente.
- línea 57: se crea un hilo (pero aún no se inicia). Este hilo ejecuta el método [serve_forever] del servidor TCP. Este método es un bucle de escucha de conexiones de clientes. En cuanto se detecta una conexión de cliente, se creará una instancia de la clase ThreadedTCPRequestHandler. Su método handle se encarga del diálogo con el cliente;
- línea 59: se inicia el hilo del servicio de eco. A partir de este momento, los clients pueden conectarse al servicio;
- línea 27: la clase del servidor de eco. Deriva de dos clases: SocketServer.ThreadingMixIn y SocketServer.TCPServer. Esto lo convierte en un servidor TCP multihilo: el servidor se ejecuta en un hilo y cada cliente es atendido en un hilo adicional;
- línea 9: la clase que procesa las solicitudes de clients. Deriva de la clase SocketServer.StreamRequestHandler. Por lo tanto, hereda dos atributos:
- rfile: que es el flujo de lectura de los datos enviados por el cliente; puede tratarse como un archivo de texto;
- wfile: que es el flujo de escritura que permite enviar datos al cliente; puede tratarse como un archivo de texto.
- línea 11: el método handle procesa las solicitudes de clients;
- línea 13: el subproceso que ejecuta este método handle;
- línea 17: bucle de procesamiento de las solicitudes del cliente. El bucle finaliza cuando el cliente envía una línea vacía;
- línea 19: lectura de la solicitud del cliente;
- línea 21: self.client_address[0] representa la dirección IP del cliente. cur_thread.getName() es el nombre del hilo que ejecuta el método handle;
- línea 23: la respuesta al cliente tiene dos componentes: el nombre del hilo que atiende al cliente y el comando que este ha enviado, escrito en mayúsculas.
# -*- coding=utf-8 -*-
import re,sys,socket
# ------------------------------------------------------ main
# cliente tcp genérico
# sintaxis de la llamada: argv[0] host puerto
# el cliente se conecta al servicio de eco (host, puerto)
# el servidor devuelve las líneas escritas por el cliente
# sintaxis
syntaxe="syntaxe : %s hote port" % (sys.argv[0])
#------------------------------------- se comprueba la llamada
# debe haber dos argumentos
nbArguments=len(sys.argv)
if nbArguments!=3:
print syntaxe
sys.exit(1)
# se recuperan los argumentos
hote=sys.argv[1]
# el puerto debe ser numérico
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:
# conexión del cliente al servidor
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:
# bucle de entrada
ligne=raw_input("Commande (rien pour arreter): ").strip()
while ligne!="":
# se envía la línea al servidor
connexion.send("%s\n" % (ligne))
# se espera la respuesta
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:
# se cierra la conexión
connexion.close()
El servidor se inicia en una primera ventana de comandos:
Se inicia un primer cliente en una segunda ventana:
El cliente recibe correctamente, en respuesta al comando que envía al servidor, ese mismo comando escrito en mayúsculas. Se inicia un segundo cliente en una tercera ventana:
La consola del servidor queda entonces así:
Los clients se ejecutan correctamente en diferentes subprocesos. Para detener un cliente, basta con escribir un comando vacío.
11.6. Servidor Tcp genérico
Nos proponemos escribir un script en Python que
- sería un servidor Tcp capaz de atender a un cliente a la vez,
- que acepte líneas de texto enviadas por el cliente,
- aceptar líneas de texto procedentes del teclado y que se envían como respuesta al cliente.
De este modo, es el usuario que teclea quien actúa como servidor:
- ve en su consola las líneas de texto enviadas por el cliente;
- y le responde escribiendo la respuesta en el teclado.
De este modo, puede adaptarse a cualquier tipo de cliente. Por eso lo llamaremos «servidor Tcp genérico». Es una herramienta práctica para descubrir protocolos de comunicación Tcp. En el siguiente ejemplo, el cliente Tcp será un navegador web, lo que nos permitirá descubrir el protocolo Http utilizado por los clients web.
![]() |
# -*- coding=utf-8 -*-
# servidor tcp genérico
# carga de archivos de encabezado
import re,sys,SocketServer,threading
# servidor genérico tcp
class MyTCPHandler(SocketServer.StreamRequestHandler):
def handle(self):
# se muestra el cliente
print "client %s" % (self.client_address[0])
# se crea un subproceso para leer los comandos del cliente
thread_lecture = threading.Thread(target=self.lecture)
thread_lecture.start()
# parada ante el comando «bye»
# lectura del comando introducido con el teclado
cmde=raw_input("--> ")
while cmde!="bye":
# envío del comando al cliente. self.wfile es el flujo de escritura hacia el cliente
self.wfile.write("%s\n" % (cmde))
# lectura del siguiente comando
cmde=raw_input("--> ")
def lecture(self):
# se muestran todas las líneas enviadas por el cliente hasta recibir el comando «bye»
ligne=""
while ligne!="bye":
# las líneas de texto del cliente se leen con el método readline
ligne = self.rfile.readline().strip()
# seguimiento de la consola
print "<--- %s : %s" % (self.client_address[0], ligne)
# ------------------------------------------------------ main
# sintaxis de llamada: argv[0] puerto
# el servidor se inicia en el puerto indicado
# Datos
syntaxe="syntaxe : %s port" % (sys.argv[0])
#------------------------------------- se comprueba la llamada
# debe haber un único argumento
nbArguments=len(sys.argv)
if nbArguments!=2:
print syntaxe
sys.exit(1)
# el puerto debe ser numérico
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)
# inicio del servidor
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()
Notas:
- línea 57: el servidor Tcp será una instancia de la clase SocketServer.TCPServer. El constructor de esta clase admite dos parámetros:
- el primer parámetro es una tupla de dos elementos (host, port), donde host es la máquina en la que opera el servicio (generalmente localhost) y port, el puerto en el que el servicio espera (escucha) las solicitudes de clients;
- el segundo parámetro especifica la clase de servicio de un cliente. Cuando un cliente se conecta, se crea una instancia de la clase de servicio y es su método handle el que debe gestionar la conexión con el cliente.
El servidor Tcp SocketServer.TCPServer no es multihilo. Atiende a un cliente a la vez;
- línea 59: se ejecuta el método serve_forever del servidor Tcp. Se trata de un bucle infinito de espera de clients;
- línea 9: el servidor Tcp es aquí una clase derivada de la clase SocketServer.StreamRequestHandler. Esto permite considerar los flujos de datos intercambiados con el cliente como archivos de texto. Ya nos hemos encontrado con esta clase. Disponemos de los métodos:
- readline para leer una línea de texto procedente del cliente;
- write para enviarle líneas de texto.
- línea 12: self.client_address[0] es la dirección Ip del cliente;
- línea 14: el servidor Tcp se comunicará con el cliente mediante dos subprocesos
- un subproceso de lectura de las líneas del cliente;
- un subproceso para escribir líneas al cliente.
- línea 14: se crea el subproceso de lectura de las líneas del cliente. Su parámetro target establece el método que ejecuta el subproceso. Es el definido en la línea 25;
- línea 25: se inicia el subproceso de lectura;
- líneas 25-32: el método ejecutado por el hilo de lectura;
- línea 28: el hilo de lectura lee todas las líneas de texto enviadas por el cliente hasta recibir la línea «bye»;
- línea 18: aquí nos encontramos en el hilo de escritura al cliente. El principio consiste en enviar al cliente todas las líneas de texto tecleadas por el usuario.
El servidor
El navegador del cliente
La solicitud recibida por el servidor
La respuesta del servidor introducida por el usuario con el teclado (sin el signo -->)
- líneas 1-5: respuesta Http enviada al cliente;
- línea 6: la página Html enviada al cliente;
- línea 7: fin del diálogo con el cliente. El servicio al cliente va a terminar y la conexión se va a cerrar, lo que interrumpirá bruscamente el hilo de lectura de las líneas de texto enviadas por el cliente;
- las líneas 1-5 de la respuesta Http enviada al cliente tienen el siguiente significado:
- línea 1: se ha encontrado el recurso solicitado por el cliente;
- línea 2: identificación del servidor;
- línea 3: el servidor cerrará la conexión tras enviar el recurso;
- línea 4: naturaleza del recurso enviado por el servidor: un documento Html;
- línea 5: una línea vacía.
La página mostrada por el navegador [1]:
![]() |
Si se muestra el código fuente recibido por el navegador [2], se observa que el código HTML recibido por el navegador web es efectivamente el que se le había enviado.




