Sockets en Python

De ChuWiki
Saltar a: navegación, buscar

Ejemplo sencillo de sockets con python

En este tutorial vamos a hacer un pequeño servidor python y un cliente python que se conectan con socket, se intercambian unas cadenas de texto y cierran la conexión. Aquí tienes los fuentes completos del ejemplo de sockets con python.


El servidor

En el servidor, primero establecemos el socket servidor. Para ello, usamos el módulo socket de python, haciendo el import correspondiente. Damos los siguientes pasos:

  • Llamada a la función socket(), a la que le pasamos el tipo de socket que queremos abrir (en el ejemplo, AF_INET, SOCK_STREAM que es el habitual).
  • Llamada a la función bind(), pasándole un address compuesto por (host, puerto). Como hacemos de servidor, pasamos "" como host y puerto el que queramos que esté libre (8000). La llamada a bind() le indica al sistema operativo que nosotros vamos a atender las conexiones por el puerto 8000.
  • Llamada a listen(). Esta llamada indica al sistema operativo que ya estamos listos para admitir conexiones. El número 1 de parámetro indica cuantos clientes podemos tener encolados en espera simultáneamente. Este número no debería ser grande, puesto que es el número máximo de clientes que quedarán encolados desde que aceptamos un cliente hasta que estamos dispuestos a aceptar el siguiente. Si el código está bien hecho, este tiempo debería ser realmente pequeño, ya que al conectarse un cliente, deberíamos lanzar un hilo para atenderlo y entrar inmediatamente a la espera de otro cliente.

El código de todo esto puede ser

import socket

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("", 8000))
server.listen(1)

Ahora nos metemos en un bucle eterno, esperando clientes y atendiéndolos. Para esperar un cliente, la llamada es accept(), que nos devuelve un par (socket_cliente, datos_cliente) del que obtenemos el socket para hablar con el cliente y sus datos (ip, puerto).

   # bucle para atender clientes
   while 1:
      # Se espera a un cliente
      socket_cliente, datos_cliente = server.accept()
      # Se escribe su informacion
      print "conectado "+str(datos_cliente)

Ahora hacemos otro bucle para recibir y atender los datos del cliente. Deberíamos meter este bucle en un hilo, de forma que el hilo principal vuelva al accept() y sea capaz de atender más clientes mientras atendemos al primero, pero no vamos a hacerlo para no liar el código.

Para leer los datos del cliente, usamos recv(), pasando como parámetro el número máximo de bytes que queremos leer de una tacada. La lectura se quedará bloqueada hasta que llegue algo del cliente. En cuanto lleguen datos, la llamada nos devolverá esos datos y no esperará hasta el máximo que hemos indicado. El bucle para atender al cliente puede quedar así

      # Bucle indefinido hasta que el cliente envie "adios"
      seguir = True
      while seguir:
         # Espera por datos
         peticion = socket_cliente.recv(1000)
         
         # Contestacion a "hola"
         if ("hola"==peticion):
             print str(datos_cliente)+ " envia hola: contesto"
             socket_cliente.send("pues hola")
             
         # Contestacion y cierre a "adios"
         if ("adios"==peticion):
             print str(datos_cliente)+ " envia adios: contesto y desconecto"
             socket_cliente.send("pues adios")
             socket_cliente.close()
             print "desconectado "+str(datos_cliente)
             seguir = False

La variable seguir nos hará permanecer en el bucle hasta que la pongamos a False, cosa que haremos cuando cerremos la conexión con el cliente. recv(1000) lee las peticiones del cliente y con los if comprobamos si lo recibido es "hola" o "adios". Para contestar usamos send(), enviando la cadena de respuesta. Si es "adios", además de contestar, cerramos el socket y ponemos a False la variable seguir, para terminar el bucle.

El código completo está en servidor.py

El cliente

Para el cliente, creamos el socket con la llamada a socket(), igual que en el servidor, pero establecemos la conexión con connect(), indicando el host servidor (localhost en nuestro ejemplo) y el puerto.

import socket
...
# Se establece la conexion
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("localhost", 8000))

El resto es más sencillo que en el servidor. Envíamos "hola" con send(), esperamos y escribimos la respuesta con recv(), hacemos una espera de 2 segundos con sleep(), enviamos con send() la cadena "adios", esperamos y escribimos respuesta con recv() y finalmente cerramos el socket con close()

import time
...
# Se envia "hola"
s.send("hola")
# Se recibe la respuesta y se escribe en pantalla
datos = s.recv(1000)
print datos    
# Espera de 2 segundos
time.sleep(2)    
# Se envia "adios"
s.send("adios")    
# Se espera respuesta, se escribe en pantalla y se cierra la
# conexion
datos = s.recv(1000)
print datos
s.close()

Aquí tienes el código completo de cliente.py

Ponemos un hilo al servidor

Como comentamos antes, la forma correcta de hacer el servidor es haciendo que cree un nuevo hilo para atender a cada cliente. De esta forma, el servidor podrá seguir aceptando clientes mientras atiende simultáneamente a los ya conectados.

Para la creación del hilo en python usaremos la clase Thread del módulo threading de python. Nos basta hacer una clase Cliente hija de Thread y ponerle el método run(). En ese método haremos el bucle para atender al cliente. Esta clase necesitará los datos del cliente así como el socket que debe atender. Por ello, metemos ambos parámetros en el constructor de la clase para almacenarlos. El código puede ser este

import threading
...
#Clase con el hilo para atender a los clientes.
#En el constructor recibe el socket con el cliente y los datos del
#cliente para escribir por pantalla
class Cliente(Thread):
    def __init__(self, socket_cliente, datos_cliente):
        # LLamada al constructor padre, para que se inicialice de forma
        # correcta la clase Thread.
        Thread.__init__(self)
        # Guardamos los parametros recibidos.
        self.socket = socket_cliente
        self.datos = datos_cliente
 
    # Bucle para atender al cliente.       
    def run(self):
      # Bucle indefinido hasta que el cliente envie "adios"
      seguir = True
      while seguir:
         # Espera por datos
         peticion = self.socket.recv(1000)
         
         # Contestacion a "hola"
         if ("hola"==peticion):
             print str(self.datos)+ " envia hola: contesto"
             self.socket.send("pues hola")
             
         # Contestacion y cierre a "adios"
         if ("adios"==peticion):
             print str(self.datos)+ " envia adios: contesto y desconecto"
             self.socket.send("pues adios")
             self.socket.close()
             print "desconectado "+str(self.datos)
             seguir = False

Vemos que en el método run() el código es prácticamente igual al que teníamos en el servidor de python para atender al cliente. En dicho servidor, ahora solo tenemos que instanciar una clase Cliente cada vez que se conecte un cliente, pasándole como parámetros el socket del cliente y sus datos.

   # bucle para atender clientes
   while 1:
      # Se espera a un cliente
      socket_cliente, datos_cliente = server.accept()
      # Se escribe su informacion
      print "conectado "+str(datos_cliente)
      # Se crea la clase con el hilo
      hilo = Cliente(socket_cliente, datos_cliente)
      # y se arranca el hilo
      hilo.start()

Ahora, si arrancamos el servidor, podemos arrancar a la vez varios clientes y todos ellos serán atendidos simultáneamente. Aquí tienes el código completo de servidor_thread.py

Enlaces