Cómo crear tu propio servidor en python

Los que nos dedicamos al mundo de la informática y las tecnologías de la información nos encontramos, a menudo, con una situación en la que dos programas o dos dispositivos deben poder enviarse mensajes. Hoy os cuento cómo, de manera muy sencilla, podéis crear un ejemplo básico de aplicación cliente – servidor en Python. Este lenguaje es increíblemente discreto para estas tareas. Editas un par de archivos, los pones a correr y todo funciona en poquísimo tiempo. Al finalizar el tutorial os dejaré un enlace al repositorio de GitHub que contiene los archivos. ¡Comencemos!

Lo primero que debemos tener, evidentemente, es Python instalado. En sistemas Linux, como el utilizado para escribiros este tutorial, ya está instalado. Abre una terminal y escribe “Python” y te aparecerá informacińo acerca de la versión que tienes instalada. Podrás ejecutar código en tiempo real cuando la herramienta esté en ejecución.

python linux ubuntuSi usas Windows no lo tendrás instalado por defecto. Así que antes de seguir, visita la web de Python e instálalo en tu equipo.

Para crear comunicación entre dos programas utilizaremos sockets. Los sockets son un concepto abstracto. Con ellos dos programas pueden comunicarse. Estos programas pueden estar en la misma máquina o bien ejecutarse en dispositivos diferentes.

Para poder usar sockets debemos importarlos, tanto en el servidor como en el cliente.

Servidor

Lo primero de todo son las importaciones y la declaración con las variables necesarias para la conexión: la dirección IP del servidor, el puerto y el número máximo de conexiones:

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
# Ejemplo cliente - servidor en python
# Programa Servidor
# www.elfreneticoinformatico.com


#Importacines:
import socket #utilidades de red y conexion

#Definimos parámetros necesarios por defeccto
ip = "0.0.0.0"
puerto = 9797
dataConection = (ip, puerto)
conexionesMaximas = 5 #Podrán conectarse 5 clientes como máximo

Todas esas variables se pueden configurar por el usuario. Simplemente haz un input() para que el usuario introduzca manualmente la configuración que desee cada vez que arranque el programa. En este caso se trata solamente de un ejemplo, por lo que no es necesario.

Lo siguiente, ya que tenemos los datos necesarios, es crear el servidor. Realmente es un objeto de tipo socket que está a la escucha. Le indicamos que utilice IPv4 y TCP/IP. Podríamos también utilizar UDP o IPv6:

#Creamos el servidor.
#socket.AF_INET para indicar que utilizaremos Ipv4
#socket.SOCK_STREAM para utilizar TCP/IP (no udp)
socketServidor = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

socketServidor.bind(dataConection) #Asignamos los valores del servidor
socketServidor.listen(conexionesMaximas) #Asignamos el número máximo de conexiones

Nuestro socket ya está creado. Ahora debemos ponerlo a la escucha:

print("Esperando conexiones en %s:%s" %(ip, puerto))
cliente, direccion = socketServidor.accept()
print("Conexion establecida con %s:%s" %(direccion[0], direccion[1]))

El método socket.accept() permanecerá a la escucha hasta recibir cualquier petición. Después, en bucle, indicamos lo que el servidor debe hacer al recibir cada conexión:

#Bucle de escucha. En él indicamos la forma de actuar al recibir las tramas del cliente
while True:
    datos = cliente.recv(1024) #El número indica el número maximo de bytes
    if datos == "exit":
        cliente.send("exit")
        break
    print("RECIBIDO: %s" %datos)
    cliente.sendall("-- Recibido --")

En este bucle es donde está el núcleo de nuestro programa. Ahí podemos introducir lo que queramos. O bien que mande una respuesta, o bien (con un switch) que elabore una respuesta según el mensaje recibido o, si deseamos que esa petición la gestione otro programa, mandarla a otro programa. O guardar el mensaje recibido en una base de datos. En fin, aquí es donde se nos abren las posibilidades. El esquema de conexión es invariable, lo que sea para que la utilices es cosa tuya.

Finalmente, cuando se cierre la conexión, indicamos con un mensaje que esta se ha cerrado y cerramos el socket con el método socket.close():

print("------- CONEXIÓN CERRADA ---------")
socketServidor.close()

Nuestro servidor ya está terminado. Ahora, debemos crear el cliente que se conectará con él.

Cliente

El inicio del archivo es igual. Importamos las utilidades necesarias e inicializamos las variables con los parámetros para la conexión.

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
# Ejemplo cliente - servidor en python
# Programa Cliente
# www.elfreneticoinformatico.com

import socket #utilidades de red y conexion

#declaramos las variables
ipServidor = "127.0.0.1" #es lo mismo que "localhost" o "0.0.0.0"
puertoServidor = 9797

Después creamos de nuevo el socket, pero esta vez no está a la escucha, sino que lo conectamos con el servidor cuyos parámetros están indicados en las variables. Igual que en el servidor, es posible que el usuario codifique estos valores manualmente con un simple input() o una interfaz gráfica.

#Configuramos los datos para conectarnos con el servidor
#socket.AF_INET para indicar que utilizaremos Ipv4
#socket.SOCK_STREAM para utilizar TCP/IP (no udp)
#Estos protocolos deben ser los mismos que en el servidor
cliente = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
cliente.connect((ipServidor, puertoServidor))
print("Conectado con el servidor ---> %s:%s" %(ipServidor, puertoServidor))

Y finalmente indicamos lo que queremos hacer con la conexión. En este caso también lo haremos en bucle. Ya que la forma de funcionamiento que he elegido para este ejemplo es que el clietne mande un mensaje al servidor y el servidor le responda “Recibido”. Cuando el cliente reciba el mensaje de “Recibido”, volverá a pedir un mensaje al usuario para poder mandarlo de nuevo al servidor, y así infinitamente. Para cerrar la conexión, el usuario debe escribir en el cliente “exit” y mandar ese  mensaje al servidor. Al llegar al servidor, éste reenviará el mensaje “exit” al clinte, mostrará un mensaje de “Conexión cerrada” y cerrará la conexión. El cliente al recibir el mensaje “exit” del servidor hará lo mismo y la conexión terminará correctamente en ambos bandos. El código de este funcionamiento es el siguiente:

while True:
    msg = raw_input("> ")
    cliente.send(msg)
    respuesta = cliente.recv(4096)
    print(respuesta)
    if respuesta == "exit":
        break;

print("------- CONEXIÓN CERRADA ---------")
cliente.close()

Ejecución

Ya está todo terminado. Solo nos queda ejecutar ambos programas y probarlos.

Primero arrancamos el servidor (python servidor.py)  y se quedará a la espera de conexión:

A continuación, arrancamos el cliente (python cliente.py):

Y veremos que el servidor ya ha visto también la conexión:

Lo que debemos hacer ahora es escribir cualquier mensaje en el cliente. Como por ejemplo este:

Y al pulsar intro se enviará al servidor. Veremos lo siguiente:

Al recibir el mensaje, como especificamos en el código, enviaremos una sentencia de “Recibido” al cliente. Vemos en el cliente que la hemos recibido:

Probemos ahora a escribir exit:

Al recibir el exit el servidor cerrará la conexión. El cliente también ha recibido un exit y tambíen cerrará la conexión:

Si en vez de escribir exit seguimos introduciendo mensajes la conexión no se cerrará y podremos enviar infinitamente mensajes.

Obtención del código

Podéis descargaros estos archivos en este repositorio en GitHub. Nos vemos la semana que viene!

6 Comentarios

  1. Erick Iván Martínez Martínez

    10 enero, 2018 at 4:55 pm

    Muy buena explicación, todo muy entendible y claro. Me resultó de mucha utilidad.

    • Urbano Villanueva

      16 enero, 2018 at 6:11 pm

      Hola! Muchas gracias por tu comentario, sienta muy bien ver que el trabajo de uno ayude a la gente. Espero volver a verte por aquí. Saludos!

  2. Como podría hacer que funcionara en computadoras que están en dos países distintos por ejemplo?

    • Urbano Villanueva

      29 enero, 2018 at 11:24 pm

      Hola!
      Es muy interesante lo que comentas, pero me temo que es imposible.
      Para poder establecer esta comunicación directa de máquina a máquina es necesario que ambas máquinas tengan una IP fija y única.
      Por el modo de funcionamiento de nuestras redes cambiamos cada ciertos períodos de dirección y con una misma IP se conectan muchas personas a la vez al mismo proveedor de servicios.
      Si todo se alinea exactamente como debe teóricamente sí que es posible, pero este tipo de trafico es bloqueado totalmente por los proveedores de servicio o ISP por seguridad.
      Si por ejemplo tienes un servidor con un dominio o una IP estática sí puedes hacer pruebas.
      Ten en cuenta que todo internet funciona con capas. Si quieres conocermás en profundidad cómo sucede hay mucha información en internet acerca del modelo OSI.

      Seguramente te haya dejado con muchas preguntas, pero aunque parece una pregunta muy sencilla la respuesta es enormemente compleja. Espero por lo menos haberte abierto la puerta a un nuevo concepto y que puedas aprender.

      Nos vemos en futuros artículos. Pasa un buen día!

  3. johans cuellar

    3 mayo, 2018 at 12:28 am

    muy buen trabajo compañero ,en estos monmento estoy montandolo en los distros de windows 10 ( WSL) me conecta pero me salen errores
    lo intentare mas tarde en debian con una virtual en qemu , aqui te dejo el resultado el WSL
    johans@DESKTOP-T9HCTCP:~$ python3 cliente_socket1.py
    Conectado con el servidor —> 192.168.0.24:9797
    Traceback (most recent call last):
    File “cliente_socket1.py”, line 23, in
    msg = raw_input(“> “)
    NameError: name ‘raw_input’ is not defined
    johans@DESKTOP-T9HCTCP:~$

    y este es el mensaje del servidor
    johans@DESKTOP-T9HCTCP:~$ python3 cliente_socket1.py
    Conectado con el servidor —> 192.168.0.24:9797
    Traceback (most recent call last):
    File “cliente_socket1.py”, line 23, in
    msg = raw_input(“> “)
    NameError: name ‘raw_input’ is not defined
    johans@DESKTOP-T9HCTCP:~$

    denuevo lo felicito por su buen trabajo

    • Urbano Villanueva

      21 mayo, 2018 at 12:39 am

      Hola, es posible que estes utilizando una versión diferente de python o que hayas escrito mal algo. Python no es el lenguaje en el que más experiencia tengo, pero siempre que me ha salido el “name raw_input is not defined” ha sido por tener algo mal escrito como el nombre de una variable o algún paréntesis. Revisa tu código y tu versión de Python.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

*