Nuestro propio servidor Software (Socket con Python)


Para crear nuestro Servidor paso a paso utilizaremos Sockets ¿Y qué se puede hacer con los Sockets? Pues partiendo de que mantienen la conexión en tiempo real, podremos enviar y recibir datos de un lado a otro. Por ejemplo, podremos crear nuestro propio chat; es decir, una aplicación de escritorio en nuestro ordenador (o Samartphone) recibiendo y enviando mensajes, para que el lado del servidor reciba y envíe los mensajes en tiempo real.

Otra cosa que podríamos hacer es la monitorización de cualquier cosa ¿Te suena «el Internet de las Cosas»? (por ejemplo, recoger en tiempo real datos de sensores y enviar estos datos a nuestro Servidor a través de placas como Arduino o Raspberry). Por ejemplo, podríamos monitorizar la presencia en nuestra casa para que nos avise al móvil al instante si algún sensor de movimiento detecta algo; o también, podríamos controlar el regadío de un jardín al enviar datos de temperatura y humedad al servidor, luego el Software del Servidor (que, por ejemplo, podría ser este Python mismo que vamos a ver en este artículo) tomaría la decisión de regar las plantas, de activar las lámparas infrarrojas, de cubrir las plantas con un toldo con motor, o un poco más industrial: activar los tractores robóticos para que recolecten exclusivamente las frutas/verduras que estén en su mejor momento de recolección cuyos datos están siendo monitorizados en tiempo real mediante sockets. Si tienes interés sobre como crear un socket con Arduino, al final del artículo, después de entender Sockets Cliente y Servidor te indico dónde aprender a usarlo, es igual de fácil que lo que vas a aprender aquí.

Para entender este artículo recomiendo tener claras las diferencias entre Cliente vs Servidor que explicamos en una cómoda lectura de unos pocos minutos pinchando aquí, donde también explicamos algunos peligros que hay que tener en cuenta. Otro artículo que está entrelazado con el anterior, y que es recomendable dedicar unos minutos a leerlo, es el que trata sobre la petición del cliente, que puedes leer pinchando aquí.

Por cierto, si lo que quieres es usar los Servidores Software ya creados (por ejemplo, crear tu propia web) te recomiendo que eches un vistazo a este artículo pinchando aquí. Por otro lado, si quieres entender a nivel técnico cómo funcionan los distintos Servidores y Servicios de Hosting puedes revisar ante el artículo previo pinchando aquí. Te lo recomiendo si estás empezando el desarrollo, ya que en este artículo se dan por entendidos ciertos detalles explicados en los anteriores (en otras palabras, los artículos que te acabe de recomendar, aunque son sueltos, bien pueden formar un curso completo para ser un profesional de Back-end y Front-end).

Socket

El Socket de red sirve para usar la conexión directa entre dos aplicaciones, como si ambas aplicaciones estuvieran conectadas la una a la otra sin nada intermedio (el socket abstrae de todo lo demás que hay por en medio, desde que sale el dato de una aplicación hasta que llega a otra mediante la red).

Desde la perspectiva de un desarrollador, se utiliza para programar aplicaciones sin preocuparse nada más que de recibir y enviar datos por el socket. Es decir, al programador le bastará poner dentro de un bucle infinito a un método semejante a este (la siguiente línea es pseudo-código, no la copies ya que no sirve para nada más que como ejemplo):

getDatosDesdeSocket(ip=“85.208.20.119”, port=50000) 

E ir obteniendo línea a línea los datos que vaya recibiendo desde la red; y sin preocuparse de otros temas de la red: ni de si hay puertos físicos por el camino de los datos, ni si hay servidores intermedios, ni si al dato le faltan cachos, etc. Por cierto, esto del bucle infinito quizá te ha sonado a lo que hacen los servidores, y sí con este tipo de métodos puedes crear tu propio servidor desde “cero”.

No confundir el «Socket de red» con el «Socket electrónico». El «Socket electrónico» también se llama «Zócalo», que en una placa base de la torre de un ordenador se suele llamar «Socket» al hueco físico (zócalo) para acoplar y conectar un procesador. También, existen otros usos para la palabra Socket en electrónica menos conocidos, como unos acoples para no tener que soldar chips a una placa de circuito impreso (como una placa base).

En este artículo siempre que aparezca la palabra «Scoket» hablamos del «Socket de red».

Consolas: Local y Remota (SSH)

Vamos a necesitar tener dos consolas abiertas en tu ordenador (sí, en un mismo ordenador las dos), es importante que reconozcas cada una de ellas:

  • La consola con la conexión SSH a nuestro Servidor físico (el Servidor): donde ejecutamos los comandos que darán las órdenes a la distribución Ubuntu de Linux (al sistema operativo) de nuestro servidor físico. Nota sobre las siguientes imágenes: representaré esta consola en azul a la derecha, con el icono de servidor y el logo de Linux. Explicamos como establecer una conexión SSH a nuestro Servidor en el artículo sobre cómo funcionan los Servidores; aunque para las pruebas del Servidor también puedes simular al Servidor en Local (Si estás aprendiendo recomiendo siempre que puedas que pruebes en un Servidor físico real en remoto, pues deja muy claras las diferencias qué es «Local» y qué es en «Remoto», además que aprenderás sobre producción real y a enfrentarte a sus particularidades); para simular un servidor remoto en local, si es un Linux abres otra consola directamente, si es un Windows con el subsistema Linux WSL (igualmente, lo explicamos en el artículo que te indiqué antes).
  • La consola normal de tu ordenador (el Cliente): donde ejecutaremos los comandos que darán las órdenes al sistema operativo de nuestro ordenador. Nota sobre las siguientes imágenes: representaré esta consola en verde a la izquierda con el icono un ordenador portátil y el logo de Windows (en tu ordenador podrás tener el sistema operativo que quieras como Linux, Mac, Windows, etc. Aquí pongo Windows para que si estás aprendiendo puedas diferenciar perfectamente cual es la parte del cliente y la del servidor; también decir, que tanto los comandos de Windows como de Linux que voy a utilizar aquí son los mismos para ambos sistemas).

Nota sobre el tamaño del texto de las imágenes: al posicionar ambas consolas juntas con motivos de comparación, puede que se vea muy pequeño el texto dependiendo de la resolución. Si pinchas sobre la imagen se abrirá en nueva ventana con la resolución completa, para poder ampliar la imagen a discreción.

Nota sobre Windows/Linux/Mac/etc.: He elegido Linux para el Servidor porque suele ser lo más común y Windows en el ordenador para que se vea muy claramente la diferencia ambos lados: Cliente y Servidor. Pero podría tener las dos máquinas Linux o las dos Windows o en cualquier combinación con el sistema operativo que sea.

Con la consola del Cliente iremos donde queramos crear el fichero con el Script de Python que representará al Cliente. Yo quiero crearla en el escritorio de Windows, por lo que utilizaré el comando:

cd C:\Users\usuario\Desktop

Y en la consola SSH del Servidor iremos a donde queremos tener el otro fichero con el Script de Python en la carpeta “/usr/local/bin” (podremos guardarlo en otro sitio, pero aquí se recomienda guardar los Scripts):

cd /usr/local/bin

Tener Python

Para el siguiente ejemplo voy a utilizar Python, pero funciona muy parecido (por no decir exactamente igual) en cualquier lenguaje de programación. Puedes utilizar cualquier servidor para realizar los siguientes pasos, necesitarás tener instalado la última versión de Python (en mi caso será la versión 3.7) tanto en el Servidor físico como en local.

Nota: si vienes de seguir el primer artículo, en este artículo continuaré utilizando el “Servidor Cloud VPS” de clouding.io que ya trae preinstalado Python 3, por lo que no lo tendremos que instalar nosotros (nos saltaríamos este comando).

En caso de que necesitemos instalar Python 3 en un Servidor físico Linux lo haremos con el siguiente comando (puedes consultar la última versión de Python en https://www.python.org/downloads/):

sudo apt install python3.7

Para instalar Python en un servidor Windows descargamos el instalador desde https://www.python.org/downloads/

Después de instalar Python recomiendo cerrar y volver a abrir cada una de las consolas (para que las variables de entorno auto-instaladas funcionen).

Puedes probar que Python está bien instalado en cualquier da las consolas escribiendo:

python --version

Si muestra la versión de Python, es que funciona correctamente.

Scripts de Python

Ahora necesitamos los Scripts de Python colocados en su sitio por lo que te dejo elegir cómo quieres proceder (elige uno y solo uno de los dos siguientes puntos):

  • Crearlos tú mismo (Con lo que aprenderás más), reescribiendo el código que te pongo más abajo mientras lo entiendes (recomiendo este punto para aprender, que será el que te guíe paso a paso a continuación; además, que siempre vas a tener el código completo en Github).
  • Descargar de Github para subir directamente tanto el Script Python del Servidor (“MiServidorCreado.py”) como el Script Python de Cliente (“MiCliente.py”) desde https://github.com/Invarato/JarrobaPython/tree/master/sockets. Tendrás que subir el fichero que se llama “MiServidorCreado.py” a la carpeta “/usr/local/bin” del Servidor (podrás hacerlo con el programa FileZilla arrastrándolo a la carpeta “/usr/local/bin” como se explicó previamente) y mover el fichero llamado “MiCliente.py”.

¿Cómo creo los ficheros yo mismo?

Si lo que quieres es aprender, entonces has elegido sabiamente 😉

Para crearlos en Linux puedes crearlos con el programa Vim directamente en la carpeta donde los quieres crear. Por ejemplo, estando en la carpeta “/usr/local/bin” podemos utilizar el siguiente comando y escribir el código (en el anterior artículo sobre cómo usar Servidores di una breve explicación de Vim que recomiendo que repases rápidamente pinchando aquí):

vim MiServidorCreado.py

Creando directamente el fichero en el Servidor nos evitará tener que subirlo.

De otra manera, podremos crear ambos ficheros en nuestro ordenador y luego subir el del servidor.

En cualquier sistema operativo (Windows, Mac o Linux) con interfaz gráfica (el sistema operativo del Servidor no tiene interfaz gráfica, pero tu ordenador sí) podrás utilizar un IDE como los indicados previamente. Por ejemplo, en Sublime Text pulsando en “File” y luego en “New File”.

Recuerda renombrar los ficheros, yo por ejemplo los he nombrado como: “MiServidorCreado.py” y como “MiCliente.py”.

Tendrás que subir el fichero que se llama “MiServidorCreado.py” a la carpeta “/usr/local/bin” del Servidor (podrás hacerlo con el programa FileZilla arrastrándolo a la carpeta “/usr/local/bin” como se explicó previamente) y mover el fichero llamado “MiCliente.py” a por ejemplo “C:\Users\usuario\Desktop”.

Abrir puerto en el Firewall

Como estamos creando nuestro propio “Servidor Software” necesitamos abrir un puerto en el Firewall de nuestro “Servidor físico”. Debido a que, por defecto, el Firewall bloquea todas las conexiones (bloquea todas las IP y puertos) que no estén definidos por seguridad (lo que evita conexiones peligrosas e indeseadas).

El Firewall es un conjunto de “reglas de Firewall” (también llamadas “normas de Firewall”), y cada una de estas “reglas de Firewall” tiene los siguientes datos:

  • Protocolo de transporte: normalmente TCP o UDP, aunque hay otros muchos. Por ejemplo, para crear un Socket usa TCP, que será el protocolo que necesitemos (¿Y quién dice que Socket usa TCP? Viene en la especificación, además de por necesidad ya que TCP no pierde datos y realiza conexiones uno a uno; más información en https://es.wikipedia.org/wiki/Socket_de_Internet).
  • IP de origen: la IP de origen en la que esperamos recibir datos (por ejemplo, si queremos conectarnos a un “servidor Sofware” que está en la misma máquina (en Local) para hacer pruebas usaremos el localhost que es “127.0.0.1”. Para escuchar las conexiones a una IP determinada pondremos esa IP; por ejemplo, para conectarnos a nuestro “servidor físico” pondríamos su IP.
  • Puerto: El puerto que va a ser escuchado por nuestro servidor Software (y que vendrá acompañado de la IP de origen). Podremos escoger un puerto entre el 49152 y 65535 (desde el 1 al 1023 son los puertos “bien conocidos” que están reservados, y desde el 1024 al 49151 son los puertos “registrados” que están mayoritariamente reservados; por tanto, nos aseguramos de uno que no lo esté en el rango de los puertos privados del 49152 al 65535; más información sobre puertos reservados en https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers)

Para conectar nuestro Socket es necesario que abras un puerto del Firewall, yo voy a abrir el puerto 50000 porque me da la gana ese. También te lo comento aquí porque en los siguientes párrafos y hasta el siguiente título «Sockets de Python» voy a explicar paso a paso cómo se abre el puerto específico en un tipo de Firewall, si ya sabes te lo puedes saltar.

En mi caso, como estoy usando de servidor físico un “Servidor Cloud VPS” de clouding.io, para abrir una nueva conexión tendremos que ir al panel de control de nuestro servicio de Hosting (en este ejemplo y para continuar con el artículo anterior utilizaremos https://portal.clouding.io).

Depende del servicio de Hosting (o si tenemos montada nuestro propio servidor Software) podrá variar ligeramente los siguientes pasos, pero seguramente tengan nombres parecidos.

En mi caso tendré que ir a la pestaña de “Mis Firewalls”. Aquí podremos hacer dos cosas, o bien crear un nuevo Firewall y asignarlo a nuestro “Servidor físico”, o modificar el que ya teníamos (recordar que cuando cree mi “Servidor Cloud VPS” le asigné el Firewall por defecto llamado “default”; podremos recordarlo yendo un momento a la pestaña “Mis Servidores” y consultar que Firewall pusimos). Por simplicidad voy a modificar el Firewall “default” (para aprender sigue estos pasos; en un futuro te recomiendo que crees un nuevo Firewall para tener el Firewall por defecto como base para otros “Servidores físicos” futuros).

Al modificar el Firewall “default”, que ya nos venía creado, veremos que ya tendrá unas cuantas “reglas de Firewall” por defecto.

Como queremos abrir un puerto vamos a añadir una nueva “regla del Firewall”; en mi caso es pulsando el “+” que está en la barra azul con el texto “Normas del Firewall”.

En mi caso se abrirá un cuadro nuevo con un desplegable. Elegiremos el protocolo TCP que es el que vamos a utilizar con el Socket y pulsaremos el “+” que tiene al lado.

Ahora configuraremos la ficha de nuestra “norma de Firewall”. Hemos dicho que queremos que nuestro servidor Software escuche concretamente puerto 50000; entonces como es solo un puerto lo ponemos en “Desde puerto” y en “Hasta puerto”. Como realizaremos la conexión a “servidor físico” entonces la “IP de Origen” será la de nuestro “servidor físico” (en mi caso, creé un “Servidor Cloud VPS” y se me asignó la IP “85.208.20.119”), para mí me quedará “85.208.20.119/0” (hay que añadirle una máscara de subred, en este caso “/0”, ya que es una IP privada; esto lo explicamos en el artículo previo sobre cómo funcionan los Servidores). Es importante añadir una “Descripción” que utilizaremos como nombre de nuestra regla para acordarnos para qué sirve (por si en un futuro la tenemos que eliminar porque deje de usarse, por seguridad); yo la llamaré “Puerto Abierto para MiServidorCreado”. Cuando terminemos pulsaremos el botón de “Enviar”.

Si todo ha ido bien podremos ver nuestra nueva “norma del Firewall” creada al final del listado de “Normas del Firewall”:

Después de aprender a abrir nuestros puertos en el Firewall nos servirá para crear nuestro propio servidor con código.

Sockets Python

Vamos a utilizar Python, por lo que recomiendo tener un poco de práctica previa, tampoco hace falta mucha, pero sí saber leer lo básico del código (Puedes consultar nuestros artículos sobre Python pinchando aquí).

El fichero del cliente que llamaré “MiCliente.py” tendrá el siguiente código, del que modificaremos la variable HOST (por la IP del servidor físico que tendrá ejecutando al Servidor Software que explicaré luego; en mi caso es la IP que nos da el panel de control portal.clouding.io, como te mostré antes) y la variable PORT (utilizaré el mismo puerto privado que abrí antes, si tu abriste otro tendrá que utilizar ese otro).

# El programa cliente que se comunica con el Servidor Software que está escuchando en el Servidor Físico
import socket

# El host remoto. Sería "localhost" si el cliente está en la misma máquina que el servidor
HOST = "85.208.20.119"

# El puerto en el que está escuchando nuestro servidor
PORT = 50000


if __name__ == "__main__":
	print('[Cliente 1] Iniciando socket con el host "{}" al puerto {}'.format(HOST, PORT))
	with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as mi_socket:
		tupla_para_el_enlace = (HOST, PORT)

		print('[Cliente 2] Conectando a: {}'.format(tupla_para_el_enlace))
		mi_socket.connect(tupla_para_el_enlace)

		mantener_el_socket_abierto = True
		while mantener_el_socket_abierto:
			dato_para_enviar_str = input("[Cliente 3] Escribe el dato a enviar al servidor: ")

			if dato_para_enviar_str == "cerrar":
				print("[Cliente 6] Cerrando socket desde cliente")
				break

			mi_socket.sendall(bytes(dato_para_enviar_str, encoding='utf8'))

			print('[Cliente 4] Datos enviados, esperando respuesta del servidor ...')
			dato_recibido_en_bytes = s.recv(1024)

			dato_recibido_en_str = dato_recibido_en_bytes.decode("utf-8")
			print("[Cliente 5] Respuesta recibida desde el servidor: {}".format(dato_recibido_en_str))

		print("[Cliente 7] Socket cerrado desde cliente")

Este Script de Python (que bien podría ser una aplicación en nuestro ordenador) utiliza la biblioteca “socket”, para realizar la conexión y se mantendrá abierta la conexión (el bucle “while”) para poder escribir los datos (con la función “input” que lee de teclado) que queramos mandar en tiempo real (como si fuera un chat) con “sendall”, el servidor responderá y recibiremos los datos con “recv” (“sendall” y “recv” funcionan con bytes, de ahí que convierta los strings a bytes con el método “bytes()” y viceversa con el método “decode()”). La conexión del cliente permanecerá abierta (dentro del bucle “while”) hasta que escribamos la palabra que he reservado como “cerrar” (que cerrará el socket y notificará al servidor software). Te habrás fijado que utilizamos AF_INET indica que utilizaremos la familia de direcciones IPv4, que va a necesitar el host y el puerto.

Familias de direcciones para sockets

Hay varias familias de direcciones para sockets, las más comunes (están en Python):

  • AF_INET: son las direcciones IPv4. Se representa con el host y el puerto.
  • AF_INET6: son las direcciones IPv6. Se representa con el host y el puerto (así como la etiqueta de flujo “Flow Label” con la documentación en https://tools.ietf.org/html/rfc6437, y el alcance de la id “scope id” que indica el interfaz qué usar)
  • AF_UNIX o AF_LOCAL: son las direcciones locales de Unix. Se representa como un string que apunta a un fichero del sistema.

El fichero del Servidor que he llamado “MiServidorCreado.py” en los que también hay que modificar las variables PORT y HOST (por lo mismo que antes).

# Nuestro Servidor Software sobre el Servidor Físico
import socketserver

# Dirección IP
HOST = "85.208.20.119"

# El puerto privado que queramos escuchar, uno de los comprendidos entre 49152 y 65535  1-65535
PORT = 50000


class MiControladorTCP(socketserver.BaseRequestHandler):
    """
    La clase que controlará las peticiones para nuestro servidor.

    It is instantiated once per connection to the server, and must
    override the handle() method to implement communication to the
    client.
    """

    def handle(self):
        """
        Método sobrescrito para controlar la comunicación que ocurra ne nuestro servidor.
        Aquí recibiremos los mensajes del cliente y le responderemos
        """
        print('[Servidor 3] Cliente conectado desde: {}'.format(self.client_address[0]))

        socket_abierto = True
        while socket_abierto:
            print('[Servidor 4] Esperando por petición del cliente...')
            dato_recibido_en_bytes = self.request.recv(1024).strip()
            if dato_recibido_en_bytes:
                dato_recibido_en_str = dato_recibido_en_bytes.decode("utf-8") 
                print('[Servidor 5] Recibido desde el cliente: {}'.format(dato_recibido_en_str))

                respuesta_en_str = "## RESPUESTA DEL SERVIDOR: {} ##".format(dato_recibido_en_str)
                
                self.request.sendall(bytes(respuesta_en_str, encoding='utf8'))
                print('[Servidor 6] Se ha respondido al cliente con el mensaje: {}'.format(respuesta_en_str))
            else:
                print('[Servidor 7] El cliente ha cerrado el Socket desde su lado, cerrando socket desde el Servidor...')
                socket_abierto = False


if __name__ == "__main__":
    tupla_para_el_enlace = (HOST, PORT)

    try:
        print('[Servidor 1] Enlazando Socket en: {}'.format(tupla_para_el_enlace))
        with socketserver.TCPServer(tupla_para_el_enlace, MiControladorTCP) as servidor:
            print('[Servidor 2] Iniciando buble del servidor. Para interrumpir pulsar a la vez las teclas: [Ctrl]+[C]')
            servidor.serve_forever()
    except KeyboardInterrupt:
        print('[Servidor 8] Interrupción por teclado')
    finally:        
        if servidor is not None:
            servidor.shutdown()
        print('[Servidor 9] Servidor Cerrado')

Este segundo Script de Python (con el que crearemos nuestro propio Servidor) utiliza la biblioteca “socketserver” (también podríamos haber utilizado “socket”, pero esta biblioteca nos facilita y mucho la tarea para crear nuestro propio servidor Software; además, “socketserver” funciona de forma parecida a “socket”). Crearemos una clase controladora que he llamado “MiControladorTCP” que hereda de “socketserver.BaseRequestHandler”, para sobrescribir el método “handle()” en cuyo parámetro “self” nos pasarán los datos para usar recv y sendall (estos funcionan igual que en el código anterior de “MiCliente.py”; salvo que con estos recibiremos y enviaremos datos a los clientes que se conecten). Al igual que antes, mantendremos el socket abierto con el bucle “while”, que en este caso se cerrará cuando ordene el cliente (cuando en “MiCliente.py” escribamos la palabra “cerrar”); es decir, cuando en el servidor (“MiServidorCreado.py”) se reciba en recv un texto vacío. Esta clase controladora “MiControladorTCP” se la pasaremos a “socketserver.TCPServer” para con el método “serve_forever()” indicar que nuestro servidor esté siempre activo (esto realmente es otro bucle “while” por debajo, que vuelve a crear un socket en caso de que un cliente lo demande; en resumen, aquí tenemos dos bucles “while” uno que estará esperando a que un cliente se conecte y otro que mantendrá el socket activo para que se puedan enviar varios datos dentro de un mismo socket); el servidor terminará cuando pulsemos la combinación de teclas “Ctrl” y “C”, que será cuando se llame al método “shutdown()” para finalizar correctamente al Servidor.

Teniendo los ficheros cada uno en su sitio (para mi ejemplo he puesto al fichero “MiServidorCreado.py” en “/usr/local/bin” en el servidor físico, y el fichero “MiCliente.py” estará en mí ordenador en “C:\Users\usuario\Desktop”)

Con esto ya podremos arrancar nuestro Servidor Software que hemos creado, para que quede escuchando peticiones con el comando de Python (me gusta ejecutar Python con el parámetro -u para que la salida, lo que imprimimos con el método “print()”, no se almacenen en buffer y me los muestre inmediatamente; de lo contrario, habrá que esperar a que el programa termine para que muestre los datos por pantalla. No suele ser necesario usarlo). Lo iniciaremos desde la consola SSH que conecta con nuestro servidor físico:

python3 -u MiServidorCreado.py

Veremos los textos que he puesto de ayuda. El primero nos indicará a dónde se ha enlazado el socket (en mi caso a la IP 85.208.20.119 y al puerto 50000), y el segundo nos dirá que se ha iniciado el bucle del servidor (he añadido el texto de ayuda, que indica que para detener nuestro servidor software tendremos que pulsar la combinación de teclas “Ctrl” y “C” a la vez).

Ver que nuestro servidor se está ejecutando en ese puerto

Si abre otra tercera consola y la conectas por SSH a nuestro servidor físico (como vimos en el artículo previo https://jarroba.com/como-funcionan-los-servidores-y-servicios-de-hosting/) podremos ejecutar en paralelo el comando “lsof” para ver que está sucediendo.

El comando “lsof” muestra la información de fichero que están abiertos por un proceso. Usando el parámetro “-i” seguido de dos puntos y el número de puerto de nuestro servidor software (en mi caso el puerto es 50000), mostrará los ficheros que están abiertos en base a un puerto específico:

lsof -i :50000

Con lo que podrás ver:

Nos indica que nuestro servidor software que hemos creado y ejecutado, está funcionando correctamente.

Teniendo el servidor software corriendo (arrancado), podremos iniciar un socket desde el cliente. Para ello en la consola del cliente (la que NO está conectada al SSH) escribiremos:

python3 -u MiCliente.py

Nota sobre el nombre de las variables de entorno “python” o “python3”: actualmente están conviviendo dos versiones de Python, por lo que se suele usar la variable “python” para ejecutar con Python 2, y la variable “python3” para ejecutar con Python 3 (ya que un ordenador puede tener instaladas ambas versiones de Python a la vez). Como Python 2 está dejando de ser utilizado por quedar obsoleto en favor de Python 3, si tu ordenador no necesita diferenciar ambas versiones puede que de manera automáticamente (o hayas cambiado a mano la variable de entorno del sistema) se haya cambiado el ejecutable de Python 3 para que apunte a la variable “python” (es lo que pasa en mi ordenador, que solo tengo Python 3 por lo que no necesito diferenciarlas y solo tengo la variable “python” que apunta a Python 3; en un servidor físico es más complicado, pues las empresas que hacen uso intensivo de los servidores físicos tienen mucho código escrito en Python 2, por lo que tienen que convivir ambas versiones Python todavía). Por esta razón, verás en los ejemplos que he escrito en la consola de mi ordenador:

python -u MiCliente.py

Podrás ver que nuestro cliente (el programa en nuestro ordenador), se habrá conectado inmediatamente al servidor y nos mostrará la IP del cliente que se ha conectado (NO hay que confundir con la IP que hemos escrito de nuestro Servidor Físico). Se habrá quedado el Servidor esperando por la petición del cliente (quiere que le enviemos datos), y el cliente se habrá quedado esperando a que escribamos algo (como un chat).

En la consola del cliente escribimos lo que queramos y pulsamos la tecla “Enter”. Yo he escrito: “Mi Dato 1”.

Los datos se envían desde el cliente al servidor. Puedes ver que el servidor recibe correctamente los datos y que devuelve un acuse de recibo (para que el cliente sepa que el servidor ha recibido los datos).

Aplicaciones de mensajería

Hasta aquí puedes pensar en la aplicación del Whatsapp (o Telegram, Hangout, etc.) y las comprobaciones de lectura (que coloquialmente los usuarios de estas aplicaciones llaman “checks” o “ticks”; es decir, el símbolo: ✔).

Que tiene tres estados (de los cuales nos interesan los dos primeros, ya que el tercero es una combinación de ambos primeros):

  • Un “check”: Cuando envías un mensaje a tu amigo, significa que el mensaje ha salido del smartphone (el dato que hemos enviado del cliente al servidor). Todavía no conoce nuestro smarthphone en este punto si ha llegado al servidor o no.
  • Dos “checks”: Cuando el servidor físico ha recibido el mensaje responde a mí smartphone y con este acuse de recibo se nos mostrarán dos «checks» en pantalla (la respuesta del servidor que ha recibido el cliente).
  • “Checks” azules (lo pongo como curiosidad): Cuando el amigo ha leído el mensaje que le envié (esto ya aquí no lo hacemos en el código, pero imagínate otro socket abierto con el smartphone del amigo; entonces el amigo lee el mensaje y envía al servidor una confirmación, entonces el servidor recibe la comprobación de lectura del mensaje, y por nuestro socket abierto el servidor nos envía otra segunda respuesta que nos indica que nuestro amigo ha leído el mensaje)

Como tenemos en un bucle (“while”) ambos extremos del socket (el extremo del cliente y el extremo del servidor) para que se mantengan abiertos los extremos, por lo que podremos enviar los datos que queramos. Puedes probar a enviar varios datos, yo enviaré “Mi Dato 2”.

Ahora queremos finalizar el socket desde el extremo del cliente, como hemos programado, al escribir la palabra “cerrar” se cerrará el socket en el cliente (aquí puedes pensar cuando un usuario cierra una aplicación de mensajería, como al cerrar WhatsApp, que por debajo esta aplicación cierra el socket con el servidor), que enviará un mensaje vacío al servidor para indicarle que el cliente ha terminado con su socket. Entonces nuestro servidor software tomará la decisión de cerrar el socket desde su lado y se quedará esperando por otra conexión con otro cliente.

Si volvemos a ejecutar el Script de Python “MiCliente.py” desde el lado del cliente (sería como volver a abrir la aplicación de WhatsApp, por ejemplo).

python -u MiCliente.py

Veremos como la conexión se vuelve a establecer, ya que el bucle del servidor (que iniciamos con el método “serve_forever()”) se mantiene a la espera de otro cliente. Desde el cliente podremos volver a enviar datos y terminar la conexión con la palabra “cerrar” como hicimos antes.

Aunque un servidor Software se supone que tiene que funcionar siempre (sino no funcionaría WhatsApp 24 horas al día), nosotros lo podremos querer terminar. Teniendo seleccionada la consola SSH que conecta con nuestro servidor físico, pulsaremos a la vez la combinación de teclas “Ctrl” y “C” para terminar con la ejecución del servidor software que hemos creado.

Terminará con el bucle del servidor.

Hasta aquí ya sabes cómo crear un Socket tanto en Cliente como en Servidor y probarlo desde ambas partes. Ahora solo te queda utilizar este conocimiento para todo cuanto te propongas 🙂

Otro tipo de Servidores con Python

En Python existen varios servidores Software ya creados que podremos usar:

Socket vs WebSocket

Es importante la diferencia, pues NO podremos simplemente escuchar un WebSocket con un Socket.

WebSocket se escribe en JavaScript y sirve para comunicar en directamente (full-duplex) una página web contra un servidor (como los chats que aparece integrados en la página web del correo electrónico o de las redes sociales para hablar en tiempo real con los amigos). Si quieres aprender a escribir WebSocket tienes más información en https://developer.mozilla.org/es/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications

Los Sockets «normales» (TCP) utilizan la «capa de Transporte» de la pila de protocolos OSI (más cerca del binario transmitido en pulsos eléctricos) mientras que los WebSockets utilizan la «capa de Aplicación» (más lejos de los pulsos eléctricos, pero más cerca del usuario). Si tienes curiosidad sobre el Modelo OSI tienes más información en https://es.wikipedia.org/wiki/Modelo_OSI

Para que te hagas una idea, cuando abres una página web en un navegador, el navegador establece un único Socket TCP contra el servidor físico al que se conecta (por ejemplo, al servidor Web de Apache). Y por este Socket TCP va a ir todo, por ahí va a ir todo el protocolo HTTP (que también trabaja en la «capa de Aplicación» del modelo OSI) y el protocolo WebSocket.

Por otro lado, indicar que HTTP (esquemas de la URL: http o https) y WebSocket (esquemas de la URL: ws o wss) son protocolos diferentes.

A HTTP se puede invocar desde el navegador con una URL como https://jarroba.com y a WebSocket wss://jarroba.com/servidorsocket

Ese Socket TCP que utiliza el navegador se va a conectar a los puertos (que posiblemente te suenen; son los que escucha un Servidor Software Web, como el de Apache):

  • 80 para tráfico inseguro: http y ws (WebSocket)
  • 443 para tráfico seguro: https y wss (WebSocket Secure)

WebSocket utiliza la idea de Socket a la hora de programarlo, pero no es un Socket, simplemente para que sea igual de fácil programar uno que otro (para que aprendas uno y ya sepas utilizar el otro de inmediato). Es decir, WebSocket se simula que es un Socket «normal» pero va por encima de uno ya creado (el Socket TCP) del que no tenemos el control directo a la hora de programar páginas webs.

Por eso los WebSockets NO son compatibles directamente con los Sockets TCP (no puedes crear un WebSocket en una web con JavaScript y luego en el Servidor con Python crear un Socket normal, por ejemplo).

Además, WebSocket requiere de una negociación para el establecimiento de la comunicación (handshake) con el servidor (al igual que el protocolo HTTP) a través del envío de una serie de cabeceras transmitidas a través del socket TCP y un mensaje en un formato concreto (frame).

¿Podríamos tratar un WebSocket con un Socket? Sí, si tratamos tanto la negociación (handshake) como el mensaje (frame) que requiere WebSocket según la especificación de WebSocket https://tools.ietf.org/html/rfc6455

Sockets con Arduino

Aquí te pongo donde puede aprender más sobre crear Sockets con Arduino, siempre que tu placa tenga Wi-Fi para poder comunicarse: https://www.arduino.cc/en/Reference/WiFi, y aquí tienes un ejemplo de código oficial para utilizar la API de Wi-Fi: https://www.arduino.cc/en/Reference/WiFiStatus

Un ejemplo completo para crear un Socket con Arduino como cliente y Python como Servidor lo puedes encontrar en este otro blog: https://techtutorialsx.com/2018/05/17/esp32-arduino-sending-data-with-socket-client/

Bibliografía

Atribuciones

Imagen de WhatsApp obtenida de la Wikipedia https://es.wikipedia.org/wiki/WhatsApp (De Helar Lukats, CC BY-SA 4.0, https://commons.wikimedia.org/w/index.php?curid=48993365) y modificada por www.Jarroba.com

Comparte esta entrada en:
Safe Creative #1401310112503
Nuestro propio servidor Software (Socket con Python) por "www.jarroba.com" esta bajo una licencia Creative Commons
Reconocimiento-NoComercial-CompartirIgual 3.0 Unported License.
Creado a partir de la obra en www.jarroba.com

2 thoughts on “Nuestro propio servidor Software (Socket con Python)”

  1. Hola excelente publicación.

    Este servidor maneja un solo cliente, pero si quisiera conectar multiples clientes que cambiaría?

    1. Podrías controlar los clientes con su IP, por ejemplo, en el lado del servidor puedes usar self.client_address[0] para saber qué cliente se está conectando el servidor y controlar las diferentes respuestas.

Deja una respuesta

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

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Uso de cookies

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies

ACEPTAR
Aviso de cookies