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!

21 comentarios

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

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

    • Urbano Villanueva

      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. jhesua

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

    • Urbano Villanueva

      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!

      • Saúl Ernesto

        Si se puede pero necesitas acceder a una página en mi caso seria esta : http://192.168.1.254/
        y abrir un puerto, en el código del cliente debe de usar la ip publica del y del lado del servidor la ip privada y usar los puertos abiertos.

        • Urbano Villanueva

          Si no solicitas (pagando) una IP estática, lo que comentas funciona hoy y mañana no

          • José

            Si se puede, sea ip estatica o dinamica, lo que necesitas realmente es tener un puerto abierto, en este caso es el puerto 9797, el cual debe ser abierto desde el router y ya.

            eso se debe hacer desde la ip que indica en la puerta de enlace predeterminada, una vez ingresando alli, se debe abrir el puerto escogido en la aplicacion desarrollada, y luego se puede conectar desde cualquier parte, siempre que se tenga la ip y el puerto para ingresar.

  3. johans cuellar

    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

      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.

    • juan jose jurado perez

      Hola amigo, ten en cuenta que raw_input se utiliza en python2 y solo input en python3!!!

      • Urbano Villanueva

        Buena consideración. Actualmente, y más aún desde que Ubuntu en la versión 18.04 ya usa Python 3 de serie, tengo la intención de actualizar todos mis proyectos en github a python 3 jeje, pero son las típicas cosas que quieres hacer y tardas años en ponerte.

  4. Angel Alvarez

    Tengo una pequeña duda, si quisiera mandar al cliente un archivo como una imagen o un documento desde el servidor a la pc cliente como lo haria

    • Urbano Villanueva

      No es difícil. Aunque para ese propósito usar una tecnología de un nivel más alto que un simple socket te irá mejor. Debes abrr un file stream para leer el archivo y lo guardas en una variable. La mandas y ejecutas el método de guardar en el cliente, usando también un file stream, pero para guardar en vez de para leer.

  5. Santiago

    Que tal, exelente post. Tengo varias consultas, con este protocolo, podria implementar el servidor en una raspberry y enviarle codigo para que ejecute, verdad?
    Hasta donde es posible explotar al maximo este tipo de implementaciones? Actualmente estoy procesando señales con python, y necesito realizar varias correlaciones, eso lleva tiempo de ejecucion en mi pc( que es mejor que la raspberry) pero pensaba que si, lograra derivarle procesos, podria dejarlo corriendo en ella y seguir en otra tarea.

    • Urbano Villanueva

      Muy buenas. Buena pregunta. En respuesta a ella: sí, claro que puedes enviar órdenes. Ahora bien, debes tener en cuenta que este es un ejemplo de comunicación a bajo nivel. Normalmente se utilizan frameworks y librerías que abstraen de la comunicación directa entre sockets, centrándote solo en lo que a tí te interesa. En tu caso, si tienes mucho tráfico de red, puede venirte bien subir de nivel y que el framework o librería se ocupe de gestionar las conexiones para, con poco trabajo, conseguir mejor optimización. No obstante, no puedo afirmar nada seguro si no conozco el proyecto concreto. ¿Lo tienes en github o cualquier repositorio público? Podría echarle un vistazo (bueno, más bien intentarlo, tengo poco tiempo actualmente) Saludos!

  6. Omar Santiago

    Hola amigo buen trabajo me encanto tu post pero tengo un problema :/ cuando intento ingresar algo del lado del cliente me sale este error
    cliente.sendall(«– Recibido –«)
    TypeError: a bytes-like object is required, not ‘str’
    en el servidor y en el cliente es el mismo error.
    Buen trabajo bro

    • Urbano Villanueva

      Hola Omar. Sintiéndolo mucho, no he sido capaz de reproducir tu error en mi máquina y no puedo ayudarte. Si tienes más información, o tu código, puedes ponerlo aquí a ver qué puedo hacer. Un saludo!

    • Al3X

      Hola, bueno debes codificar los datos antes de enviarlos.
      por Ejemplo:

      data=»Algo aqui»
      cliente.sendall(data.encode())

      O

      cliente.sendall(b»Algo aqui»)

  7. Clases de Ordenador

    Voy a empezar un curso de Python y voy a necesitar instalar un servidor. Muy util este blog Enorabuena al webmaster

    • Urbano Villanueva

      Muchas gracias!

  8. Angel

    Como seria para establecer comunicación entre dos clientes? sin qué los mensajes los visualice otro cliente o el mismo servidor?

Deja una respuesta

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

*