7.1 ¿Qué son los sockets?
Vamos a profundizar aún más. Primero aprendimos a trabajar con request
, luego con http.client
, después con proxies. ¿Y ahora qué? Ahora vamos a mirar bajo el capó de todas estas bibliotecas...
Un socket (literalmente, enchufe) es un punto en la red a través del cual los datos se envían y reciben. Un socket se puede ver como un punto final de un canal de comunicación bidireccional entre dos programas, que funcionan en una misma o en diferentes máquinas.
Los sockets soportan diferentes protocolos de red, pero los más utilizados son dos:
-
TCP
(Transmission Control Protocol): Un protocolo confiable que asegura el establecimiento de conexiones, la verificación de integridad de datos y su secuencia correcta. -
UDP
(User Datagram Protocol): Un protocolo no orientado a conexión que no garantiza la entrega de datos, pero es más rápido y eficiente para ciertos tipos de aplicaciones.
Para identificar un socket se utilizan una dirección IP (que identifica un dispositivo en la red) y un puerto (que identifica una aplicación o servicio específico en el dispositivo).
¡Importante!
La dirección IP y el puerto identifican de manera única un programa en la red. Es como la dirección de una casa y el número de apartamento. La dirección de la casa (dirección IP) es la dirección de tu computadora en la red, y el puerto es el número de apartamento que el programa usa para recibir y enviar mensajes.
Puedes aprender más sobre lo que son la dirección IP y el puerto en las lecciones dedicadas a la estructura de la red.
Pasos principales del funcionamiento de un programa con sockets:
- Creación de un socket: El programa crea un socket, especificando el tipo de protocolo (TCP o UDP).
- Vinculación a una dirección: El socket se vincula a una dirección IP y número de puerto, para estar disponible para conexiones o para enviar/recibir datos.
- Escuchar y establecer conexión (para TCP):
- Escuchar: El socket del lado del servidor se pone en modo de escucha, esperando conexiones entrantes.
- Establecer conexión: El cliente inicia una conexión con el servidor. El servidor acepta la conexión, creando un nuevo socket para interactuar con el cliente.
- Intercambio de datos: Los datos se transfieren entre el cliente y el servidor. En el caso de TCP, los datos se transfieren de manera confiable.
- Cerrar conexión: Después de completar el intercambio de datos, la conexión se cierra.
Ventajas de usar sockets:
- Flexibilidad: Los sockets permiten que las aplicaciones intercambien datos independientemente de su ubicación y plataforma.
- Rendimiento: Los sockets proporcionan una forma rápida y eficiente de transferir datos, especialmente en el caso de usar UDP.
- Confiabilidad (en el caso de TCP): El protocolo TCP garantiza una entrega confiable de datos con verificación de integridad y recuperación de paquetes perdidos.
7.2 Creación de un socket-servidor
El trabajo con sockets en Python se realiza mediante el módulo socket
incorporado, que proporciona una interfaz para la programación de redes de bajo nivel.
Con los sockets se puede crear un socket-servidor
— una aplicación/objeto que recibirá solicitudes de los clientes y les responderá. Así como un socket-cliente
— una aplicación/objeto que enviará solicitudes al socket-servidor
y recibirá respuestas de él.
Para crear un socket-servidor
, se deben realizar tres acciones:
- Crear un objeto socket-servidor.
- Vincularlo (
bind
) a alguna IP y puerto. - Activar el modo de escucha (
listen
) de mensajes entrantes.
Este código se verá más o menos así:
import socket
# Creación del socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Vinculación del socket con la dirección y puerto
server_socket.bind(('localhost', 12345))
# Escuchar conexiones entrantes
server_socket.listen(5)
print("El servidor está esperando conexiones...")
Aquí socket.AF_INET
indica que estamos usando IPv4 para el protocolo de red, y socket.SOCK_STREAM
significa que estamos usando TCP. Estos parámetros son los más usados para crear aplicaciones de red.
Después de que llega un mensaje entrante, se deben hacer otras cuatro acciones:
- Establecer
(accept)
una conexión con el cliente. - Recibir
(receive)
la solicitud (datos) del cliente. - Enviar
(send)
una respuesta al cliente — también algunos datos. - Cerrar
(close)
la conexión.
El código se verá más o menos así:
# Aceptar una nueva conexión
client_socket, client_address = server_socket.accept()
print(f"Conexión establecida con {client_address}")
# Recibir datos del cliente
data = client_socket.recv(1024)
print(f"Recibido: {data.decode('utf-8')}")
# Enviar datos al cliente
client_socket.sendall(b'¡Hola, cliente!')
# Cerrar la conexión con el cliente
client_socket.close()
Se usa el método sendall()
en lugar de send()
porque garantiza que todos los datos se enviarán. El método send()
puede enviar solo parte de los datos, si el búfer se llena.
El número 1024 en recv(1024)
indica la cantidad máxima de bytes que se pueden recibir de una vez. Esto ayuda a controlar el volumen de datos procesados en una operación.
El código del último ejemplo generalmente se ejecuta en un bucle infinito - el servidor procesa una solicitud, luego espera una nueva, luego la procesa, y así continuamente.
Si quieres ejecutarlo por ti mismo o simplemente ver un ejemplo completo, lo pongo aquí:
import socket
# Creación del socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Vinculación del socket con la dirección y puerto
server_socket.bind(('localhost', 12345))
# Escuchar conexiones entrantes
server_socket.listen(5)
print("El servidor está esperando conexiones...")
while True:
# Aceptar una nueva conexión
client_socket, client_address = server_socket.accept()
print(f"Conexión establecida con {client_address}")
# Recibir datos del cliente
data = client_socket.recv(1024)
print(f"Recibido: {data.decode('utf-8')}")
# Enviar datos al cliente
client_socket.sendall(b'¡Hola, cliente!')
# Cerrar la conexión con el cliente
client_socket.close()
7.3 Creación de un socket-cliente
Ya creamos un socket-servidor, ahora escribamos nuestro socket-cliente, que se conectará al servidor y recibirá datos de él en respuesta.
Para esto, se deben realizar cinco acciones:
- Crear un objeto
socket-cliente
. - Establecer una conexión
(connect)
con la dirección IP y el puerto de nuestrosocket-servidor
. - Enviar
(send)
un mensaje al servidor. - Recibir
(receive)
datos del servidor. - Cerrar
(close)
la conexión.
En realidad esto es más fácil de lo que parece. Así es como se verá este código:
import socket
# Creación del socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Establecer conexión con el servidor
client_socket.connect(('localhost', 12345))
# Enviar datos al servidor
client_socket.sendall(b'¡Hola, servidor!')
# Recibir datos del servidor
data = client_socket.recv(1024)
print(f"Recibido del servidor: {data.decode('utf-8')}")
# Cerrar el socket
client_socket.close()
Si en el otro lado no hay un socket-servidor
en ejecución o la conexión se interrumpe, surgirá una excepción del tipo socket.error
. Así que no olvides manejar las excepciones.
Con esto concluimos nuestro trabajo con sockets por hoy.
En cualquier trabajo con redes, te encontrarás una y otra vez con hosts, puertos, direcciones IP, establecimiento de conexiones, escuchar solicitudes y cosas por el estilo. Así que entender cómo funciona esto en profundidad realmente te facilitará la vida y te ayudará a unir conocimientos dispersos en una imagen coherente.
GO TO FULL VERSION