3 usuarios conectados
Redes y comunicación. Clientes y servidores
Redes y comunicación. Clientes y servidores
Desarrollo de Servidores
Un servidor es una aplicación que espera
conexiones de clientes y proporciona un servicio en respuesta a sus solicitudes.
Aquí hay aspectos clave a considerar al desarrollar servidores:
1. Selección del Protocolo (TCP vs. UDP):
- TCP: Elige TCP para
aplicaciones que requieren confiabilidad y entrega ordenada de datos, como
servidores web (HTTP/HTTPS), servidores de correo electrónico (SMTP, IMAP,
POP3), transferencia de archivos (FTP, SFTP), y sesiones interactivas (SSH).
- UDP: Elige UDP para
aplicaciones donde la velocidad y la baja latencia son más importantes que
la confiabilidad, como streaming de video/audio, juegos en tiempo real, y
protocolos de descubrimiento de red.
2. Enlace a una Dirección y Puerto (bind()
):
- El servidor debe asociar su socket a una
dirección IP específica de la máquina donde se ejecuta y a un número de
puerto en el que escuchará las conexiones entrantes.
- Usar
'0.0.0.0'
como dirección
IP en bind()
permite que el servidor escuche en todas las
interfaces de red disponibles en la máquina.
- Los puertos menores a 1024 generalmente
requieren privilegios de superusuario (root). Es común usar puertos mayores
para aplicaciones personalizadas.
3. Escucha de Conexiones (listen()
- Solo TCP):
- Para servidores TCP, después de
bind()
,
se llama a listen(backlog)
para poner el socket en modo de
escucha, listo para aceptar conexiones entrantes. backlog
especifica el número máximo de conexiones pendientes que el sistema
operativo puede mantener en cola.
4. Aceptación de Conexiones (accept()
- Solo TCP):
- Cuando un cliente intenta conectarse, el
servidor llama a
accept()
. Esta llamada bloquea la ejecución
hasta que se establece una conexión.
accept()
devuelve una
nueva instancia de socket que representa la conexión específica con
el cliente, así como la dirección (IP y puerto) del cliente. La comunicación
con este cliente se realizará a través de este nuevo socket. El socket
original del servidor sigue escuchando nuevas conexiones.
5. Comunicación con el Cliente (recv()
,
sendall()
):
- Una vez aceptada la conexión (para TCP) o
al recibir datos (para UDP), el servidor utiliza
recv(bufsize)
para recibir datos del cliente (hasta bufsize
bytes) y
sendall(data)
(para TCP, asegura que todos los bytes se envíen) o
sendto(data, (client_ip, client_port))
(para UDP, especifica el
destino) para enviar datos de vuelta al cliente.
- Recuerda codificar y decodificar los datos
(generalmente con UTF-8) al enviar y recibir texto.
6. Manejo de Múltiples Clientes:
- Un servidor real a menudo necesita manejar
múltiples clientes simultáneamente. Esto se puede lograr mediante:
- Hilos (
threading
):
Crear un nuevo hilo para manejar cada conexión de cliente. Es
relativamente sencillo de implementar pero puede tener limitaciones con
la concurrencia debido al Global Interpreter Lock (GIL) en CPython para
tareas intensivas en CPU.
- Procesos (
multiprocessing
):
Crear un nuevo proceso para cada conexión de cliente. Evita las
limitaciones del GIL pero tiene una mayor sobrecarga en la creación y
gestión de procesos.
- Programación Asíncrona (
asyncio
):
Utilizar un solo hilo de ejecución con un bucle de eventos para manejar
múltiples conexiones de manera eficiente sin la sobrecarga de hilos o
procesos. Es ideal para operaciones de E/S concurrentes.
7. Cierre de Conexiones (close()
):
- Es importante cerrar las conexiones con los
clientes utilizando el método
close()
del socket de conexión
cuando la comunicación ha finalizado para liberar recursos. El socket de
escucha del servidor puede permanecer abierto para aceptar nuevas
conexiones.
Desarrollo de Clientes
Un cliente es una aplicación que inicia una
conexión a un servidor para solicitar un servicio. Los aspectos clave incluyen:
1. Selección del Protocolo (TCP vs. UDP):
- El cliente debe usar el mismo protocolo que
el servidor al que intenta conectarse.
2. Creación del Socket:
- Similar al servidor, el cliente crea un
socket utilizando
socket.socket(socket.AF_INET, socket.SOCK_STREAM)
para TCP o socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
para UDP.
3. Conexión al Servidor (connect()
- Solo TCP):
- Para clientes TCP, se utiliza
connect((server_ip, server_port))
para establecer una conexión con el
servidor en la dirección IP y el puerto especificados.
4. Envío de Solicitudes (sendall()
,
sendto()
):
- El cliente utiliza
sendall(data)
(para TCP) o sendto(data, (server_ip, server_port))
(para UDP)
para enviar su solicitud al servidor. Los datos deben estar en formato de
bytes (codificados si son texto).
5. Recepción de Respuestas (recv()
,
recvfrom()
):
- El cliente utiliza
recv(bufsize)
(para TCP) para recibir la respuesta del servidor (hasta bufsize
bytes) o recvfrom(bufsize)
(para UDP) que devuelve los datos y
la dirección del servidor que los envió.
- La respuesta recibida estará en formato de
bytes y deberá ser decodificada si se espera texto.
6. Cierre de la Conexión (close()
):
- Una vez que el cliente ha recibido la
respuesta y ha terminado la comunicación, debe cerrar la conexión utilizando
close()
.
Ejemplo de Servidor TCP Multi-hilo:
En este ejemplo, cada vez que un cliente se
conecta, se crea un nuevo hilo para manejar la comunicación con ese cliente,
permitiendo que el servidor acepte nuevas conexiones simultáneamente.
El desarrollo de clientes y servidores implica
comprender los protocolos de red, la funcionalidad del módulo socket
y las técnicas para manejar la concurrencia en el lado del servidor.