Está en la página 1de 5

Práctica 1: sockets en Python

Álvaro Navarro anavarro@gsyc.es


Jesús M. González-Barahona jgb@gsyc.es

Infraestructura de Redes
5o Ingenierı́a Informática 08/09

1. Fase 1: Cliente UDP


Esta primera fase tiene como objetivo familiarizarnos con el paquete sockets de Python. Para
ello construiremos un sencillo cliente UDP que envı́e paquetes a un receptor. Para usar librerı́a de
sockets basta con importar el paquete sockets de la siguiente forma:

import socket

Podemos crear un socket mediante la función socket:

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

AF INET especifica la familia de procolos que usaremos. Las otras dos posibilidades son
AF UNIX y AF INET6, pero quedan fuera del alcance de esta práctica.
SOCK STREAM define nuestro socket como TCP. Para definir un socket como UDP usarı́amos
la constante SOCK DGRAM. Existen más tipos de sockets, como por ejemplos sockets RAW, que
no usaremos en esta práctica.
Una vez creado el socket, podemos realizar envı́o y recepciones mediante las funciones send
y recv, en el caso de haber definido un socket TCP, o mediante sendto y recvfrom para UDP. Para
finaliar cerraremos el descriptor del socket mediante el método close.
En este punto conviene leer el manual de Python del paquete sockets1 . En él se describe cómo
usar métodos importantes como inicializar la conexión mediante el método connect (para TCP) o
el método bind para escuchar peticiones en un puerto.
Construye un cliente que envı́e peticiones UDP a un host. La dirección IP y el puerto destino
serán configurados mediante argumentos en lı́nea de comandos. De esta forma podrı́amos lanzar
nuestro cliente:

python clienteUDP.py 127.0.0.1 4000


1
http://www.python.org/doc/2.5.2/lib/module-socket.html

1
Donde 127.0.0.1 es la IP destino (localhost en este caso) y 4000 el puerto.
El programa nos preguntará qué mensaje (string) queremos enviar. El usuario escribirá una
frase que será enviada al servidor. El programa finalizará sólo cuando el usuario escriba quit.
Para parsear los argumentos de la lı́nea de comandos utilizamos el paquete sys de Python.
Nuestro programa recibirá los argumentos en forma de lista, de forma que podemos acceder a
cada elemento de la siguiente forma:

sys.argv[0] # referencia al nombre del programa


sys.argv[1] # referencia al primer argumento
sys.argv[2] # referencia al segundo argumento
...

Para probar nuestro cliente necesitamos un proceso escuchando peticiones en algún puerto
determinado. Como todavı́a no hemos construido ningún servidor, usaremos la herramienta netcat.
Para ello, desde consola ejecutamos:

nc -u -l -p 4000

-u indica que esperamos datagramas UDP

-l pone netcat en modo escucha

-p indica el puerto por donde escucharemos peticiones

Por último ejecuta el analizador de redes wireshack y estudia los paquetes que están intercam-
biando cliente y servidor. Ya que wireshack necesita permisos de root, utilizaremos la herramienta
sudo para lanzarlo:

sudo ‘which wireshack‘

2. Fase 2: Servidor UDP


En esta fase implementaremos un servidor que realice la misma función que netcat en la
Fase1. Crearemos un nuevo programa, servidorUDP.py, que implemente un servidor UDP. La
estructura del programa es similar al clienteUDP.py a diferencia del argumento pasado por lı́nea
de comandos:

python servidorUDP.py 4000

Además debemos usar el método bind para escuchar peticiones en el puerto indicado.
El servidor implementará un servicio de echo, es decir, devolverá al cliente el mismo string
que éste le envı́e pero convertido a mayúsculas. Por ejemplo:

2
# lanzamos primero el servidor en un terminal
anavarro@host:˜/$ python servidor UDP.py 4000
Esperando peticiones...
mensaje aceptado!

# lamzamos el cliente
anavarro@host:˜/$ python clienteUDP.py localhost 4000
Escriba mensaje: luke, soy tu padre
El servidor dice: LUKE, SOY TU PADRE

Por último vuelve a lanzar tu práctica con Wireshack para estudiar qué está pasando.

3. Fase 3: Cálculo RTT


En esta tercera fase calcularemos el Roud Trip Time o RTT entre ambos nodos. Para ello
necesitamos construir un cliente y un servidor (fases anteriores), que realicen las siguientes tareas:

El cliente enviará una petición de tiempo al servidor tomando el timestamp del momento
del envı́o.

El servidor recibe la petición de tiempo y responde al cliente.

El cliente recibe el la respuesta del servidor y vuelve a tomar un timestamp tras la recepción.

El cliente puede calcular el RTT de la siguiente forma:

RTT = timestamp_recepcion - timestamp_envio

Para calcular un Timestamp podemos usar el paquete time de Python:

import time

y ejecutar, por ejemplo:

t = time.time()

Podéis también echarle un vistazo al paquete datetime2 . Este paquete permite manejar fechas
y horas de forma muy cómoda permitiendo el formato y el acceso a cada uno de los elementos del
timestamp (dı́a, mes, año, hora, minuto...)
2
http://www.python.org/doc/2.5.2/lib/module-datetime.html

3
4. Fase 4: Intercambio RTTs bidireccional
En esta última fase construiremos 2 nodos que envı́en y reciban tiempos. Para facilitar la
construcción de nodos más complejos, nos serviremos del paquete SocketServer3 de Python:

import SocketServer

Este paquete facilita la implementación de servidores mediante la delegación de la lógica de


la aplicación a métodos handle. Estos métodos están implementados dentro de una clase, que debe
heredar de Base RequestHandler y que incluye variables para el manejo de conexiones y clientes.
A continuación se incluye un esqueleto para implementar una aplicación basada en este paquete:

import SocketServer
import threading

# heredamos de BaseRequestHandler
class MyUDPHandler(SocketServer.ThreadingMixIn, SocketServer.BaseRequestHandler):

# constructor
def __init__ (self, request, client_address, server):
SocketServer.BaseRequestHandler.__init__(self, request, client_address, server)

# metodo handler
def handle (self):
data = self.request[0].strip()
socket = self.request[1]
print "%s wrote:" % self.client_address[0]
print data
socket.sendto (data.upper(), self.client_address)

if __name__ == "__main__":
cnn = ("127.0.0.1", 4000)
server = SocketServer.UDPServer (cnn, MyUDPHandler)
server_thread = threading.Thread (target=server.serve_forever)
server_thread.setDaemon (True)
server_thread.start()

El objetivo de esta fase es crear dos nodos que calculen sus RTTs e intercambien dicho valor
entre ellos. De tal forma que ambos nodos mantendrán dos valores: por un lado el RTT que él
mismo ha calculado y el RTT que el otro nodo ha calculado. Suponiendo que n1 y n2 sean dos
nodos de nuestra red, tendremos dos operaciones que implementar:

Cálculo de RTT (fase 3). n1 calcula el RTT respecto a n2.


3
http://docs.python.org/library/socketserver.html

4
Obtención de RTT: n1 pide a n2 el tiempo que n2 ha calculado anteriormente. En el que
supuesto que n2 no haya calculado el RTT todavı́a, puede enviar 0.

Por tanto, cada nodo deberı́a recibir tres argumentos:

puerto en el que escuchará peticiones


dirección IP de otro nodo
puerto del otro nodo

Si queremos evitar los dos últimos argumentos, podemos hacer que nuestro nodo nos pregunte
en tiempo de ejecución la IP y el puerto del nodo sobre el que queremos calcular y obtener el RTT.
Aunque la solución se puede abordar de muchas formas, recomendamos implementar un sis-
tema básico de mensajes. Será el método handler de la clase SimpleRequestHandler el que deberı́a
implementar la lógica de la aplicación. El siguiente fragmento de código ilustra un ejemplo sobre
cómo discriminar el tipo de mensaje entrante:

msg = self.request[0].strip()

if msg == "GET_TIME":
self.request.send(time)
elif msg == "CALCULATE_TIME":
self.request.send("ACK")
else:
self.request.send("Unknown command")

A la hora de enviar y recibir datos que no sean strings, necesitaremos hacer una serialización
del envı́o. Para ello usaremos el paquete struct de Python y los métodos pack/unpack. Aquı́ se
muestra un ejemplo muy sencillo:
import struct

>> packet = struct.pack(’!d’, 1294.58)


>> ’@\x94:Q\xeb\x85\x1e\xb8’
>> struct.unpack (’!d’, packet)
>> (1294.58,)

El primer argumento del método pack especifica el tipo de dato sobre el que se realizará la
conversión. En este ejemplo indicamos que es un tipo float (d) y que además irán por la red (!),
evitando ası́ problemas con el ’endian’. Tenéis más información del paquete struct en la página
del manual4
Para probar la práctica recomendamos lanzar cada nodo en máquinas diferentes y, a ser posi-
ble, desde campus diferentes.
4
http://www.python.org/doc/2.5.2/lib/module-struct.html

También podría gustarte