Herramientas de usuario

Herramientas del sitio


informatica:programacion:cursos:python_avanzado_proyectos_seguridad:creando_cliente_servidor_tcp_sockets

Creando un cliente y un servidor TCP con sockets

Módulo perteneciente al curso Python avanzado para proyectos de seguridad

En Python es posible crear un socket que actúe como cliente o como servidor. Los sockets cliente, se encargan de realizar una conexión contra un host, puerto y protocolo determinado. Los sockets servidor, se encargan de recibir conexiones por parte de los clientes en un puerto y protocolo determinado.

La idea detrás de la creación de esta aplicación es que un socket cliente puede establecer una conexión con un determinado host, puerto y protocolo. El servidor de socket es responsable de recibir las conexiones de los clientes en un puerto y protocolo específicos. Veremos cómo crear un par de programas cliente y servidor a modo de ejemplo. Lo primero que tenemos que hacer es crear un objeto socket para el servidor.

Para crear un socket, se utiliza el constructor socket.socket(), que puede tomar la familia, el tipo y el protocolo como parámetros opcionales. Por defecto, se utilizan la familia AF_INET y el tipo SOCK_STREAM.

En esta sección, veremos cómo crear un par de scripts de cliente y servidor como ejemplo. Lo primero que tenemos que hacer es crear un objeto de socket para el servidor:

servidor = socket.socket (socket.AF_INET, socket.SOCK_STREAM)

Tenemos ahora que indicar en qué puerto se va a mantener a la escucha nuestro servidor utilizando el método bind. Para sockets IP, como es nuestro caso, el argumento de bind es una tupla que contiene el host y el puerto. El host se puede dejar vacío, indicando al método que puede utilizar cualquier nombre que esté disponible.

El método bind(IP, PORT) le permite asociar un host y un puerto con un socket específico, teniendo en cuenta que los puertos 1-1024 están reservados para los protocolos estándar:

socket_s.bind(("localhost", 9999))

Métodos para aceptar conexiones

Por último, utilizamos listen para hacer que el socket acepte conexiones entrantes y accept para comenzar a escuchar. El método listen requiere de un parámetro que indica el número de conexiones máximas que queremos aceptar; evidentemente, este valor debe ser al menos 1.

El método accept se mantiene a la espera de conexiones entrantes, bloqueando la ejecución hasta que llega un mensaje. Cuando llega un mensaje, accept desbloquea la ejecución, devolviendo un objeto socket que representa la conexión del cliente y una tupla que contiene el host y puerto de dicha conexión.

socket_s.listen(10)
 
socket_cliente, (host_c, puerto_c) = socket_s.accept()

Podemos obtener más información sobre estos métodos con el comando de help(socket).

Enviar y recibir datos del socket

Una vez que tenemos este objeto socket podemos comunicarnos con el cliente a través suyo, mediante los métodos recv() y send() (o recvfrom y sendfrom en UDP) que permiten recibir o enviar mensajes respectivamente. El método send() toma como parámetros los datos a enviar, mientras que el método recv() toma como parámetro el número máximo de bytes a aceptar.

recibido = socket_cliente.recv(1024)
 
print("Recibido: ", recibido)
 
socket_cliente.send(recibido)

Una vez que hemos terminado de trabajar con el socket, lo cerramos con el método close. Crear un cliente es aún más sencillo. Solo tenemos que crear el objeto socket, utilizar el método connect para conectarnos al servidor y utilizar los métodos send y recv que vimos anteriormente. El argumento de connect es una tupla con host y puerto, exactamente igual que el método bind.

socket_cliente = socket.socket()
 
socket_cliente.connect(("localhost", 9999))
 
socket_cliente.send("hola")

Para crear un cliente, tenemos que crear el objeto socket, usar el método de conexión para conectarse al servidor y usar los métodos de envío y recepción que vimos anteriormente. El argumento de conexión es una tupla con host y puerto, exactamente como bind:

socket_cliente = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
socket_cliente.connect(("localhost", 9999))
 
socket_cliente.send("message")

Implementando el servidor TCP

En este ejemplo, vamos a crear un servidor TCP multiproceso. El socket del servidor abre un socket TCP en localhost:9999 y escucha las solicitudes en un bucle infinito. Cuando reciba una solicitud del socket del cliente, devolverá un mensaje que indica que se ha realizado una conexión desde otra máquina.

El ciclo while mantiene vivo el programa del servidor y no permite que finalice el código. La instrucción server.listen(10) escucha la conexión y espera al cliente. Esta instrucción le dice al servidor que comience a escuchar con la acumulación de conexiones máximas establecida en 10.

Para implementar un socket servidor se puede utilizar algunos de los métodos que ofrece el módulo socket. En concreto podemos utilizar el método bind() que permite asociar un host y un puerto con un determinado socket. Para aceptar peticiones por parte de un socket cliente habría que utilizar el método accept(). De esta forma, el socket servidor queda a la espera de recibir una conexión de entrada desde otro host.

Puede encontrar el siguiente código en el archivo tcpserver.py dentro de la carpeta cliente-servidor-tcp. El socket servidor abre un socket TCP en el puerto 1337 y se queda escuchando peticiones en un bucle infinito. Cuando reciba una petición desde el socket cliente devolverá un mensaje indicando que se produce una conexión desde otra máquina.

#!/usr/bin/python
# tcpserver.py
import socket
 
host = 'localhost'
puerto = 1337
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
hostport = (host, puerto)
s.bind(hostport)
s.listen(10)
 
print("Servidor tcp escuchando en el puerto", hostport)
 
while 1:
    cliente,addr = s.accept()
    print("Conexion desde", addr)
    buffer = cliente.recv(1024)
    print("Datos recibidos", buffer)
    if buffer == b"Hola mundo":
        cliente.send(bytes("Servidor recibe Hola Mundo\n",'utf-8'))
    cliente.close()

Implementando el cliente TCP

El socket del cliente abre el mismo tipo de socket en el que el servidor está escuchando y envía un mensaje. El servidor responde y finaliza su ejecución, cerrando el socket del cliente. Puede encontrar el siguiente código en el archivo tcpclient.py:

 #!/usr/bin/python
# tcpclient.py
import socket
 
host="127.0.0.1"
puerto = 1337
 
try:
    mysocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    hostport = (host, puerto)
    mysocket.connect(hostport)
    mysocket.send(bytes("Hola mundo",'utf-8'))
    buffer = mysocket.recv(1024)
    print("Datos recibidos", buffer)
    mensaje="Mensaje desde el cliente\n"
    mysocket.sendall(bytes(mensaje.encode('utf-8')))
 
except socket.errno as error:
    print("Socket error ", error)
finally:
    mysocket.close()

En nuestro ejemplo, configuramos un servidor HTTP en la dirección 127.0.0.1 a través del puerto estándar 1337. Nuestro cliente se conectará a la misma dirección IP y puerto, y recibirá 1024 bytes de datos en la respuesta y la almacenará en una variable llamada buffer, para posteriormente mostrar esa variable al usuario.

En el código anterior, el nuevo método s.connect((host, puerto)) conecta el cliente con el servidor, y el método s.recv(1024) recibe los mensajes enviados por el servidor.

Uso de cliente y servidor

Primero tendríamos que ejecutar el cliente:

python tcpserver.py

El script quedará en funcionamiento con un socket escuchando en el puerto 1337 en el host localhost.

Ahora ejecutaríamos el cliente:

python tcpclient.py

El cliente se conectará al puerto 1337 del host localhost, que es donde está escuchando el servidor. Veremos en la consola con la ejecución del servidor el mensaje que se envía desde el cliente. En la parte del cliente, veremos la respuesta que envía el servidor.

Actividad práctica

En esta actividad práctica vamos a desarrollar una sencilla aplicación cliente-servidor orientada al envío de mensajes del cliente al servidor. El servidor se va a quedar a la espera de una conexión por parte de un cliente, el cliente manda al servidor cualquier mensaje que escriba el usuario y el servidor lo que hará será repetir el mensaje recibido. La ejecución termina cuando el usuario escribe quit desde la parte del cliente.

cliente:

import socket
s = socket.xxxx()
s.xxx(("localhost", xxxx))
while xxx:
    mensaje = input("> ")
    s.xxx(bytes(xxx.encode('utf-8')))
    if mensaje == "quit":
        break
print("cerrando socket cliente")
s.xxx()

servidor:

import socket
s = socket.socket()
s.xxx(("localhost", xxx))
s.xxx(1)
print("servidor escuchando en el puerto 9999...")
sc, addr = s.xxx()
 
while xxx:
    recibido = sc.xxx(1024)
    print("Recibido:", recibido)
    sc.xxx(recibido)
    if recibido == bytes("quit",'utf-8'):
        break
print("cerrando el socket servidor")
sc.close()
s.close()

Sustituir las xxx por variables definidas en los scripts cliente.py y servidor.py con el objetivo de implementar lógica adicional para que desde el servidor se pueda controlar un cliente mediante el envío de mensajes.

Solución

Servidor:

import socket
s = socket.socket()
s.bind(("localhost", 9999))
s.listen(1)
print("servidor escuchando en el puerto 9999...")
sc, addr = s.accept()
 
while True:
    recibido = sc.recv(1024)
    print("Recibido:", recibido)
    sc.send(recibido)
    if recibido == bytes("quit",'utf-8'):
        break
print("cerrando el socket servidor")
sc.close()
s.close()

Cliente:

import socket
s = socket.socket()
s.connect(("localhost", 9999))
while True:
    mensaje = input("> ")
    s.send(bytes(mensaje.encode('utf-8')))
    if mensaje == "quit":
        break
print("cerrando socket cliente")
s.close()
informatica/programacion/cursos/python_avanzado_proyectos_seguridad/creando_cliente_servidor_tcp_sockets.txt · Última modificación: por tempwin