11. Las funciones de red de Python
Ahora abordaremos 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 una máquina en Internet
import sys, socket
#------------------------------------------------
def getIPandName(nomMachine):
#nomMachine: nombre de la máquina 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 guarda en el archivo site.html
# al principio no hay error
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 en caso de 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 comunicación 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 el canal de 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()
# regreso
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 las URL de los sitios web de los que se desea obtener la página de inicio. Esta lista 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((sitio, puerto)) permite establecer una conexión con un servicio TCP / IP que opera en el puerto port de la máquina «sitio»;
- 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 (Protocolo de transferencia HyperText);
- 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 a 11 son los encabezados HTTP de la respuesta del servidor;
- línea 1: el servidor le pide al cliente que se redirija al URL indicado 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 cierra 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 (Protocolo de transferencia SendMail) 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)
# se abre 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 de salida
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, agrega la secuencia RCLF al comando
# datos
RCLF="\r\n" if withRCLF else ""
# envía comando si el comando no está vacío
if commande:
connexion.send("%s%s" % (commande,RCLF))
# posible respuesta
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 dos ú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])
# 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 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 con 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, todas comenzando con xxx-, excepto la última
# con el formato xxx (espacio)
#: las líneas de texto intercambiadas deben terminar con los caracteres RC(#13) y LF(#10)
# # los parámetros para el 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 una computadora con Windows que cuente con un antivirus, es posible que este impida que el script de Python se conecte al puerto 25 de un servidor SMTP. En ese caso, es necesario 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], ya 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 lector de correo Thunderbird:
![]() |
11.4. Un segundo cliente SMTP
Este segundo script hace lo mismo que el anterior, pero utilizando las funcionalidades 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 en blanco» 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 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 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 con 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, todas comenzando con xxx-, excepto la última
# con el formato xxx (espacio)
# las líneas de texto intercambiadas deben terminar con los caracteres RC(#13) y LF(#10)
# # los parámetros para el 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 ahora utiliza 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 clientes al mismo tiempo gracias al uso de subprocesos.
# -*- coding=utf-8 -*-
# servidor TCP genérico en Windows
# carga de los 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"
# detener 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 la 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 inicia en el puerto denominado
# Datos
syntaxe="syntaxe : %s port" % (sys.argv[0])
#------------------------------------- se verifica la llamada
# debe haber un argumento y solo uno
nbArguments=len(sys.argv)
if nbArguments!=2:
print syntaxe
sys.exit(1)
# el puerto debe ser un número
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 clientes
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. Aquí deben tener el formato: nom_du_script puerto. Por lo tanto, deben ser dos. sys.argv[0] será entonces nom_du_script y sys.argv[1] será port ;
- 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 (aunque aún no se ha iniciado). Este hilo ejecuta el método [serve_forever] del servidor TCP. Este método es un bucle que escucha las conexiones de los clientes. Tan pronto como se detecte 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 clientes 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 los clientes. 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 los clientes;
- línea 13: el hilo que ejecuta este método;
- línea 17: bucle de procesamiento de las solicitudes del cliente. El bucle termina 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 envió, 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 (servidor, puerto)
# el servidor devuelve las líneas escritas por el cliente
# sintaxis
syntaxe="syntaxe : %s hote port" % (sys.argv[0])
#------------------------------------- se verifica 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 un número
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, como 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 se ve así:
Los clientes se atienden correctamente en hilos diferentes. 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
- sea un servidor TCP capaz de atender a un cliente a la vez,
- que acepte líneas de texto enviadas por el cliente,
- y que acepte líneas de texto provenientes del teclado, las cuales se envían como respuesta al cliente.
De esta manera, es el usuario en el teclado quien actúa como servidor:
- ve en su consola las líneas de texto enviadas por el cliente;
- y le responde al cliente al escribir la respuesta en el teclado.
De esta manera, puede adaptarse a cualquier tipo de cliente. Por eso lo llamaremos «servidor TCP genérico». Es una herramienta práctica para explorar los protocolos de comunicación TCP. En el siguiente ejemplo, el cliente TCP será un navegador web, lo que nos permitirá explorar el protocolo HTTP que utilizan los clientes web.
![]() |
# -*- coding=utf-8 -*-
# servidor TCP genérico
# carga de los archivos de encabezado
import re,sys,SocketServer,threading
# servidor TCP genérico
class MyTCPHandler(SocketServer.StreamRequestHandler):
def handle(self):
# se muestra el cliente
print "client %s" % (self.client_address[0])
# se crea un hilo para leer los comandos del cliente
thread_lecture = threading.Thread(target=self.lecture)
thread_lecture.start()
# se detiene al recibir el comando «bye»
# lectura del comando ingresado desde 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 verifica la llamada
# debe haber un argumento y solo uno
nbArguments=len(sys.argv)
if nbArguments!=2:
print syntaxe
sys.exit(1)
# el puerto debe ser un número
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. Su constructor admite dos parámetros:
- el primer parámetro es una tupla de dos elementos (host, puerto), 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 los clientes;
- 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 clientes;
- 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. Contamos con los métodos:
- readline para leer una línea de texto proveniente 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 hilo para leer las líneas del cliente;
- un hilo para escribir líneas al cliente.
- línea 14: se crea el hilo de lectura de las líneas del cliente. Su parámetro target establece el método que ejecutará el hilo. Es el definido en la línea 25;
- línea 25: se inicia el hilo 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 que el usuario escribe en el teclado.
El servidor
El navegador del cliente
La solicitud recibida por el servidor
La respuesta del servidor que el usuario escribió en 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 terminará y se cerrará la conexión, lo que interrumpirá abruptamente 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 después de enviar el recurso;
- línea 4: tipo de 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.




