109492 - Pàgina inicial de UPCommons

Anuncio
Desarrollo de una aplicación
BitTorrent
(en Pharo-Smalltalk)
Trabajo Final de Grado
David Gracia Celemendi
Director: Jordi Delgado Pin
Departamento de Ciencias de la Computación (CS)
Fecha de defensa: octubre de 2015
Titulación: Grado en Ingeniería Informática
Especialidad: Computación
Facultad de Informática de Barcelona (FIB)
página en blanco
1
Resumen
Desde principios del 2000, el uso de redes peer-to-peer ha experimentado
un gran crecimiento. Gran parte del tráfico de Internet lo generan las aplicaciones BitTorrent. BitTorrent especifica un protocolo para el intercambio
de ficheros usando un modelo peer-to-peer que se caracteriza por su gran
escalabilidad y robustez. Este proyecto consiste en el desarrollo de una aplicación BitTorrent desde cero con el sistema Pharo-Smalltalk. Primero se
hace un repaso a la historia del intercambio de ficheros desde sus inicios
hasta la actualidad. Después se comparan las redes cliente-servidor con las
redes peer-to-peer , y se distingue dentro de éstas últimas entre redes estructuradas y redes no estructuradas. Se da una explicación del funcionamiento
de BitTorrent y, por último, se profundiza en el diseño y la implementación
de la aplicación.
Abstract
Since 2000 peer-to-peer networks use has increased very fast. Most Internet
traffic is generated by BitTorrent applications. BitTorrent specify a file
sharing protocol over peer-to-peer model whose strength is scalability and
robustness. This project is about developing a BitTorrent application from
scratch with Pharo-Smalltalk system. First of all, a file sharing history
review is done from beginning up to now. Next, client-server and peerto-peer models are compared, and peer-to-peer networks are classified in
unstructured and structured. A brief summary of BitTorrent operation is
done and, at the end, report go into detail about design and implementation
of the application.
Keywords. peer-to-peer, p2p, bittorrent, swarming, smalltalk, pharo.
Agradecimientos
Gracias a Jordi Delgado por su ayuda durante el desarrollo del proyecto y en la elaboración de la memoria.
2
Índice
1. Introducción
1.1. Formulación del problema . . . . .
1.2. Actores implicados . . . . . . . . .
1.3. Objetivos generales . . . . . . . . .
1.4. Objetivos técnicos . . . . . . . . . .
1.5. Sobre el proyecto y el formato de la
. . . . .
. . . . .
. . . . .
. . . . .
memoria
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2. Contexto
2.1. Redes peer-to-peer . . . . . . . . . . . . . . . . . . .
2.1.1. Descubrimiento de recursos . . . . . . . . . .
2.1.2. Aplicaciones . . . . . . . . . . . . . . . . . . .
2.2. Historia del intercambio de ficheros . . . . . . . . . .
2.3. BitTorrent . . . . . . . . . . . . . . . . . . . . . . . .
2.3.1. Funcionamiento . . . . . . . . . . . . . . . . .
2.3.2. Implementaciones existentes y últimos avances
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
7
7
8
8
8
9
.
.
.
.
.
.
.
10
10
12
14
15
19
19
21
3. Metodología
22
3.1. Seguimiento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.2. Estilo de programación . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.3. Validación del software . . . . . . . . . . . . . . . . . . . . . . . . . 23
4. Visión global de la aplicación
23
5. Diseño e implementación
5.1. Bencoding . . . . . . . . . . . . .
5.2. Los metadatos . . . . . . . . . . .
5.3. Acceso físico a los torrents . . . .
5.4. Los nodos de la red . . . . . . . .
5.5. Descubrimiento de peers . . . . .
5.5.1. Tracker HTTP . . . . . .
5.5.2. Tracker UDP . . . . . . .
5.5.3. La clase BtMultitracker
5.6. Control de las piezas . . . . . . .
5.7. Comunicación peer-to-peer . . . .
5.7.1. Mensajes . . . . . . . . . .
5.7.2. Sistema de colas . . . . . .
5.7.3. La clase BtRemotePeer . .
26
26
28
29
31
31
31
34
40
41
42
42
46
47
3
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5.8. Piezas temporales . . . . . . . . . . . . . . . . . . . .
5.9. Algoritmo de bloqueo . . . . . . . . . . . . . . . . . .
5.10. Algoritmo de selección de piezas . . . . . . . . . . . .
5.10.1. Rarest first . . . . . . . . . . . . . . . . . . .
5.10.2. Random . . . . . . . . . . . . . . . . . . . . .
5.10.3. Most common first . . . . . . . . . . . . . . .
5.10.4. Lower first . . . . . . . . . . . . . . . . . . . .
5.11. End Game . . . . . . . . . . . . . . . . . . . . . . . .
5.12. Conteo de las piezas en la red . . . . . . . . . . . . .
5.13. La clase BtRemotePeerCollection . . . . . . . . . .
5.14. La clase BtTorrent . . . . . . . . . . . . . . . . . . .
5.14.1. Proceso: Validación incial . . . . . . . . . . .
5.14.2. Proceso: Tracker requesting . . . . . . . . . .
5.14.3. Proceso: Choking . . . . . . . . . . . . . . . .
5.14.4. Proceso: Optimistic Choking . . . . . . . . . .
5.14.5. Proceso: Tratamiento de mensajes entrantes .
5.14.6. Proceso: Petición de bloques . . . . . . . . . .
5.15. La clase BtLocalPeer . . . . . . . . . . . . . . . . .
5.15.1. Proceso: Escucha de puerto . . . . . . . . . .
5.15.2. Proceso: Gestión de torrents . . . . . . . . . .
5.15.3. Justificación de la necesidad de BtLocalPeer .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
49
50
51
51
51
52
52
52
52
53
55
56
56
57
58
59
60
62
63
64
65
6. Planificación
66
6.1. Planificación inicial . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
6.2. Modificaciones a la planificación inicial . . . . . . . . . . . . . . . . 69
7. Presupuesto y sostenibilidad
7.1. Identificación de recursos y estimación de costes . . . . . . . . . . .
7.2. Viabilidad económica . . . . . . . . . . . . . . . . . . . . . . . . . .
7.3. Impacto social y ambiental . . . . . . . . . . . . . . . . . . . . . . .
69
69
70
70
8. Conclusiones
71
9. Posibles ampliaciones
71
Adenda
73
A. Manual de usuario
73
A.1. Instalación de Pharo . . . . . . . . . . . . . . . . . . . . . . . . . . 73
A.2. Ejecución de Pharo . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
4
A.3. Importación de la librería
A.4. Pruebas . . . . . . . . . .
A.4.1. Pruebas unitarias .
A.4.2. Prueba global . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
74
76
76
77
Acrónimos
80
Glosario
81
Referencias
88
5
Índice de figuras
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
BitTorrent. Proceso que siguen las propuestas de ampliación . . .
Comparación entre las arquitecturas cliente-servidor y peer-to-peer
En Freenet, backtracking usado para encontrar un recurso . . . . .
Comparación topológica. Red peer-to-peer no estructurada. Red
peer-to-peer estructurada . . . . . . . . . . . . . . . . . . . . . . .
Amiexpress . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Jerarquía de warez scene . . . . . . . . . . . . . . . . . . . . . . .
Usenet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Funcionamiento de una red BitTorrent . . . . . . . . . . . . . . .
Perspectiva del peer local en el enjambre . . . . . . . . . . . . . .
Diagrama de clases simplificado de la aplicación . . . . . . . . . .
Ejemplo de colección de ficheros de un torrent. . . . . . . . . . . .
Jerarquía de clases de los nodos de la red . . . . . . . . . . . . . .
Jerarquía de clases de los paquetes UDP . . . . . . . . . . . . . .
Diagrama de Gantt del proyecto . . . . . . . . . . . . . . . . . . .
Selección de imagen en Pharo (Linux Mint) . . . . . . . . . . . .
Menú global de Pharo . . . . . . . . . . . . . . . . . . . . . . . .
Añadir un repositorio HTTP público a Pharo . . . . . . . . . . .
Exploración del paquete BitTalk en Pharo . . . . . . . . . . . . .
Captura de imágen de Pharo mostrando cómo ejecutar todas las
pruebas unitarias de una vez . . . . . . . . . . . . . . . . . . . . .
Ejecución del código de la prueba global . . . . . . . . . . . . . .
. 9
. 11
. 12
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
13
15
16
17
19
20
24
30
32
36
68
74
75
75
76
. 77
. 78
Índice de tablas
1.
2.
3.
Características. Redes no estructuradas y estructuradas . . . . . . . 14
Planificación de las tareas . . . . . . . . . . . . . . . . . . . . . . . 67
Presupuesto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
6
1.
1.1.
Introducción
Formulación del problema
Este Trabajo de Fin de Grado (TFG) consiste en el desarrollo de una aplicación
BitTorrent1 en el sistema Pharo-Smalltalk. BitTorrent es un protocolo diseñado
para el intercambio de ficheros sobre una red peer-to-peer (P2P) que se usa para
distribuir gran cantidad de información por Internet. Smalltalk es un estándar,
públicamente disponible desde 1980[4], que define las especificaciones de un sistema
computacional que dispone de
una biblioteca (generalmente grande) de objetos que viven dentro del sistema
y se comunican mediante mensajes;
un fichero llamado «imagen» que contiene el estado del sistema;
un lenguaje de programación (también llamado Smalltalk) completamente
orientado a objetos, con tipado dinámico y reflexivo;
un entorno de desarrollo, compilación y ejecución;
y una máquina virtual que se encarga de interpretar todos los mensajes y
actualizar el estado del sistema y los objetos.
Se puede considerar a un sistema Smalltalk como un mundo virtual donde existen
objetos «vivos» que se comunican. Estos objetos pueden ser creados, modificados
y eliminados sin necesidad de detener o reiniciar el sistema.
Una implementación de Smalltalk es cualquier sistema que cumpla las especificaciones del estándar. La implementación que se va a usar en este proyecto es
Pharo (versión 3.0).
1
La expresión común para referirse a este tipo de programas es «cliente BitTorrent», pero
es incorrecta porque en estas redes no existen los roles exclusivos de cliente y servidor: solo
existen peers. En este documento se reserva el uso de «peer » para el concepto abstracto de nodo
participante en la red, y el de «aplicación BitTorrent» para el programa que ejecutan los peers
7
1.2.
Actores implicados
Las siguientes personas están implicadas o se ven afectadas por el desarrollo del
proyecto:
Autor del proyecto: David Gracia Celemendi.
Director del proyecto: Jordi Delgado Pin. Miembro del Consejo de Dirección del European Smalltalk User Group (ESUG) y Coordinador de
l’Associació Smalltalk.cat. Se encargará de supervisar los aspectos técnicos
del proyecto.
Comunidad de usuarios y desarrolladores de Pharo-Smalltalk. Se
beneficiarán de la primera librería que implementa BitTorrent en esta comunidad. La librería supondrá una base para desarrollar otros proyectos
relacionados con BitTorrent y redes P2P.
1.3.
Objetivos generales
Los objetivos principales del proyecto son:
Diseñar e implementar el conjunto de clases que representen la naturaleza y
el funcionamiento de las redes BitTorrent.
El diseño y la implementación deben ser fácilmente comprensibles y ampliables por cualquier desarrollador de Pharo-Smalltalk que conozca el funcionamiento de BitTorrent.
1.4.
Objetivos técnicos
BitTorrent está compuesto por una serie de documentos llamados BitTorrent Enhancement Proposal (BEP) que detallan el funcionamiento del protocolo. Un BEP
es una propuesta de ampliación de alguna parte de BitTorrent, que especifica una
nueva funcionalidad o comportamiento de la red o de los actores implicados. Todos
los BEP pueden encontrarse en http://www.bittorrent.org/beps/bep_0000.
html. Estas propuestas están sujetas a un proceso de ampliación que establece que
una propuesta debe pasar por unas fases antes de ser considerada definitiva por el
Benevolent Dictator for Life (BDFL) y creador de BitTorrent, Bram Cohen.
8
Figura 1: Proceso que siguen las propuestas de ampliación
Existen varios BEP utilizados por muchas aplicaciones BitTorrent que todavía
se encuentran en fase de borrador (Draft) o solo han sido aceptados (Accepted )
sin llegar a ser definitivos (Final ). Incluso hay otros que, a pesar de no tener
siquiera borrador, son utilizados en las aplicaciones BitTorrent más populares. Los
objetivos técnicos del proyecto son implementar los siguientes BEP:
BEP3 Núcleo de BitTorrent, en fase definitiva.
BEP5 Implementa distributed hash table (DHT) para aumentar la descentralización de BitTorrent, en fase de borrador.
1.5.
Sobre el proyecto y el formato de la memoria
El desarrollo de la aplicación carece de análisis y especificación de requisitos
ya que el protocolo ya está especificado y definido. En esta memoria se explica
en detalle el diseño y la implementación. No es objetivo de la memoria explicar la especificación y el funcionamiento del protocolo, pero se profundizará
en él cuando sea necesario para justificar las decisiones de diseño.
El trabajo de este proyecto ha consistido en diseñar e implementar desde
cero el protocolo BitTorrent en Pharo-Smalltalk sin usar en absoluto otros
trabajos o librerías preexistentes.
Se adjuntará código de la aplicación como recurso explicativo del diseño y la
implementación. Si la aplicación se hubiera programada en un lenguaje como
C/C++ o Java, se habría descartado hacerlo, pero Smalltalk permite crear
lenguajes específicos de dominio (en inglés domain-specific language (DSL))
con una sintaxis tan expresiva que puede leerse casi como si fuera lenguaje
natural.
9
Pharo-Smalltalk tiene un entorno gráfico que, junto con la reflexión, permite
examinar la estructura interna y el estado de los objetos en tiempo real.
Aunque desarrollar una interfaz gráfica de usuario (en inglés graphical user
interface (GUI)) no era uno de los objetivos técnicos, es posible usar la
aplicación con pocas instrucciones y observar en tiempo real el estado de la
aplicación con el entorno gráfico de Pharo-Smalltalk.
2.
2.1.
Contexto
Redes peer-to-peer
El criterio de clasificación de redes que probablemente las separa en los dos grupos
más significativos es quién sirve los recursos disponibles. Estos dos grupos son
las redes cliente-servidor y las redes P2P. Schollmeier[13] define las redes clienteservidor como
Arquitectura de red que consiste en un sistema de alto de rendimiento, el
servidor, y varios sistemas habitualmente de menor rendimiento, los clientes.
El servidor es el único proveedor de servicios. Los clientes solo consumen
servicios sin compartir ninguno de sus recursos.
Y las redes P2P como
Arquitectura de red donde los participantes comparten parte de sus recursos
hardware (tiempo de procesador, espacio de almacenamiento, ancho de banda, dispositivos. . . ). Estos recursos son necesarios para proveer el servicio
que ofrece la red y son accesibles directamente por cualquier participante sin
necesidad de otra entidad mediadora. Los participantes de estas redes son
tanto proveedores como consumidores de servicios.
10
(a) Arquitectura cliente-servidor
(b) Arquitectura peer-to-peer
Figura 2: Comparación entre las arquitecturas cliente-servidor y peer-to-peer
Un concepto íntimamente relacionado con las redes P2P es el de supercapa
(en inglés overlay). Una supercapa es una capa de aplicación virtual, o red lógica,
que ofrece servicios normalmente no disponibles en la capa física subyacente[14].
Las redes P2P en realidad son supercapas de Internet formadas por las conexiones
entre los peers.
Las siguientes características se encuentran en la mayoría de redes P2P. Aunque
a menudo no se cumplen de forma completa o absoluta.
Conectividad Todos los nodos están interconectados.
Descentralización El comportamiento de la red P2P está determinado principalmente por la acción colectiva de los peers.
Simetría Los nodos asumen roles equivalentes.
Participación libre Cada peer puede unirse y abandonar la supercapa cuando
quiera.
Escalabilidad Las supercapa puede llegar a tener millones de nodos debido a que
la carga de trabajo se reparte de forma homogénea entre los peers.
Estabilidad Las redes P2P toleran situaciones de churn. Churn es el término
usado para referirse a la situación donde los peers de la supercapa se unen y
abandonan la red con mucha frecuencia.
Robustez El funcionamiento de las redes P2P no depende de la existencia de un
nodo especial o punto vulnerable.
11
2.1.1.
Descubrimiento de recursos
Dentro de las redes P2P existen dos grandes categorías: estructuradas y no estructuradas. Lo que las diferencia es cómo los peers descubren los recursos en la
supercapa.
Las redes P2P no estructuradas se
basan en supercapas aleatorias en las
que los recursos son descubiertos haciendo backtracking, flooding o usando
caminos aleatorios (random walks en
inglés)[7]. Backtracking es un algoritmo que busca todas las soluciones posibles (o una parte) de algunos problemas
computacionales. Flooding es un algoritmo de encaminamiento en el que cada nodo reenvía cada paquete recibido
por todas sus conexiones menos por la
que ha llegado.
La figura 3 muestra la típica secuencia de mensajes de petición que se da
en Freenet. El usuario inicia una petición de información en el peer A, el cual
reenvía la petición a B, entonces B la
reenvía a C. El peer C no puede reenviar la petición a ningún peer más y Figura 3: En Freenet, backtracking usado
devuelve un error a B. El peer B prue- para encontrar un recurso
ba su segunda opción, el peer E, el cual
reenvía la petición a F, que reenvía a B. Entonces B detecta el bucle y devuelve
un error de petición a F, el cual no es capaz de reenviar la petición a ningún peer
más y la petición vuelve atrás, hacia E. Entonces el peer E prueba su segunda
opción, el peer D, el cual tiene la información deseada. Finalmente, la información
encontrada es devuelta a través de E, B y A.
El uso de esta técnica, igual que con flooding y caminos aleatorios, es muy
costoso porque las peticiones de contenido poco común tienen que ser reenviadas a
un gran número de peers antes de encontrar el recurso deseado. Además, los peers
tienen que procesar muchas peticiones de información aunque no tengan el recurso
deseado. El uso de este tipo de técnicas hace que la topología de la supercapa
sea totalmente aleatoria, sin ninguna forma en particular, de ahí el término «no
estructurada».
En estas redes, la información popular es fácil de encontrar, pero es probable que un peer no encuentre el recurso que busca si es muy raro. Debido a la
12
falta de estructuración, estas redes son fáciles de construir y resistentes a situaciones de churn[14]. Ejemplos de redes no estructuradas son Freenet[2], Gnutella, y
FastTrack.
(a) Topología de una red P2P no estructu- (b) Topología de una red P2P estructurada
rada
(distributed hash table)
Figura 4: Comparación toplógica entre una red P2P no estructurada y una red
P2P estructurada
Una red P2P estructurada es una supercapa en la que los peers mantienen la
información de encaminamiento y localización de los recursos de forma cooperativa. Esta estrategia normalmente asegura que los recursos son encontrados en un
número de pasos acotado superiormente aunque sean extremadamente raros.
La mayoría de este tipo de redes usan DHT. Una DHT consiste en una tabla
con registros <clave,valor>que contiene la información de contacto de todos los
nodos de la red. El espacio de claves, típicamente de 160 bits, es repartido entre
los nodos de la red, y cada uno de ellos es responsable de guardar una parte de
la tabla. Gracias a las DHT, los nodos participantes pueden encontrar cualquier
recurso identificado con una clave. Ejemplos de DHT son Content Addressable
Network (CAN)[11], Tapestry[17], Chord[15], Pastry[12], Kademlia[9] y Viceroy[8].
Un estudio de 2013 sobre el uso de Mainline DHT (variante de Kademlia que
usa BitTorrent) durante los dos años y medio anteriores estima que el número de
usuarios diarios se encuentra entre 15 y 27 millones, revelando una situación de
churn diario de al menos 10 millones[16].
En la tabla 1 se puede ver una comparación entre las características de las
redes P2P estructuradas y las redes P2P no estructuradas[1].
13
P2P no estructurado
Construcción de la supercapa
Recursos
Mensajes de petición
Localización
Coste de búsqueda
Coste de mantenimiento
de la supercapa
Tolerancia a churn y fallos
Aplicabilidad
P2P estructurado
alta flexibilidad
baja flexibilidad
indexados localmente
backtracking, flooding
caminos aleatorios
probable
impredecible
bajo
indexados en una DHT
número de reenvíos acotado
garantizada
acotado
moderado
y
alta
pequeña escala y redes altamente dinámicas
moderada
gran escala y redes relativamente estables
Tabla 1: Comparación entre las características de las redes no estructuradas y las
redes estructuradas
2.1.2.
Aplicaciones
Las aplicaciones más importantes que se le ha dado a las redes P2P son las siguientes:
Intercambio de ficheros. Es el uso de P2P más extendido. El protocolo más
popular es BitTorrent (http://www.bittorrent.org).
P2PTV. Son aplicaciones diseñadas para retransmitir vídeo en tiempo real
sobre redes P2P. El contenido retransmitido normalmente son canales de televisión alrededor de todo el mundo. StreamRoot (https://www.streamroot.
io) es un ejemplo.
Osiris Serverless Portal System. Es un sistema que permite la publicación y
mantenimiento de sitios webs sobre redes P2P. Este tipo de webs son inmunes
a los ataques de denegación de servicio (http://www.osiris-sps.org).
Invisible Internet Project (I2P) es una supercapa P2P de Internet que proporciona cierto grado de anonimato a sus usuarios (https://geti2p.net).
Sistemas de monedas criptográficas digitales como Bitcoin[10] y Peercoin[6]
funcionan sobre redes P2P.
14
2.2.
Historia del intercambio de ficheros
La historia del intercambio de ficheros ha pasado por diversas épocas marcadas
principalmente por los sistemas que se han utilizado. Aunque el intercambio de
ficheros y las redes P2P actualmente son dos conceptos estrechamente relacionados,
no siempre ha sido así. A continuación se hace un breve resumen de estas épocas.
Bulletin Board System (década de los 70) El Bulletin Board System (BBS)
es considerado en gran parte el inicio del intercambio de ficheros digitales tal y como
lo conocemos hoy en día. BBS es un servidor que permite a usuarios conectarse al
sistema usando un terminal. Una vez dentro del sistema, el usuario puede realizar
una serie de operaciones como subir y descargar software y ficheros, leer noticias y
boletines, e intercambiar mensajes con otros usuarios vía correo electrónico y chat.
Hoy en día todavía existen algunos BBS en funcionamiento.
Figura 5: AmiExpress (1992). Aplicación BBS para Commodore Amiga
Warez scene (década de los 70) Warez scene es una comunidad clandestina
y oculta al gran público, especializada en la distribución de material con derechos
de autor, incluyendo programas y series de televisión, películas, música, videoclips,
videojuegos, aplicaciones, ebooks y pornografía. Empezó a emerger en la década
de los 70. Inicialmente usaban BBS privados para publicar su material, pero posteriormente pasaron a usar los topsites, servidores File Transfer Protocol (FTP)
clandestinos, altamente secretos y con gran ancho de banda capaces de transferir
un Blu-ray en cuestión de segundos y almacenar terabytes de información.
En esta comunidad primero los release groups publican cierto contenido multimedia en un topsite y lo anuncian a través de Internet Relay Chat (IRC). Una vez
el material ha sido publicado, los courier groups, que tienen acceso a los topsites
transportan las publicaciones de un topsite a otro topsite, normalmente usando
FlashXP. Con esta operación, los courier groups obtienen el derecho a descargar
15
tres veces lo que han subido, siempre y cuando el contenido sea original. De esta
forma, los courier groups compiten por ser los primeros en transportar las publicaciones entre topsites. Gracias a la naturaleza descentralizada de este sistema, el
sistema de recompensas y la posibilidad de partir los ficheros en trozos mediante
RAR, las publicaciones rápidamente están disponibles para toda la comunidad de
warez scene.
Sin embargo, debido al alto hermetismo de este tipo
de distribución, es difícil para muchos usuarios conseguir acceso a los topsites. Hoy en día siguen existiendo
algunos.
Usenet: los orígenes de la descentralización (finales de los 70) Usenet es un sistema mundial distribuido de debate y discusión. Sus usuarios pueden leer y
enviar mensajes (llamados articles o posts, y colectivamente llamados news) según categorías, conocidas como
newsgroups. Los servidores redistribuyen los mensajes a
otros servidores creando múltiples copias. Usenet es el
precursor de los foros web que son ampliamente usados
en la actualidad y puede ser visto como un híbrido entre
gestor de correo electrónico y foro web. La mayor diferencia entre BBS y Usenet es la ausencia de un servidor
o administrador central.
Aunque Usenet ha existido desde finales de los 70,
la práctica de intercambio de ficheros no se extendió
hasta mucho más tarde. En 1993, Eugene Roshal creó
la tecnología RAR, la cual permitía a los usuarios partir Figura 6: Jerarquía de
los ficheros en trozos y posteriormente reconstruirlos. warez scene
Gracias a la naturaleza descentralizada de Usenet y a
RAR, la velocidad y la eficiencia de la distribución de ficheros aumentó muchísimo
permitiendo no tener que redistribuirlos íntegramente en caso de errores.
Usenet aún se usa en la actualidad. Sin embargo, se usa mayoritariamente para
el intercambio de ficheros antes que para su propósito original, el cual ha sido
desplazado por los foros web y IRC.
IRC (1988) IRC fué creado por Jarkko Oikarinen en agosto de 1988 para reemplazar un programa de un BBS cuyo propósito original era la mensajería instantánea, pero durante la década de los 90 se popularizó su uso como medio de
intercambio de ficheros. Hoy en día, IRC todavía es ampliamente usado para su
propósito original de chat. También se usa como mecanismo de impulso o punto
16
de arranque para algunos sistemas de intercambio de ficheros.
Hotline (1997) Durante un periodo
breve de tiempo Hotline fué un medio
de intercambio de ficheros muy popular que usaba una arquitectura clienteservidor. Al principio, Hotline tuvo mucho éxito, sin embargo su uso rápidamente perdió fuerza debido a varias
complicaciones, entre ellas el cifrado
del código fuente por parte del fundador, Adam Hinkley, antes de abandonar la compañía. Hacia finales de los 90,
Hotline empezó a desvanecerse, coincidiendo con la aparición de otros sistemas emergentes. Hoy en día, su uso es
casi inexistente.
Figura 7: Una red Usenet con servidores y clientes. Los puntos azules, verdes
y rojos de los servidores representan los
grupos que gestionan. Las flechas entre
servidores indican intercambios de información entre grupos de discusión (feeds).
Las flechas entre clientes y servidores indican que un usuario está suscrito a un
grupo.
Napster (1999) Naspter llevó el
intercambio de ficheros, en concreto
MP3, a las grandes masas. Existía un
servidor central que los usuarios consultaban para encontrar los peers que
tenían el fichero MP3 que querían. Entonces los peers se ponían en contacto
e intercambiaban el fichero íntegramente. El sistema era P2P, aunque también
tenía una parte centralizada ya que dependía del servidor donde se encontraba la base datos que controlaba qué
canciones tenía cada peer .
En el año 2000 la compañía discográfica estadounidense A&M Records y otras
compañías demandaron a Napster a través de la Recording Industry Association
of America (RIAA) por vulnerar derechos de autor protegidos por la Digital Millennium Copyright Act (DMCA). El tribunal encargado del caso sentenció que
Napster era culpable y fue obligado a restringir el acceso a todo el material presente en su red que vulnerase esos derechos, pero no fue capaz y tuvo que detener
el servicio en julio de 2001. Al año siguiente, en 2002, Napster entró en bancarrota
y fue vendida a otra compañía.
17
Diversificación de los sistemas P2P (a principios del 2000) Durante el
litigio entre Napster y la RIAA en el año 2000, aparecieron los sistemas eDonkey2000, Gnutella y Freenet.
eDonkey2000 podía intercambiar ficheros de gran tamaño, pero también dependía de un servidor central como Napster. Este sistema fue el primero en aplicar
el concepto de swarming, que permitía descargar trozos del fichero de distintos
peers simultáneamente para acelerar la descarga. Gnutella era un sistema que no
necesitaba ningún servidor central, por lo que no tenía un punto vulnerable y era
más difícil detener sus servicios. Freenet también carecía de servidor central, pero además ofrecía un mayor grado de anonimato a sus usuarios que los sistemas
anteriores.
En 2001, se publicó el protocolo FastTrack cuya implementación más popular fue Kazaa. Este protocolo no era totalmente descentralizado como Gnutella y
Freenet, ya que existían unos supernodos cuya función era aumentar la eficiencia
de la red.
El cierre de Napster en 2001 propició que muchos de sus usuarios se pasaran
a otros sistemas P2P y el uso de este tipo de redes siguió creciendo. Audiogalaxy
(1998) creció en popularidad, y el protocolo BitTorrent y la aplicación LimeWire
(Gnutella) fueron publicados. En 2002, la RIAA demandó a Audiogalaxy y consiguió detener sus servicios. Hasta su decadencia en 2004, Kazaa fue la aplicación
de intercambio de ficheros más popular a pesar de sus litigios en los Países Bajos,
Australia y los Estados Unidos.
Debido a la dificultad de acabar con los sistemas P2P descentralizados, en 2002,
la RIAA comenzó a demandar a los usuarios de Kazaa gracias a que los Internet
service provider (ISP) o proveedores de servicio a Internet facilitaban su identidad
y se produjeron las primeras multas a usuarios.
Durante 2002 y 2003 se crearon múltiples servicios web que permitían a los
usuarios de BitTorrent introducirse en sus redes, incluyendo Suprnova.org, isoHunt,
TorrentSpy y The Pirate Bay. Este último sufrió redadas en 2006 y 2014.
En 2005, la compañia desarrolladora de eDonkey2000, MetaMachine, fue obligada a dejar de ofrecer su software como resultado de una demanda impuesta por
la RIAA. Ese mismo año, la aplicación BitTorrent Azureus (ahora conocida como
Vuze) fue la primera en implementar DHT dentro de la comunidad BitTorrent, hecho que le dió mucha popularidad a este tipo de redes ayudando a que se acabaran
convirtiendo en las redes P2P más usadas a nivel mundial.
Sistemas de alojamiento de archivos (principios y mediados de los 2000)
Paralelamente a la difusión de los sistemas P2P, algunos sistemas de alojamiento
de archivos se hicieron muy populares, como Megaupload, Rapidshare y Hotfile
(entre otros). La gran ventaja de estos sistemas era su gran facilidad de uso.
18
Los usuarios solo tenían que subir archivos a un servidor y compartir la Uniform
Resource Locator (URL) con otros usuarios para que los descargaran mediante
Hypertext Transfer Protocol (HTTP). Normalmente, las URL eran compartidas
en foros de Internet.
Era práctica común premiar a aquellos usuarios cuyos enlaces eran más veces
descargados, así que era habitual partir los ficheros en trozos como en la época de
los topsites y Usenet, solo que en este caso por motivos estrictamente de provecho.
Estos sistemas también fueron objetivo de gobiernos y otras organizaciones
como RIAA y Motion Picture Association of America (MPAA). El caso más famoso
fue el cierre de Megaupload y arresto de su fundador Kim Dotcom el 19 de enero
de 2012. Justo un año después, el 19 de enero de 2013, Kim Dotcom lanzó un
nuevo servicio de alojamiento de archivos llamado Mega.
2.3.
2.3.1.
BitTorrent
Funcionamiento
BitTorrent es un protocolo P2P diseñado para el intercambio de ficheros (normalmente de gran tamaño). BitTorrent
crea una supercapa no estructurada sobre Internet. Usa la técnica de swarming como ya hacía la red eDonkey, es
decir, parte la información que se quiere
compartir en piezas pequeñas (256 KiB
- 4 MiB) de igual tamaño y los peers de
la red descargan y suben estas partes
simultáneamente de y hacia múltiples
peers acelerando enormemente la descarga. A diferencia de los sistemas de
intercambio de ficheros anteriores, BitTorrent no crea una red única donde se
comparten todos los ficheros en el mundo, sino que crea una red separada por
cada contenido o recurso distinto (estos
recursos únicamente identificables reciben el nombre de torrents).
En una red BitTorrent participan
dos tipos de nodos: los peers y el tracker . Los peers son los encargados de
distribuir las piezas por la red, y el trac-
Figura 8: Funcionamiento de una red BitTorrent que comparte un torrent de 5 piezas. En la red participan 4 peers (tres leechers y un seeder ) y un tracker . En verde,
las piezas que tiene cada peer .
19
ker simplemente registra y coordina a
los peers participantes. El conjunto de todos los peers de una red se llama enjambre o swarm. Los peers normalmente son clasificados como seeders o leechers.
Los primeros son peers que tienen todas las piezas del torrent, mientras que los
segundos son peers que aún les faltan piezas por descargar. Esta distinción es solo
teórica ya que a efectos prácticos todos los peers de la red tienen el mismo rol y
capacidades. Los peers solo se comunican con el tracker cuando quieren unirse a
la red o quieren pedir más peers con los que intercambiar piezas.
Cuando un peer quiere unirse a una
red BitTorrent, lo primero que hace es
descargar un fichero .torrent. Este fichero contiene los metadatos esenciales
para el funcionamiento de la red. Además, estos metadatos identifican de forma única al torrent. Entre otras cosas,
contienen la dirección URL del tracker ,
la colección de ficheros que forman el
torrent, el tamaño de las piezas y el
resumen de cada una de ellas, necesario para verificar su integridad. Una
vez el peer tiene el fichero .torrent, conecta con el tracker mediante HTTP
para «anunciarse», es decir, para que
el tracker sepa que hay un nuevo peer
que quiere participar en la red. Acto
seguido, el tracker devuelve una lista
con la información de contacto de otros
Figura 9: Perspectiva del peer local en el peers (dirección Internet Protocol (IP)
enjambre.
y puerto) para que pueda conectarse
con ellos e intercambiar piezas. Estos
ficheros .torrent normalmente se encuentran en páginas web que actúan como directorios de torrents.
En la figura 8 se puede ver un ejemplo de cómo funciona el swarming en
BitTorrent. El peer B, que solo tiene las piezas 1 y 4, descarga la pieza 3 del peer
C. Éste último, al mismo tiempo está descargando las piezas 2 y 4 de los peers D
y A, respectivamente. Por último, el peer D descarga las piezas 1 y 3 de B y A,
respectivamente.
Es importante recalcar que un peer cualquiera normalmente no conoce a todos
los peers del enjambre, ya que suelen ser más que el número de conexiones simultáneas que el sistema del peer local puede soportar. Pero técnicamente hablando,
es posible que un peer pida al tracker todos los peers del enjambre. Otra cuestión
20
es que sea capaz de abrir una conexión con cada uno de ellos simultáneamente.
Y si es capaz, que el ancho de banda de su conexión a Internet permita recibir
paquetes de información de todas las conexiones a la vez. Los enjambres pueden
llegar a ser realmente grandes. Por ejemplo, el enjambre más grande en la historia
de BitTorrent se dio en 2014 con 193.000 peers.
La figura 8 es útil para tener una visión global del funcionamiento de BitTorrent, pero no se corresponde con la perspectiva de un peer cualquiera. Lo que ve
el peer local en realidad es una red como la de la figura 9 donde solo conoce a una
parte del enjambre. Dicho de otra forma, cada peer conoce un grupo particular de
vecinos. Estos vecinos pueden coincidir con los de otros peer , pero en principio es
por pura casualidad.
2.3.2.
Implementaciones existentes y últimos avances
A lo largo de todos los años que diferentes aplicaciones BitTorrent han estado en
funcionamiento, la comunidad BitTorrent ha propuesto nuevos BEP que amplían
las funcionalidades del protocolo. Algunas propuestas han sido tan bien valoradas
por la comunidad que se han convertido en práctica de facto, incluso teniendo
solamente estatus de borrador o aceptadas, sin llegar a ser definitivas (ver figura
1). Estos son los avances tecnológicos más significativos hasta la fecha.
uTorrent Transport Protocol (uTP) (BEP29) Es un protocolo implementado sobre User Datagram Protocol (UDP) y diseñado para sustituir a Transmission Control Protocol (TCP) y así evitar sus problemas inherentes de
congestión y al mismo tiempo mantener la fiabilidad y el orden de la información.
Magnet Uniform Resource Identifier (URI) (BEP9) El esquema Magnet
URI permite a los peers unirse a la red sin descargar el fichero .torrent.
DHT (BEP5) Con las DHT los peers son capaces de localizar los recursos de la
red sin consultar al tracker .
Peer Exchange (PEX) A pesar de que su uso es extendido, no existe propuesta
oficial. Permite que un peer conozca otros peers a través de sus vecinos. Sigue
siendo necesario consultar inicialmente al tracker para unirse a la red.
Web seeding (BEP17, BEP19) Permite a los peers descargar el fichero de un
servidor HTTP/FTP (además de la opción P2P).
Tracker UDP Permite realizar la comunicación entre los peers y el tracker sobre
UDP. Disminuye hasta un 50 % el tráfico entre los peers y el tracker .
21
Descubrimiento de peers locales No existe propuesta oficial. Es una forma de
descubrir otros peers que se encuentran en la local area network (LAN) o
red de área local del peer aprovechando su gran ancho de banda y al mismo
tiempo minimizando el tráfico que sale hacia Internet.
Super-seeding (BEP16) Es una técnica que ayuda a los leechers a que se conviertan en seeders más rápidamente. Es útil cuando solo hay un seeder en la
red.
Multitracker (BEP12) Permite el uso de múltiples trackers coordinados (y de
repuesto, en caso de que uno falle).
Actualmente existen dos librerías que son usadas por aplicaciones BitTorrent:
libtorrent es una librería multiplataforma de código abierto escrita en C++.
Su primera publicación fue en 2005. Las características implementadas más
notables son DHT, uTP, Web seeding y PEX. Algunas aplicaciones implementadas con esta librería son Deluge, Tribler, qBittorrent, Free Download
Manager, BitLord, rTorrent y el videojuego World of Tanks.
MonoTorrent es otra librería multiplataforma de código abierto escrita en
C# basada en Mono (una implementación de .NET Framework). Su primera
publicación fue en 2010. Moonson es un aplicación basada en esta librería.
Además, también existen otras aplicaciones con sus propias implementaciones
como BitTorrent(Mainline) (C++), uTorrent (C++), Vuze (Java), Transmission
(C, Objective-C), WebTorrent (JavaScript, WebRTC), Shareaza (C++), Tixati
(C++), BitComet (C++) y MLDonkey (OCaml, C y ensamblador).
Actualmente no existe ninguna implementación BitTorrent en Pharo de la que
se pueda aprovechar su código para el proyecto. Pero sí que pueden ser útiles
otras implementaciones como las mencionadas arriba a la hora de tener referencias
sólidas y fiables sobre cómo diseñar la aplicación.
3.
3.1.
Metodología
Seguimiento
En el desarrollo del software se usará una metodología incremental, de abajo arriba,
partiendo de los sistemas más pequeños e independientes, creando otros sistemas
más grandes y complejos en forma de prototipos. Se iterará este ciclo hasta producir el prototipo final. Como mínimo, se celebrará una reunión con el director
del proyecto tras la finalización de cada prototipo para evaluar el estado de la
22
aplicación. El director del proyecto también podrá supervisar en todo momento el
estado del software a través de http://smalltalkhub.com, un repositorio online
para proyectos Smalltalk, donde se irán actualizando las diferentes versiones de la
aplicación.
3.2.
Estilo de programación
En el desarrollo del software se seguirán los patrones de diseño del libro Smalltalk
Best Practice Patterns[5]. Además se hará énfasis en producir código que cumpla
los siguientes principios generales de programación, que son especialmente útiles
para este proyecto.
Minimalismo Dividir las funcionalidades lo máximo posible para tener métodos
pequeños y altamente reusables.
Código autocomentado Elegir el nombre de las clases, métodos y variables de
tal forma que el código sea fácilmente comprensible sin necesidad de comentarios explícitos.
3.3.
Validación del software
El software será validado mediante pruebas unitarias. Estas pruebas unitarias serán
automatizadas para que las pueda reproducir cualquier usuario fácilmente. Además
se incluirán otras pruebas sobre el funcionamiento general de la aplicación. Las
instrucciones para ejecutar estas pruebas se encuentran en el apartado A.4 de la
adenda.
4.
Visión global de la aplicación
En este apartado se hace un resumen en alto nivel del diseño y el funcionamiento
de la aplicación con el objetivo de dar una visión global introductoria. El método
a utilizar en la explicación va a ser empezar por la clase que representa el sistema
más grande, es decir, la que representa la aplicación en sí, y a continuación explicar
superficialmente los subsistemas que contiene.
La clase principal es BtLocalPeer, que representa la aplicación BitTorrent que
ejecuta el peer local. Puede parecer de entrada que el nombre escogido no es el
apropiado para representar la aplicación, pero como se explica en los objetivos
generales del proyecto, lo que se persigue es representar virtualmente, dentro de
Smalltalk, la naturaleza de las redes BitTorrent. Y esta naturaleza se basa en
objetos (peers) que interaccionan entre sí a través de mensajes, por lo que se ha
23
decidido llevar también este enfoque a una elección de nombres que ayude a ver
estas redes como objetos con entidad propia que se comunican.
Como se pretende que solo pueda existir una sola aplicación al mismo tiempo,
esta clase es de instancia única.
Esta clase contiene una colección de objetos BtTorrent. Un objeto BtTorrent
representa la tarea de descarga y compartición de un torrent cualquiera. La razón
para que BtLocalPeer contenga una colección de torrents es poder realizar operaciones globales sobre todos los torrents, como iniciarlos y detenerlos todos a la
vez, o imponer un límite global de conexiones simultáneas en la aplicación.
Figura 10: Diagrama de clases simplificado de la aplicación
El primero de los elementos que contiene BtTorrent es un objeto BtMetainfo,
que representa los metadatos contenidos en el fichero .torrent, sin el cual no es
posible unirse a la red e intercambiar piezas con otros peers.
El siguiente objeto que contiene es una colección de objetos BtFile. Cada
torrent está compuesto por una serie de ficheros cuyas rutas y tamaños aparecen en
los metadatos. De hecho, el torrent es considerado como un único fichero, resultado
de la concatenación de los ficheros mencionados en el orden en que aparecen en
los metadatos. Esta colección de objetos BtFile lo que permite es acceder a las
piezas de este gran fichero de forma totalmente independiente a los ficheros que lo
componen, sin importar cuantos ficheros haya implicados.
Durante la compartición del torrent también es necesario llevar un control de
las piezas que tiene cada peer del vecindario (tanto del local como de los remotos).
Esta funcionalidad es necesaria porque, de no llevar este control, el peer local no
24
sabría qué piezas pedir a otros peers. De esto se encarga la clase BtBitfield,
que es un campo de bits tan grande como piezas haya en el torrent. Cada objeto
BtTorrent contiene una instancia de esta clase ya que cada torrent es un conjunto
de ficheros distinto.
Este tipo de objetos también los contienen las instancias de la clase
BtRemotePeer, que representa los peers remotos del vecindario, para los que
también es necesario llevar un control de las piezas que tienen. Los objetos
BtRemotePeer representan las conexiones del peer local con los peers remotos.
Se podría haber elegido como nombre para estos objetos «BtConnection» o algo
similar, pero, como en el caso de BtLocalPeer, se ha elegido el nombre que denota
la naturaleza de objetos con los que se puede interaccionar. Cada BtTorrent tiene
su propia colección de peers remotos (o conexiones).
Otro tipo de objetos con los que interacciona el peer local son los trackers,
representados por la clase BtTracker. Por cada torrent, siempre existe como mínimo un tracker que se encarga de introducir y dar a conocer los peers dentro
del enjambre dedicado a un torrent concreto. Así que cada BtTorrent contiene un
BtTracker como mínimo (también existe el concepto de multitracker, ver apartado
5.5.3).
La unidad mínima de información útil del torrent no es la pieza, sino el bloque.
Cuando los peers intercambian información útil (octetos del torrent), no intercambian piezas íntegras, sino que intercambian pequeñas porciones de estas piezas:
bloques. Para no tener que estar accediendo constantemente al disco físico, estos
bloques se guardan temporalmente en la memoria volátil del sistema. Una vez se
dispone de todos los bloques de una pieza, entonces se valida calculando su resumen y se escribe en disco, y se libera la memoria volátil. Cada BtTorrent tiene una
colección de objetos BtTemporaryPiece, donde se guardan los bloques temporales.
Por último, los objetos BtTorrent también contienen una instancia de
BtPiecesOnNetwork. La responsabilidad de esta clase es llevar un control de la
«distribución» de las piezas en el vecindario. Dicho de otra forma, por cada pieza,
cuántas copias hay en el vecindario. Además, también ofrece la posibilidad de ordenar las piezas según varios criterios. La necesidad y utilidad de este sistema se
explica en el apartado 5.12.
En el siguiente apartado se profundiza en el funcionamiento de la aplicación
siguiendo el sentido inverso al que se ha usado en este apartado. Empezando por
los subsistemas más pequeños e independientes, y terminando por los sistemas
más complejos y dependientes hasta llegar a la aplicación en sí. Esta metodología
es, precisamente, la que se ha utilizado en el desarrollo de la aplicación (de abajo
arriba), y también es la más idónea para explicarla con un gran grado de detalle.
25
5.
Diseño e implementación
En este apartado se explica el diseño e implementación de la aplicación BitTorrent.
Aunque el objetivo no es explicar en detalle el protocolo, sí que será necesario profundizar un poco en su funcionamiento en algunas partes para que se comprendan
determinadas decisiones de diseño.
5.1.
Bencoding
Los metadatos del fichero .torrent vienen codificados en un sistema de codificación
especial diseñado para BitTorrent que se llama bencoding. Los tipos de datos que
soporta son cadenas de caracteres, enteros, listas y diccionarios.
Las cadenas de caracteres se codifican en secuencias de octetos de la forma
<longitud-de-la-cadena-en-base-decimal-ASCII>:<cadena>.
Ejemplo: ‘spam’ → 4:spam
Los enteros se codifican en secuencias de octetos de la forma
i<longitud-del-entero-en-base-decimal-ASCII>e.
Ejemplo: 345 → i345e
Las listas se codifican en secuencias
l<dato-codificado><dato-codificado>. . . e.
Ejemplo: [‘spam’,345] → l4:spami345ee
de
octetos
de
la
forma
Los diccionarios se codifican en secuencias de octetos de la forma
d<cadena-de-caracteres-codificada><dato-codificado><. . . ><. . . >e.
Ejemplo: {‘a’ ⇒ 5,‘b’ ⇒ [‘as’,25]} → d1:ai5e1:bl2:asi25eee
Como es necesario que la aplicación pueda leer y escribir estos metadatos, tiene
que implementar dos operaciones: codificar los tipos de datos en una secuencia de
octetos y decodificar una secuencia de octetos en los tipos de datos que corresponda.
Los métodos encargados de llevar a cabo estas operaciones se han añadido a las clases estándar String, Integer, OrderedCollection, Dictionary,
ByteArray y PositionableStream. Para codificar, las clases String, Integer,
OrderedCollection y Dictionary tienen el método bencoded que codifica las
instancias de esas clases en secuencias de octetos. En el siguiente ejemplo las secuencias de octetos resultantes se convierten a cadenas de caracteres (asString)
para que los resultados sean legibles.
26
’hello world’ bencoded asString. −> ’11:hello world’
3567 bencoded asString. −> ’i3567e’
(OrderedCollection with: 3567 with: ’hello world’) bencoded asString. −> ’li3567e11:hello worlde’
d:=Dictionary new.
d at: ’key1’ put: ’value1’.
d bencoded asString. −> ’d4:key16:value1e’
Del mismo modo, la clase ByteArray tiene un método con el nombre bedecoded
que implementa la función inversa.
’hello world’ bencoded bedecoded. −> ’hello world’
3567 bencoded bedecoded. −> 3567
(OrderedCollection with: 3567 with: ’hello world’) bencoded bedecoded. −> an
OrderedCollection(3567 ’hello world’)
d:=Dictionary new.
d at: ’key1’ put: ’value1’.
d bencoded bedecoded. −> a Dictionary(’key1’−>’value1’ )
La clase ByteArray (como todas las subclases de Collection) tiene un incoveniente: si queremos decodificar varios datos seguidos necesitamos devolver, a parte
del dato decodificado, la posición del array donde termina éste para decodificar
el dato siguiente a partir de la siguiente posición. Inicialmente, la decodificación
la hacía ByteArray pero posteriormente se trasladó esta funcionalidad a la clase
PositionableStream (subclase de Stream) para evitar este inconveniente. Las instancias de Stream son wrappers de colecciones que permiten leer los elementos de
la colección subyacente al mismo tiempo que avanza la posición de lectura hasta el
elemento posterior al último leído. Por tanto, el método ByteArray>>bedecoded2
simplemente invoca al método PositionableStream>>bedecoded.
ByteArray>>bedecoded
^self readStream bedecoded
Entonces PositionableStream se encarga de decodificar el tipo de dato que
corresponda:
2
Clase>>metodo es la forma de Pharo para referirse al método metodo de la clase Clase.
27
PositionableStream>>bedecoded
elem := self peek asCharacter.
^ elem = $i
ifTrue: [ self bedecodeInteger ]
ifFalse: [
elem isDigit
ifTrue: [ self bedecodeString ]
ifFalse: [
elem = $l
ifTrue: [ self bedecodeList ]
ifFalse: [
elem = $d
ifTrue: [ self bedecodeDictionary ]
ifFalse: [ self error: ’Unknown bencoded form’ ] ] ] ]
5.2.
Los metadatos
Los metadatos, como ya se ha explicado en el apartado 2.3.1, contienen la información esencial para el funcionamiento de la red BitTorrent. En realidad, los
metadatos son un diccionario «bencodificado» que contiene las siguientes claves:
announce Cadena de caracteres codificada que representa la URL del tracker
info Diccionario codificado que contiene las claves descritas abajo
El diccionario info contiene las siguientes claves:
name Cadena de caracteres que representa el nombre sugerido para el fichero o
directorio compartido.
piece length Entero que especifica el tamaño en octetos de las piezas en que es
partido el torrent. La última pieza puede tener un tamaño diferente.
pieces Concatenación de los resúmenes (o funciones hash) de las piezas.
También hay otra clave dentro de info que puede ser length o files. Es
length cuando el torrent es un solo fichero y su valor es un entero que representa
el tamaño del fichero en octetos. Es files cuando el torrent es una colección de
ficheros y su valor es una lista de diccionarios que contienen las siguientes claves:
length Entero que representa el tamaño del fichero
path Una lista de cadenas de caracteres que representa la ruta del fichero, empezando por el directorio más alto y acabando con el nombre del fichero
28
Cuando el torrent es una colección de ficheros, se trata como un solo fichero
resultado de la concatenación de todos los ficheros en el orden en que aparecen en
files. Cada torrent es identificado de forma única en Internet con el resumen del
diccionario info.
La clase BtMetainfo, subclase de Dictionary, representa los metadatos. Los
metadatos de un torrent son nombrados en la documentación del protocolo como «metainfo», por eso se ha elegido este nombre y de ahora en adelante nos
referiremos a los metadatos así.
Respecto a la metainfo, solo hay dos situaciones posibles en una red BitTorrent:
o bien que el creador de ésta sea el usuario de la aplicación porque quiere compartir
unos ficheros de su sistema; o bien que el usuario quiera descargar unos ficheros
cuya metainfo ha sido creada por otro usuario de la red. En el primer caso, la forma
de generar la metainfo asociada a un fichero o directorio de ficheros del sistema es
la siguiente.
metainfo:= BtMetainfo new.
metainfo buildOn: ’/myFiles/directoryToShare’ asFileReference .
metainfo writeToFile: ’/myFiles/myMetainfo.torrent’ asFileReference.
En cambio, en el segundo caso solo querremos leer el fichero .torrent y decodificar el diccionario que recibe BtMetainfo>>from: como argumento.
metainfoFile:= ’/myFiles/someMetainfo.torrent’ asFileReference .
metainfo:= BtMetainfo from: (metainfoFile readStreamDo:[:x | x binary ; bedecoded]).
5.3.
Acceso físico a los torrents
Los ficheros de un torrent vienen definidos en la metainfo, en ella aparecen sus
rutas y el tamaño que cada uno debe tener al finalizar la descarga. Las rutas de los
ficheros obviamente son relativas, ya que la ruta absoluta dependerá del sistema
de ficheros donde se almacenen. Una aplicación BitTorrent debe tener un sistema
de acceso físico a estos ficheros tanto para escribirlos en disco como para leerlos,
ya que el peer local envía y recibe piezas a otros peers.
El protocolo especifica que los ficheros que componen un torrent deben tratarse
como un sólo fichero resultado de la concatenación de todos ellos en el orden en
que aparecen en la metainfo. Así que es posible que un fichero empiece en una
pieza determinada y termine en otra que se encuentra 4 piezas más allá, como
pasa en el segundo fichero de la figura 11. Aunque también es posible que empiece
y termine en la misma pieza si el fichero es lo suficiente pequeño. Por tanto, el
sistema de acceso físico debe ser capaz de acceder a cualquier porción contigua de
la información sin importar cuantos ficheros haya implicados. Este sistema se ha
implementado en el paquete BitTalk-DataAccess y está compuesto por las clases
29
BtFile y BtFileCollection.
Figura 11: Ejemplo de colección de ficheros de un torrent de 10 piezas y 4 ficheros.
Las piezas tienen un tamaño de 512 KiB menos la última, de 264 KiB.
La clase FileReference es una clase estándar de Pharo que sirve para representar rutas de ficheros. Esta clase dispone del método size que devuelve el
tamaño del fichero en cada momento, pero en una descarga BitTorrent además es
necesario saber su tamaño final esperado para saber en qué posición de la colección
de ficheros hay que leer y escribir cada pieza. Por eso, la clase BtFile representa
las instancias de ficheros que se espera que acaben teniendo un tamaño máximo.
Esta clase contiene dos variables de instancia: reference (apunta a un objeto
FileReference) y finalSize (apunta a un entero). La primera guarda la ruta y
la segunda el tamaño máximo permitido. Este tipo de objetos son precisamente
los que se especifican en la metainfo, puesto que inicialmente estarán vacíos y al
finalizar la descarga habrán alcanzado el tamaño definido en la metainfo.
La clase BtFileCollection (subclase de OrderedCollection) representa las
instancias de colecciones ordenadas de ficheros, que es precisamente lo que son
los ficheros especificados en la metainfo. Las instancias de esta clase tratan sus
elementos como si fueran un solo fichero. Con los métodos readOffset:amount: y
writeOffset:data: es posible escribir y leer cualquier porción de la concatenación
de ficheros dado un offset y, o bien una cantidad de octetos solicitada en caso de
lectura, o bien un ByteArray en caso de escritura. Estos métodos de acceso se
encargan de abrir y cerrar solamente aquellos ficheros que hay que acceder para
leer o escribir la secuencia de octetos solicitada.
Esta clase también dispone del método allSha1SplitEvery: cuyo parámetro es un entero que define cada cuántos octetos de información se calculará el resumen de la colección de ficheros. Por ejemplo, este método lo utiliza
BtMetainfo>>buildOn:pieceLength: para calcular los resúmenes de las piezas
resultantes de un directorio de ficheros a partir del cual quiere construirse su metainfo.
Hay que destacar que las clases BtFile y BtFileCollection no solo sirven
para el protocolo BitTorrent, sino que pueden usarse para cualquier concatenación
de ficheros que deba tratarse como una sola secuencia de octetos, por lo que la clase
BtFileCollection podría ampliarse para que pudiera realizar otras operaciones
que satisficieran otras necesidades a parte de las de BitTorrent.
30
5.4.
Los nodos de la red
Todos los nodos implicados en una red BitTorrent tienen en común que tienen
un dominio (ya sea nombre o dirección IP) y un puerto. La clase encargada de
representar a cualquier nodo de la red es BtNode y tiene como variables de instancia
un BtDomain y un puerto. Pero no todos los nodos son iguales, existen peers
y trackers. Ambos tipos comparten las características de BtNode pero cada uno
tiene sus particularidades. Estas particularidades las representan las clases BtPeer
y BtTracker, ambas subclases de BtNode.
La clase BtPeer tiene dos subclases: BtLocalPeer y BtRemotePeer. El peer
local es una instancia de BtLocalPeer. Los peers remotos que el local puede llegar
a conocer y conectar con ellos son instancias de BtRemotePeer. Esta clasificación
es necesaria porque la instancia (única) de BtLocalPeer representa a la aplicación
BitTorrent del sistema local, y las instancias de BtRemotePeer representan las
conexiones que tiene el peer local con los peers remotos. Se podría haber llamado
a la primera clase «BtApplication» y a la segunda «BtPeerConnection» (o algo
similar), pero la intención es respetar el «espíritu» de la orientación a objetos y
elegir los nombres que ayuden a ver de forma clara que dentro de Smalltalk hay una
serie de objetos interpretando el papel de los nodos de la red y que interaccionan
entre sí.
La clase BtTracker también tiene dos subclases: BtHTTPTracker y
BtUDPTracker. Esta clasificación es necesaria ya que no todos los trackers funcionan de la misma forma, unos trabajan sobre HTTP y otros sobre UDP. Las
instancias de BtHTTPTracker reciben consultas HTTP mientras que con las instancias de BtUDPTracker la interacción es radicalmente distinta. Todas estas diferencias son transparentes para el peer local ya que la forma de comunicarse con los
trackers es invocando los métodos abstractos announce y scrape de BtTracker.
Estos métodos realizan unas operaciones que son comunes de todos los trackers,
independientemente del protocolo que usen.
Este diseño nos permite crear una red BitTorrent virtual dentro Smalltalk que
representa en tiempo real la red física conocida por el peer local. En esta red
virtual los nodos de la red física se representan como objetos de Smalltalk capaces
de interaccionar entre sí. Y todos los mensajes que viajan entre ellos se traducen
en el envío de mensajes entre objetos dentro de Smalltalk.
5.5.
5.5.1.
Descubrimiento de peers
Tracker HTTP
La forma principal de interacción entre los peers y el tracker viene definida en el
BEP3. Esa parte del protocolo especifica que esta comunicación debe efectuarse
31
Figura 12: Jerarquía de clases de los nodos de la red.
mediante consultas HTTP GET. Y detalla qué deben contener estas consultas y
que información debe responder el tracker . Todas las clases implicadas directamente en este protocolo se encuentran en el paquete BitTalk-HTTPTrackerProtocol.
Existen dos tipos de operaciones: announce y scrape. El significado de la operación announce es que el peer local comunica al tracker que quiere conocer cierta
cantidad de peers remotos. Además, el peer local también puede querer informar
de que se ha producido un evento concreto como la finalización de la descarga o el
abandono de la red. Por tanto, existen dos tipos de información en esta operación:
la petición del peer local y la respuesta del tracker .
La petición es una consulta GET en la que se especifica una query string con
una serie de campos y valores que le dan al tracker cierta información sobre el
peer que realiza la consulta. En la query string aparecen, entre otros valores, el
info_hash (el resumen del diccionario info que identifica al torrent), el puerto
que usa el peer local para el tráfico BitTorrent, el número de peers remotos que
desea y los octetos que le quedan por descargar hasta completar el torrent. En la
siguiente URL aparece en negrita una posible query string:
http://tracker.com/announce?port=60513&numwant=50&info_hash=. . .
La clase que representa estos campos y valores es BtHTTPAnnounceQuery y sus
variables de instancia son cada uno de los campos que deben o pueden rellenarse
en una query string. El método asString convierte la instancia en una cadena de
caracteres con la misma forma que la del ejemplo de arriba en negrita.
La información relevante de la respuesta HTTP se encuentra en el cuerpo del
mensaje (tras la línea en blanco) y es un diccionario «bencodificado». En la clave
peers de este diccionario se encuentra una lista de diccionarios cuyas claves son
32
peer id, ip y port. Pero esta forma de expresar peers se ha quedado obsoleta
de facto, puesto que se suele usar por defecto otra forma más compacta para expresarlos (BEP23). Esta mejora consiste en sustituir el diccionario de peers por
una concatenación de grupos de 6 octetos, donde los 4 primeros octetos expresan la dirección IP y los 2 últimos el puerto. Así, el peer local puede intentar
conectar con los peers con esas direcciones. Como esta información es un diccionario, puede representarse con un Dictionary, en concreto con la subclase
BtHTTPAnnounceResponseBody.
Todas las instancias de BtTracker tienen una variable para guardar una petición de announce (announceRequest) y otra para guardar una respuesta de
announce (announceResponse). La clase BtTracker tiene el método abstracto
announce implementado por su subclase BtHTTPTracker:
1
2
3
4
BtHTTPTracker>>announce
httpGET := self newGET: path query: announceRequest asString .
response:= self sendGET: httpGET .
self updateAnnounceResponse: response.
En el código de arriba se pueden ver todos los pasos de la operación announce.
En la línea 2 se forma el mensaje GET a partir de la query string. En la línea 3 el
método sendGET: envía la petición y devuelve la respuesta del tracker . Y en la línea
4 el método updateAnnounceResponse: recupera el diccionario «bencodificado»
del cuerpo de la respuesta, lo decodifica para obtener un BtAnnounceResponseBody
y lo guarda en la variable de instancia announceResponse.
La operación scrape, sin embargo, tiene un significado distinto. En este caso el
peer local no quiere darse a conocer ni conocer otros peers, sino que desea conocer
las estadísticas de ciertos torrents que el tracker conoce. El funcionamiento es el
mismo que el de la operación announce, así que el diseño también es el mismo.
Pero en este caso, la query string solo está formada por los valores info_hash de
los torrents sobre los que el peer local quiere conocer sus estadísticas. Un posible
ejemplo sería este:
http://tracker.com/scrape?info_hash=. . . &info_hash=. . . &info_hash=. . .
La clase que representa este tipo de query string es BtHTTPScrapeQuery,
la cual tiene una variable de instancia que es una colección de info_hash. El
tracker responde un diccionario «bencodificado» con las estadísticas de cada torrent (el nombre, número de seeders, número de leechers y números de veces
que ha registrado descargas completadas). Esta información se representa con
la clase BtHTTPScrapeResponseBody, subclase de Dictionary. Las instancias de
BtTracker también tienen dos variables para guardar la petición (scrapeRequest)
y respuesta (scrapeResponse) de scrape. Del mismo modo que con la operación
announce, la clase BtHTTPTracker implementa el método abstracto scrape:
33
BtHTTPTracker>>scrape
httpGET := self newGET: self asString asZnUrl asZnUrlScrape pathPrintString query:
scrapeRequest asString.
response := self sendGET: httpGET.
self updateScrapeResponse: response
Como se puede ver, esta implementación es análoga a la del método announce.
5.5.2.
Tracker UDP
Aunque el núcleo de BitTorrent (BEP3) especifica solamente el funcionamiento
de los trackers sobre HTTP, la realidad es que una aplicación BitTorrent que
solo implemente este tipo de comunicación no podrá unirse a la mayoría de las
redes presentes en Internet. La gran mayoría de los ficheros .torrent que existen
tienen trackers UDP en su metainfo. Y la tendencia es usar cada vez más UDP en
detrimento de HTTP.
Esta propuesta de ampliación del protocolo (BEP15) se hizo para evitar el
overhead que tiene TCP debido a la necesidad de abrir y cerrar una conexión, y
asegurar el orden y la fiabilidad de la información transferida. Usando el protocolo
HTTP (que usa TCP), en una petición announce con una respuesta de 50 peers, el
número de octetos enviados y recibidos son alrededor de 1206. El protocolo UDP,
en la misma operación, usa 4 paquetes y alrededor de 618 octetos, reduciendo el
tráfico casi un 50 %. Para un peer este ahorro puede ser insignificante, pero para
un tracker que sirve a millones de peers supone una gran diferencia.
En este protocolo existen cuatro tipos de operaciones: connect, announce, scrape
y error. La primera operación que se realiza es la operación connect. En esta
operación el tracker envía un identificador de conexión (connectionID) al peer , el
cual deberá utilizar en todas las operaciones posteriores. Cada vez que el tracker
recibe una petición, comprueba el connectionID y, si no coincide con el que le
suministró, ignora la petición. El peer tiene permitido usar este identificador de
conexión hasta un minuto después de recibirlo y los trackers solo pueden responder
a peticiones con identificadores de conexión de hasta dos minutos de vida. En caso
de que una conexión expire, el peer debe realizar una operación connect de nuevo.
Las operaciones announce y scrape tienen el mismo significado que en el protocolo HTTP y la operación error solo la utiliza el tracker para comunicar al peer
que se ha producido un error y porqué.
UDP no es un protocolo fiable. Esto significa que no retransmite los paquetes
perdidos por sí solo, sino que es la aplicación la responsable de ello. Siguiendo
esta idea, si el peer no ha recibido una respuesta después de 15 · 2n segundos, debe
retransmitir la petición, donde n empieza siendo 0 y se incrementa después de cada
intento hasta llegar a 8 (3840 segundos).
34
Las clases implicadas en este protocolo se encuentran en el paquete
BitTalk-UDPTrackerProtocol. Todas las operaciones se llevan a cabo con el intercambio de paquetes UDP entre el peer y el tracker , y estos paquetes se han
representado en la clase BtUDPPacket. Todos estos paquetes tienen en común que
contienen un identificador único de operación (id) y un número que establece el
código o tipo de operación (actionCode), ambos representados con enteros de 32
bits. Los códigos de operación son: 0 - connect, 1 - announce, 2 - scrape y 3 - error.
La combinación de estos dos números se ha representado en la clase
BtUDPTransaction cuyas variables de instancia son id y actionCode. Y todas
las instancias de BtUDPPacket tienen una variable de instancia transaction que
apunta a un objeto BtUDPTransaction.
Cada operación consta de un paquete de petición (request) enviado por el peer y
de un paquete de respuesta (response) enviado por el tracker (excepto la operación
error, que solo tiene paquete de respuesta).
Todos los paquetes request tienen en común que contienen un número de conexión (connectionID) además de la información de BtUDPPacket por lo que los
paquetes request son representados en la clase BtUDPConnectRequest (subclase de
BtUDPPacket) con una variable de instancia connectionID. Si el paquete request
en cuestión es de la operación connect entonces no contiene ninguna información
más a parte del número de conexión. En cambio, si el paquete es de la operación announce o scrape, tiene cierta información particular. Esta información se representa
en las clases BtUDPAnnounceRequest y BtUDPScrapeRequest, ambas subclases de
BtUDPConnectRequest. La información particular de estos dos últimos tipos de paquetes es la misma que contienen BtHTTPAnnounceQuery y BtHTTPScrapeQuery,
respectivamente.
Por otro lado, se encuentran los paquetes response de cada operación. En este
caso, estos paquetes no comparten información a parte de la de BtUDPPacket
así que simplemente cada tipo de repuesta se representa con una de las clases BtUDPConnectResponse, BtUDPAnnounceResponse, BtUDPScrapeResponse y
BtUDPErrorResponse, todas ellas subclases de BtUDPPacket.
En el caso particular de BtUDPScrapeResponse, la respuesta contiene 3 enteros de 32 bits por cada torrent consultado que informan del número de seeders, el número de veces que el tracker ha registrado el evento de descarga completada y el número de leechers (en este orden). En total, el tracker responde una secuencia de 3n enteros donde n es el número de torrents
consultados. La clase BtUDPScrapeTorrentStat representa la estadística de un
torrent que devuelven los trackers UDP y tiene las variables de instancia
seeders, completed y leechers. La clase BtUDPScrapeResponse, por tanto,
contiene una colección de BtUDPScrapeTorrentStat en la variable de instancia
scrapeTorrentStatCollection.
35
Figura 13: Jerarquía de clases de los paquetes UDP.
Todas las clases explicadas hasta ahora sirven para representar la información de las operaciones como objetos dentro de Smalltalk, pero esta información
viaja por la red como secuencias de octetos, así que es necesario un sistema de
codificación y decodificación que permita transformar estos objetos en secuencias
de octetos y viceversa. En primer lugar, la clase BtUDPPacket, todas sus subclases y las clases BtUDPTransaction y BtUDPScrapeTorrentStat tienen el método
asByteArray que convierte la información que representan en una secuencia de
octetos tal y como especifica el protocolo. En el siguiente trozo de código se puede
ver la implementación del método asByteArray en la clase BtUDPTransaction.
BtUDPTransaction>>asByteArray
^(actionCode asByteArrayOfSize: 4) , (id asByteArrayOfSize: 4)
Este método lo invoca a su vez el método BtUDPPacket>>asByteArray ya que
todos los paquetes UDP contienen como mínimo la información de una operación
(transaction).
BtUDPPacket>>asByteArray
^transaction asByteArray
En el siguiente trozo de código se puede ver la implementación de
BtUDPConnectRequest>>asByteArray. Como todos los paquetes request comparten la información de BtUDPPacket, este método invoca al método asByteArray
de la superclase y concatena el resultado con la información específica de este tipo
de paquetes en el orden que establece el protocolo.
BtUDPConnectRequest>>asByteArray
^ (connectionID asByteArrayOfSize: 8) , (super asByteArray)
Análogamente, el método BtUDPAnnounceRequest>>asByteArray invoca al
método de la superclase BtUDPConnectRequest porque todas las peticiones de
announce comparten la información de BtUDPConnectRequest. Una vez tiene la
36
información de la superclase en forma de octetos, los concatena con la información
específica de este tipo de paquetes en el orden que determina el protocolo.
BtUDPAnnounceRequest>>asByteArray
^ super asByteArray ,
( infoHash asByteArrayOfSize: 20) ,
( peerID asByteArrayOfSize: 20) ,
[...]
( numWant asByteArrayOfSize: 4) ,
( node port asPortNumber ) .
Las implementaciones de asByteArray de los demás tipos de paquetes que no
aparecen aquí siguen el mismo diseño.
Hemos visto que las implementaciones de asByteArray convierten la
información de los paquetes UDP en secuencias de octetos, pero también
es necesaria la función inversa: convertir las secuencias de octetos procedentes del tracker en instancias de las clases BtUDPConnectResponse,
BtUDPAnnounceResponse, BtUDPScrapeResponse o BtUDPErrorResponse.
El diseño de esta funcionalidad es idéntico al del bencoding (ver
apartado 5.1); el método ByteArray>>decodeUDPResponse invoca a
PositionableStream>>decodeUDPResponse,
ByteArray>>decodeUDPResponse
^self readStream decodeUDPResponse
que se encarga de decodificar el tipo de respuesta en función del código de operación:
PositionableStream>>decodeUDPResponse
actionCode:= (self next: 4) asInteger.
4 timesRepeat: [ self back ].
actionCode = (BtUDPTransaction announceActionCode)
ifTrue:[^self decodeUDPAnnounceResponse].
actionCode = (BtUDPTransaction connectActionCode)
ifTrue:[^self decodeUDPConnectResponse].
actionCode = (BtUDPTransaction scrapeActionCode)
ifTrue:[^self decodeUDPScrapeResponse].
actionCode = (BtUDPTransaction errorActionCode)
ifTrue:[^self decodeUDPErrorResponse].
La clase BtUDPTracker representa la «conexión UDP» del peer con el tracker .
Aunque también puede interpretarse como la representación del tracker UDP físico
dentro de Smalltalk, con el que el peer local puede interaccionar a través de los
métodos announce y scrape. Los objetos principales que contiene una instancia
de BtUDPTracker son una instancia de la clase Socket en la variable de instancia
socket y una instancia de BtUDPTransactionQueue en la variable de instancia
transactionQueue.
37
La variable socket se inicializa como un socket UDP que el peer local
puede utilizar para enviar y recibir paquetes con los métodos sendPacket: y
receivePacket.
BtUDPTracker>>sendPacket: aPacket
socket sendData: aPacket asByteArray toHost: domain address asIPv4 port: port.
BtUDPTracker>>receivePacket
packetSize := 0.
bufferIn := ByteArray new: 2000.
packetSize := socket receiveDataInto: bufferIn fromHost: domain address asIPv4 port: port ].
packet := (bufferIn copyFrom: 1 to: packetSize) decodeUDPResponse.
^ packet
La clase BtUDPTransactionQueue es una cola (subclase de SharedQueue) en
cuyas instancias el peer puede introducir las operaciones pendientes de realizar.
Como se ha dicho más arriba la instancia del tracker UDP contiene el objeto
transactionQueue, este objeto es necesario puesto que en un momento determinado el peer puede querer realizar más de una operación. Por ejemplo, puede
querer realizar una operación connect, una operación announce y una operación
scrape.
El objeto BtUDPTracker es el encargado de ir accediendo a la cola para realizar
las operaciones pendientes en el orden que se introdujeron. Este tipo de cola tiene
la particularidad que cada vez que se añade una operación connect, ésta pasa a la
primera posición ya que mantener una conexión viva es más prioritario que realizar
cualquier otra operación puesto que sin conexión el tracker no servirá ninguna petición excepto la de connect. Así que siempre que hay una operación connect en la
cola, se encuentra en la primera posición. Para conseguir este funcionamiento específico, BtUDPTransactionQueue, en su inicialización, sustituye la colección subyacente (instancia de OrderedCollection) por una instancia de SortedCollection
y usa la función de comparación BtUDPTransaction>>priorOrEqualTo: como
criterio de ordenación que asegura el orden deseado, como se puede ver en los
siguientes trozos de código.
BtUDPTransactionQueue>>initialize
super initialize.
items := SortedCollection sortBlock: [ :a :b | a priorOrEqualTo: b ]
BtUDPTransaction>>priorOrEqualTo: anotherTransaction
^actionCode = ConnectActionCode or:[ anotherTransaction actionCode ~= ConnectActionCode]
Además, el objeto BtUDPTracker se encarga de eliminar de la cola la operación
pendiente que se satisface cada vez que llega una respuesta. Para ello, decodifica el
mensaje entrante para recuperar el objeto BtUDPTransaction que contiene y así
38
poder encontrarlo en la cola.
Para que la comunicación entre el peer y el tracker funcione, el objeto
BtUDPTracker necesita enviar los paquetes pendientes y recibir las respuestas
constantemente, además de controlar si hay que restablecer la conexión o si ha
pasado el tiempo suficiente antes de volver a enviar una petición. Para ello, inicia dos procesos independientes que solo terminan cuando se invoca el método
BtUDPTracker>>terminate. El contenido del bloque de ejecución del proceso de
envío de paquetes (definido en la variable sending3 ) es el siguiente.
[ self secondsToNextAttempt <= 0
ifTrue: [ self dequeueAndSendNextPacket ]
ifFalse: [ self waitForNextAttempt ] ] repeat
Se puede observar que solo envía peticiones si ha pasado el tiempo suficiente
(secondsToNextAttempt <= 0) y espera hasta que se pueda enviar la siguiente
petición pendiente. En caso de poder enviar otra petición, el proceso invoca a
dequeueAndSendNextPacket.
BtUDPTracker>>dequeueAndSendNextPacket
self isConnectionAlive
ifFalse: [ self reestablishConnection ].
packet := self dequeueNextRequest.
packet ifNotNil: [ self sendPacket: packet ]
Este método, antes de enviar cualquier petición, comprueba que la conexión no
haya expirado. En caso de que haya expirado, añade una nueva petición de connect
a la cola invocando el método reestablishConnection y el criterio de ordenación
asegura que será la primera petición en enviarse aunque se hayan añadido otras
previamente.
El proceso de recibir paquetes (definido en la variable receiving) invoca el
método receiveAndHandleNextPacket constantemente, como se puede ver en su
bloque de ejecución.
[ self receiveAndHandleNextPacket ] repeat
BtUDPTracker>>receiveAndHandleNextPacket
self handleReceivedPacket: self receivePacket
Este último método invoca a receivePacket, que no devuelve un paquete hasta que no haya recibido uno. A continuación se invoca el método
handleReceivedPacket: con el paquete entrante como argumento y éste actualiza el estado de la conexión en función del tipo del paquete y la información que
contiene.
3
En Smalltalk todo son objetos, incluso los procesos
39
5.5.3.
La clase BtMultitracker
La mayor debilidad de las redes BitTorrent es el tracker . Si el tracker deja de funcionar ya sea por error o por acciones malintencionadas, la red entera desaparece.
El BEP12 es una propuesta de ampliación que intenta paliar esta vulnerabilidad.
Esta medida consiste en que varios trackers puedan servir a la misma red
coordinadamente de forma que si un tracker no puede responder, otro tracker
pueda seguir realizando la misma función.
Es bastante común la situación en que los trackers solitarios de los ficheros
.torrent que incluyen múltiples trackers adicionales no sean accesibles. Así que
una aplicación BitTorrent que no implemente esta ampliación tendrá bastantes
dificultades para unirse a muchos enjambres, puesto que la mayoría de ficheros
.torrent incluyen la opción multitracker.
Para que esta mejora funcione, primero hay que modificar el diccionario metainfo para que los peers sepan las URL de los trackers adicionales que pueden consultar. Esta información aparece en una nueva clave del diccionario (al mismo nivel
de la clave announce) con el nombre announce-list y apunta a una lista de listas
de URL. A estas listas se les da el nombre de tiers (capas o niveles en castellano).
Más adelante se explica el porqué de estos tiers. Si la aplicación es compatible con
esta funcionalidad y esta nueva clave se encuentra en la metainfo, entonces ignora
la clave announce y solo usa las URL apuntadas por announce-list.
Los tiers de trackers son usados secuencialmente; todas las URL de un tier
deben ser comprobadas antes de usar el siguiente tier. Dentro de cada tier, las URL
se comprueban en orden aleatorio. Por lo que es responsabilidad de la aplicación
mezclarlas aleatoriamente antes de usar el tier por primera vez. Si la aplicación
conecta satisfactoriamente con un tracker , su URL se mueve a la primera posición
del tier. A continuación se muestran unos ejemplos de valores de announce-list
y cómo la aplicación debe usarlos. En estos ejemplos, se supone que la aplicación
ya ha mezclado todas las URL dentro cada tier.
d[‘announce-list’]=[ [tracker1], [backup1], [backup2] ]
En este caso, primero se prueba tracker1. Si no responde, se intenta con backup1. Si
tampoco responde, se intenta con backup2. Y se sigue probando todos los tiers en
el orden que aparecen mientras no responda ningún tracker . Este caso se da cuando
los trackers son estándar y no pueden coordinarse para compartir información.
d[‘announce-list’]=[[tracker1, tracker2, tracker3]]
En este caso, primero se intenta conectar con tracker1. Si no se puede, se intenta
con tracker2. Si éste sí responde, entonces la lista pasa a ser [tracker2, tracker1,
tracker3]. Y este es el nuevo orden en que la aplicación procesa la lista en el
40
próximo intento. Si más adelante ni tracker2 ni tracker1 responden pero tracker3
sí, entonces la lista pasa a ser [tracker3, tracker2, tracker1] y será procesada en este
orden en el próximo intento. Cuando hay varios trackers dentro de un tier, como en
este ejemplo, significa que son capaces de coordinarse para compartir información.
Además, el hecho de mezclarlos ayuda a balancear la carga de trabajo.
d[‘announce-list’]=[[tracker1, tracker2], [backup1,backup2]]
En este caso, la aplicación intenta acceder a los trackers del primer tier como en
el ejemplo anterior. Si todos los trackers fallan, entonces pasa a probar el siguiente
tier y lo reordena de la misma forma que en el ejemplo anterior si es necesario.
Esta estructura de trackers es representada con la clase BtMultitracker.
Esta clase encapsula una OrderedCollection que contiene los tiers (también
OrderedCollection’s). El método shuffle mezcla los elementos de todos los
tiers; el método selectNext selecciona el siguiente tracker del tier actual o selecciona el primero del siguiente si el tracker es el último del tier actual; el método
moveCurrentToFirst mueve el tracker actual a la primera posición del tier donde
se encuentra; los métodos selectFirst y selectLast devuelven el primer tracker
del primer tier y el último tracker del último tier, respectivamente; y el método
current devuelve el tracker seleccionado en el instante de invocarlo.
Con estos métodos, la aplicación ya dispone de una interfaz para manipular la
estructura de los multitracker.
5.6.
Control de las piezas
En el enjambre, el peer local debe mantener el control de las piezas que tiene y las
piezas que tienen los peers remotos conocidos. Tener esta información es esencial
para saber qué piezas pedir a otros peers y al mismo tiempo poder comunicar a
los demás peers qué piezas tiene para que se las puedan pedir.
La clase encargada de llevar este control es BtBitfield. Esta clase encapsula
un ByteArray en el que el bit de mayor peso del primer octeto controla si el peer
en cuestión tiene la primera pieza (con índice 0), mientras que el bit de menor peso
del último octeto hace lo mismo con la última pieza. Además dispone de todos los
métodos necesarios para consultar o modificar cualquier bit.
El método BtBitfield>>interestingBitfield: recibe como argumento otro
BtBitfield y responde otra instancia de la misma clase pero solo con los bits a 1
si el receptor del mensaje los tenía a 0 y el argumento a 1. En otras palabras, el
receptor responde un BtBitfield indicando las piezas que le faltan y el argumento
sí tiene. Esto se consigue aplicando la función booleana (a ⊕ b) ∧ b bit a bit a cada
octeto, lo que en Pharo se expresa así:
41
BtBitfield>>interestingBitfield:
[...]
newByte := (byte bitXor: anotherByte) bitAnd: anotherByte.
[...]
^ newBitfield
Con este método es posible saber qué piezas pedir a cada peer . Por cada torrent
que gestiona, el peer local tiene
un objeto BtBitfield para controlar qué piezas tiene,
y una colección de peers remotos que contienen un objeto BtBitfield cada
uno de ellos, que pueden ser accedidos para actualizarlos.
De esta forma el peer local puede mantener un control de sus piezas y las de los
vecinos.
5.7.
Comunicación peer-to-peer
La comunicación P2P es la parte del protocolo que se produce a partir del momento
en que un peer ha conseguido la dirección IP y puerto de otro peer e intenta establecer una conexión con la intención de intercambiar piezas. El núcleo de BitTorrent
(BEP3) especifica la forma estándar en que se producen estas conexiones, que es
sobre TCP. Estas conexiones son simétricas, es decir, el formato de los mensajes
es independiente del sentido en el que viajan. Esto es así porque a ambos extremos
de la conexión se encuentran dos nodos con roles y capacidades equivalentes.
Existen dos conceptos que determinan el estado de una conexión entre dos
peers. Estos conceptos son nombrados en el protocolo como choked y interested.
La traducción literal de choked es «obstruido», pero de ahora en adelante nos
referiremos a este concepto como bloqueado o no aceptado. Cuando un peer A
bloquea a otro peer B significa que A no enviará ninguna pieza a B mientras no lo
desbloquee. La traducción de interested es «interesado». Cuando un peer A está
interesado en otro peer B significa que B tiene piezas que A no tiene.
Por cada conexión, el peer local debe guardar ambos estados para los dos
extremos de la conexión. Es decir, si el peer local bloquea al peer remoto y si es
interesante; y viceversa. Las conexiones empiezan por defecto con el peer remoto
bloqueado y siendo no interesante. En el apartado 5.9 se explica qué papel juega
el bloqueo en una red BitTorrent.
5.7.1.
Mensajes
El mecanismo para establecer una conexión P2P en BitTorrent es mediante un
handshake entre los dos extremos. Primero, el peer que quiere iniciar la conexión
42
envía un mensaje que consta de la siguiente información.
Un octeto con el valor 19 en decimal, indicando la longitud de la cadena de
caracteres que da nombre al protocolo.
La cadena «BitTorrent protocol», de la longitud indicada en el primer octeto.
Ocho octetos reservados para posibles ampliaciones del protocolo.
El valor info_hash (20 octetos) que identifica al torrent de forma única.
El peer_id (20 octetos) que el peer envía al tracker en la operación announce.
Entonces, el peer receptor del mensaje envía el mismo mensaje pero cambiando
el valor peer_id con el suyo propio. Los peers deben comprobar que la información de los mensajes que reciben en el handshake es correcta y que el info_hash
corresponde con alguno de los torrents que está compartiendo. En caso contrario,
deben cerrar la conexión. Suponiendo que el handshake ha tenido éxito, a partir
de ese momento la conexión P2P ha sido establecida y los peers pueden empezar a
compartir sus piezas. Estas piezas son nombradas por su índice (la primera pieza
tiene el índice 0).
Hasta ahora solo se ha mencionado que los torrents se parten en piezas de
igual tamaño y que éstas se transfieren entre los peers de la red. Pero eso es una
simplificación del funcionamiento de BitTorrent. En realidad, la unidad mínima
de información útil es el bloque. Si entendemos una pieza como una secuencia de
octetos, un bloque es una parte contigua de tamaño variable de una pieza.
Durante la vida de la conexión P2P, los peers están autorizados a enviarse una
serie de mensajes, todos ellos con la siguiente forma.
<longitud del mensaje>[<tipo del mensaje>[<información útil>]]
La primera parte son 4 octetos que indican la longitud total del mensaje (sin incluir
esta parte) en decimal; el tipo del mensaje (decimal) es 1 octeto; y por último, la
información útil es una secuencia de octetos de tamaño variable (en función del
tipo de mensaje). A continuación se detalla el contenido y significado de cada tipo
de mensaje.
keep alive: <0> Se envía para mantener la conexión viva si ningún otro mensaje
ha sido enviado durante 2 minutos.
choke: <1><0> El emisor comunica al receptor que lo ha bloqueado, es decir,
que no le servirá ninguna petición de bloque pendiente o futura.
43
unchoke: <1><1> El emisor comunica al receptor que lo ha desbloqueado y
que responderá las peticiones de bloques.
interested: <1><2> El emisor comunica al receptor que está interesado en
alguna de sus piezas.
not interested: <1><3> El emisor comunica al receptor que no está interesado en ninguna de sus piezas.
have: <5><4><índice de pieza> El índice de pieza son 4 octetos en decimal. El emisor comunica al receptor que tiene una pieza determinada. Este
mensaje lo envía el peer local a todos los peers conocidos cada vez que consigue descargar una pieza nueva.
bitfield: <1+X><5><bitfield> El emisor envía al receptor una secuencia de
octetos de tamaño X que representa un campo de bits indicando qué piezas
tiene (bit a 1) y cuales no (bit a 0). Leyendo los bits de izquierda a derecha,
el primer octeto corresponde a las piezas 0-7, el segundo corresponde a la
piezas 8-15. . . Los bits del final que no se usan se ponen a 0. Este es el primer
mensaje que se envía en la conexión P2P. Los peers sin ninguna pieza pueden
obviar el envío de este mensaje.
request: <13><6><índice de pieza><comienzo><longitud> Cada uno
de los tres campos que aparecen después del tipo del mensaje son 4 octetos
en decimal. Estos campos describen un bloque que se encuentra en una pieza
determinada, comienza en un octeto dentro de ésta y tiene una determinada
longitud. Este mensaje sirve para pedir un bloque al peer receptor.
piece: <9+X><7><índice de pieza><comienzo><bloque> Cada uno de
los dos primeros campos que aparecen después del tipo del mensaje son 4
octetos en decimal e indican la posición de un bloque dentro de una pieza. El
tercer campo contiene el bloque, que es una secuencia de octetos de longitud
X. Este mensaje sirve para enviar un bloque previamente solicitado por un
peer remoto.
cancel: <13><8><índice de pieza><comienzo><longitud> Cada uno de
los tres campos que aparecen después del tipo del mensaje son 4 octetos en
decimal y describen un bloque. Este mensaje normalmente se envía durante
la fase End Game para cancelar peticiones duplicadas. En el apartado 5.11
se explica en qué consiste esa fase.
Como en otras partes de la implementación, es necesario un conjunto de clases
que representen estos mensajes dentro de Smalltalk como objetos y un sistema
44
para, por un lado, codificar estos objetos en secuencias de octetos para ser enviadas
a la red y, por otro lado, decodificar las secuencias de octetos que contienen estos
mensajes obteniendo los objetos que los representan. Todas las clases implicadas
directamente en la comunicación P2P se encuentran en el paquete BitTalk-P2P.
Los mensajes intercambiados durante el handshake son distintos a los mensajes
enviados después ya que no comparten forma. Así que la clase BtHandshakeMessage
representa los mensajes del handshake y la clase BtMessage representa a todos los
mensajes enviados después del handshake, que sí comparten forma.
La clase BtHandshakeMessage tiene como variables cada una de las partes que
se envían en el mensaje del handshake como se puede ver en su definición.
Object subclass: #BtHandshakeMessage
instanceVariableNames: ’protocolIdentifier infoHash peerID protocolBehaviour’
classVariableNames: ’ProtocolIdentifier’
category: ’BitTalk−P2P’
Y el método BtHandshakeMessage>>asByteArray codifica sus instancias
en secuencias de octetos listas para ser enviadas a la red. Con el método
ByteArray>>decodeHandshake se realiza la operación inversa. Como pasa con
el bencoding y los paquetes de la comunicación con los trackers UDP, este método
invoca a PositionableStream>>decodeHandshake.
La clase BtMessage tiene las variables de instancia type, que representa el tipo
del mensaje, y payload, que representa la información útil. No tiene ninguna variable de instancia para guardar la longitud del mensaje ya que ésta depende de las
otras dos, y es calculada y codificada en el método BtMessage>>asByteArray. Este método codifica cada una de las partes que componen un mensaje en secuencias
de octetos como se ve en su implementación.
BtMessage>>asByteArray
| bytesPayload length message |
length := type ifNil: [ 0 ] ifNotNil: [ 1 ].
bytesPayload := payload ifNil: [ ByteArray new ] ifNotNil: [ payload asByteArray ].
length := length + bytesPayload size.
message := OrderedCollection new.
message addAll: (length asByteArrayOfSize: 4).
type ifNotNil: [ message add: type ].
message addAll: bytesPayload.
^ message asByteArray
La última parte, el payload o información útil, es representada por la clase
BtMessagePayload y tiene una subclase por cada tipo de payload que puede enviarse. Esta clase tiene el método abstracto asByteArray ya que la forma de codificar la
información del payload depende de su tipo. Estos tipos son BtBitfieldPayload
(para el mensaje bitfield ), BtBlockDescriptionPayload (para los mensajes request y cancel ), BtBlockPayload (para el mensaje piece) y BtHavePayload (para
45
el mensaje have). Cada tipo de payload tiene sus propias variables de instancia
porque la información que contienen es distinta. Por tanto, cada tipo tiene su propia implementación de asByteArray. En los siguientes trozos de código podemos
ver las implementaciones de BtBlockDescriptionPayload y BtBlockPayload.
BtBlockDescriptionPayload>>asByteArray
| bytes |
bytes := OrderedCollection new.
bytes
addAll: (index asByteArrayOfSize: 4);
addAll: (begin asByteArrayOfSize: 4);
addAll: (length asByteArrayOfSize: 4).
^ bytes asByteArray
BtBlockPayload>>asByteArray
| bytes |
bytes := OrderedCollection new.
bytes
addAll: (index asByteArrayOfSize: 4);
addAll: (begin asByteArrayOfSize: 4);
addAll: block.
^ bytes asByteArray
El proceso inverso (decodificar secuencias de octetos en instancias de
BtMessage) lo hace el método ByteArray>>decodeMessage. Como en los
anteriores sistemas de codificación mencionados, este método invoca a
PositionableStream>>decodeMessage.
5.7.2.
Sistema de colas
En cada conexión P2P es necesaria una estructura de cola para almacenar los objetos BtMessage pendientes de enviar y otra para almacenar los objetos BtMessage
entrantes procedentes del peer remoto. La clase BtMessageQueue, subclase de
SharedQueue, cumple esta función. Se ha decidido heredar las propiedades de
SharedQueue porque estas colas son accedidas por más de un proceso a la vez y
este tipo de cola garantiza exclusión mutua. Además, el uso de estas colas permite
no tener que enviar directamente los mensajes request por el socket que gestiona
la conexión P2P. Esto es importante, por ejemplo, cuando el peer local recibe un
mensaje choke. En tal caso, todos los mensajes request de la cola de salida pueden ser descartados ya que no serán respondidos. Sucede lo mismo a la inversa,
cuando el peer local envía el mensaje choke a un peer remoto. El primero debe
descartar todos los mensajes piece salientes pendientes de enviar, así como todos
los mensajes request entrantes porque no los va a responder. Sin el uso de estas
colas, no se podría evitar el envío de estos mensajes innecesarios. Aún así, no se
46
puede impedir completamente el envío de mensajes innecesarios a la red, ya que
siempre es posible que algunos mensajes ya hayan sido extraídos y tratados antes de poder impedirlo. Los métodos encargados de descartar estos mensajes son
discardAllPieces y discardAllRequests.
Estas colas también disponen de una serie de optimizaciones implementadas
en el método BtMessageQueue>>nextPut: que se explican a continuación.
Cada vez que se añade un mensaje choke/unchoke o interested /not interested,
todos los mensajes de estos tipos son eliminados de la cola, respectivamente. Esto
es posible hacerlo porque en una sucesión de estos tipos de mensajes, el último en
llegar anula a los anteriores puesto que representa el último estado deseado por el
peer emisor.
Cada vez que se añade un mensaje cancel, elimina todos los requests de la cola
que concuerdan con la descripción de bloque del cancel, puesto que éste los anula
y ya no es necesario tratarlos.
Cada vez que se añade un mensaje not interested, elimina todos los requests
porque el emisor de estos mensajes ya no quiere los bloques descritos por esos
requests.
5.7.3.
La clase BtRemotePeer
La clase BtRemotePeer representa las conexiones P2P. Aunque una conexión P2P
dentro de Smalltalk también puede verse como el objeto que representa al peer
remoto en el otro extremo de la conexión y con el cual se puede interaccionar
enviando y recibiendo mensajes. Las principales variables de esta clase son las
siguientes.
socketStream. Un objeto SocketStream sobre TCP para enviar y recibir
los mensajes P2P.
bitfield. Un objeto BtBitfield para llevar el control de las piezas del peer
remoto.
unfulfilledOutgoingRequests. Una OrderedCollection que contiene las
peticiones pendientes de respuesta del peer local hacia el peer remoto. Esta
colección es necesaria para no duplicar peticiones en la red. Los elementos de
unfulfilledOutgoingRequests son instancias de la clase BtBlockRequest,
que representa las peticiones de bloque. Sus variables de instancia son
pieceIndex, offset y length (la información que describe un bloque).
inMessageQueue. Un objeto BtMessageQueue para guardar los mensajes entrantes pendientes de tratar.
47
outMessageQueue. Un objeto BtMessageQueue para guardar los mensajes
salientes pendientes de enviar.
Como ya se ha dicho anteriormente, el peer local puede bloquear/desbloquear
y poner como interesante/no interesante al peer remoto de cada conexión; y el peer
remoto hace lo mismo con el peer local. Estos estados se controlan con las variables
de instancia isAccepted y isInteresting para los estados que determina el peer
local, y acceptsMe y isInterested para los estados que determina el peer remoto.
Las instancias de BtRemotePeer tienen dos procesos que se ejecutan indefinidamente mientras la conexión está viva. Estos procesos son el de enviar los mensajes
(variable sending) y el de recibirlos (variable receiving). El proceso de enviar
mensajes invoca el método dequeueAndSendNextMessage.
BtRemotePeer>>dequeueAndSendNextMessage
self ensureKeepAlive.
nextMessageToSend := outMessageQueue nextOrNil.
nextMessageToSend
ifNotNil: [
socketStream nextPutAllFlush: nextMessageToSend asByteArray.
peerLock critical: [ lastMessageSentTime := Time millisecondClockValue ] ]
Este método primero añade el mensaje keep alive a la cola de salida si no
se ha enviado ningún mensaje en los últimos 2 minutos. Para saber si esto es
así, siempre se actualiza el tiempo del último mensaje enviado en la variable
lastMessageSentTime. A continuación, extrae el siguiente mensaje pendiente de
la cola de salida outMessageQueue y lo envía por el socketStream codificado en
una secuencia de octetos (con el método asByteArray).
El proceso de recibir mensajes invoca el método receiveAndEnqueueNextMessage.
BtRemotePeer>>receiveAndEnqueueNextMessage
messageReceived := [ self receiveNextMessage ]
on: ConnectionTimedOut , BtMessageError
do: [ :ex | ex return: nil ].
messageReceived ifNotNil: [ inMessageQueue nextPut: messageReceived ]
Primero recibe el siguiente mensaje entrante del socketStream con el método receiveNextMessage y seguidamente lo introduce en la cola de entrada
inMessageQueue. El método receiveNextMessage, recibe los octetos del siguiente
mensaje por el socketStream e invoca al método ByteArray>>decodeMessage,
que es el encargado de decodificar los octetos en una instancia de BtMessage. Y
ese es el objeto que devuelve, como puede verse en su implementación.
48
BtRemotePeer>>receiveNextMessage
| length stream prefix |
prefix := self receiveNext: 4.
stream := WriteStream with: prefix.
length := prefix asInteger.
length > 0
ifTrue: [ stream nextPutAll: (self receiveNext: length) ].
^ stream contents decodeMessage
Adicionalmente, los objetos BtRemotePeer también tienen las variables de instancia downloaded y uploaded que sirven para actualizar la cantidad de octetos
descargados y subidos por el peer remoto, respectivamente. Estas variables son
importantes, como se verá más adelante, en el algoritmo de bloqueo.
5.8.
Piezas temporales
Como se ha dicho anteriormente, la unidad mínima de información útil que se
envía es el bloque. El tamaño de bloque que la mayoría de implementaciones usan
es 16 KiB y también es el que se usa por defecto en esta implementación.
Cuando el peer local recibe un bloque tiene que decidir qué hacer con él: o bien
lo escribe directamente en la parte del disco que le corresponde, o bien lo conserva
en la memoria volátil del sistema hasta que tenga todos los bloques que forman
una pieza, y entonces escribe la pieza en disco y borra los bloques de la memoria
volátil.
En esta implementación se ha decidido conservar los bloques en la memoria
volátil por una cuestión de eficiencia. En Pharo-Smalltalk, como en otros sistemas
y lenguajes, es mucho más costoso realizar muchas escrituras de poca información
que realizar una única escritura de toda la información a la vez.
La clase encargada de guardar los bloques antes de escribirlos en disco es
BtTemporaryPiece. Las instancias de esta clase contienen una colección ordenada
cuyos elementos son bloques (instancias de ByteArray). Y su tamaño es el cociente
entre el tamaño de la pieza que representa y el tamaño de los bloques que esté
usando el peer local para el torrent que está descargando. La forma de acceder
a los bloques es mediante los métodos at: y at:put:. El primer parámetro en
ambos casos es el offset que ocupa dentro de la pieza; en el caso de escritura, el
segundo parámetro son los octetos a escribir.
Además, también es necesario saber si los bloques han sido pedidos a otro peer
para no pedirlo por duplicado. El estado de los bloques se controla con los métodos
requestedAt:, setRequest: y clearRequest:, los cuales reciben un offset.
49
5.9.
Algoritmo de bloqueo
Durante la compartición de torrent, el peer local está recibiendo constantemente
múltiples peticiones de bloque procedentes de los peers de su vecindario. El peer
local tiene que poner un límite al número peers a los que envía bloques. Es decir,
tiene que decidir qué peers va a bloquear y cuales no. Hay varios motivos para
hacerlo. El primero es que el control de congestión de TCP se comporta bastante
mal cuando se envían (o se suben) mensajes por muchas conexiones a la vez. El
segundo es que es necesario establecer una política de reciprocidad u «ojo por
ojo, diente por diente» para evitar que peers egoístas (o free riders), aquellos que
descargan de otros peers pero no corresponden compartiendo sus recursos, puedan
aprovecharse de los demás. Por defecto, el protocolo establece que el peer local
sirva a los 4 peers remotos más generosos, es decir, aquellos peers de los que ha
conseguido descargar más bloques. Esta decisión es revaluada cada 10 segundos
para evitar lo que se conoce como fibrillation, lo que pasa cuando un peer entra
en un estado en el que es bloqueado y desbloqueado sucesivamente con tanta
frecuencia que el enjambre pierde eficiencia. Cuando el peer local ha descargado el
torrent completamente ya no tiene nada que descargar, así que ya no puede decidir
en función de la generosidad de los demás peers. En este caso el peer local debe
desbloquear los 4 peers a los que más bloques ha enviado.
Este algoritmo de bloqueo supone un problema para los peers que acaban de
unirse a la red y no tienen piezas, puesto que nunca serán desbloqueados por
otros peers debido a que son considerados los más egoístas (no han compartido
nada todavía). Para solucionarlo, el protocolo establece una algoritmo adicional
para desbloquear de forma optimista (optimistic unchoking). En todo momento, el
peer local tiene desbloqueado a un peer remoto aleatorio independientemente de
lo generoso que sea, y esta decisión se toma cada 30 segundos. Tiempo suficiente
para que el peer nuevo consiga descargar una pieza que pueda compartir con
otros peers, y así poder ser considerado menos egoísta, y por tanto, desbloqueado.
Con este algoritmo los peers nuevos tienen tres veces más de posibilidades de
ser desbloqueados de forma optimista que los demás. Además, este desbloqueo
optimista ofrece al peer local la posibilidad de encontrar nuevas conexiones con
mejores tasa de descarga que las desbloqueadas por el algoritmo de bloqueo que
se basa en la reciprocidad.
Teóricamente, se puede dar el caso que un peer esté bloqueado por todos sus
vecinos, incluso por aquellos a los que está subiendo información si el ancho de
banda de subida no es suficientemente grande como para ser elegido entre los 4
primeros por alguno de sus vecinos. En este caso, el peer local lo que debe hacer
es, o bien buscar nuevos peers, o bien reducir el número de peers desbloqueados.
BitTorrent usa una medida «antibloqueo» (o anti-snubbing) que consiste en bloquear a todos los peers remotos de los que no se ha descargado ningún bloque en
50
el último minuto.
El algoritmo de bloqueo de BitTorrent intenta alcanzar la eficiencia u óptimo
de Pareto[3].
La implementación de este algoritmo de bloqueo se encuentra en la clase
BtTorrent, que se explica en el apartado 5.14.
5.10.
Algoritmo de selección de piezas
Una vez que el peer local ha establecido conexiones con sus vecinos, necesita determinar en qué orden va a descargar las piezas disponibles (a cuales les da más
prioridad) en la parte del enjambre que conoce. A continuación se explican las
diferentes políticas de selección de piezas que se pueden usar en algún momento
durante el tiempo que el peer local está en la red. En todas las políticas se da por
sentado que las piezas que ya tiene el peer local se descartan de la selección. La implementación del sistema de selección de piezas se encuentra en la clase BtTorrent,
que se explica en el apartado 5.14.
5.10.1.
Rarest first
Esta política selecciona primero las piezas con menor número de copias en el vecindario. Esta política tiene tres ventajas. Primero, aumenta la probabilidad de que
el peer local sea interesante para otros peers porque posee algo que los demás no
tienen. Segundo, iguala la distribución de las piezas en la red, cosa que minimiza
el riesgo de que el tiempo de descarga de un grupo cualquiera de peers aumente
considerablemente cuando los peers que poseen una pieza muy rara abandonan la
red. Tercero, esta política tiende a igualar la velocidad de propagación de todas
las piezas por la red.
Esta política puede ralentizar la descarga en el período inicial, cuando el peer
todavía no ha descargado un cierto número pequeño de piezas. Esto se debe a dos
motivos. Uno, el algoritmo de bloqueo perjudica a los peers que no tienen piezas
que compartir. Y dos, cuanto más rara es una pieza más tiempo se necesita para
descargarla. Por lo que la política rarest first puede ser mala justo al inicio.
5.10.2.
Random
Esta política simplemente selecciona una pieza aleatoria. Puede ser útil justo en
el inicio de la descarga, como alternativa a la política rarest first. Es la política
estándar en el núcleo de BitTorrent (BEP3), aunque la política rarest first es la que
se usa de facto durante la mayor parte de la descarga por las ventajas mencionadas
arriba.
51
5.10.3.
Most common first
Esta política selecciona primero las piezas más comunes. Puede ser mejor que
la política random justo al inicio de la descarga si el peer local tiene muchas
dificultades para conseguir su primera pieza.
5.10.4.
Lower first
Es posible que una aplicación BitTorrent necesite descargar las piezas en orden
estricto de índice. Esto sucede sobretodo en las aplicaciones que hacen streaming
sobre BitTorrent.
5.11.
End Game
Cuando el peer local se ha unido a una red BitTorrent y ha conseguido una buena
tasa de descarga se debe a que está descargando un número considerable de piezas
procedentes de múltiples peers remotos que lo tienen desbloqueado. A medida que
el torrent se acerca al final de la descarga, la tasa de descarga disminuye.
La primera explicación que cualquiera podría dar es que las últimas piezas son
muy raras. Y no es así, las últimas piezas son tan comunes o más que todas las
descargadas previamente porque lo asegura la política rarest first. La disminución
de la tasa de descarga se debe a que el número de piezas que se buscan es tan
pequeño que es mucho más difícil conocer el mismo número de peers que las tengan
que cuando se buscaban muchas más piezas. Y al encontrar menos peers, la tasa
de descarga disminuye.
Para contrarrestar esta situación, BitTorrent establece la fase o modo End
Game, que comienza cuando todos los bloques de las piezas restantes han sido
pedidos. A partir de ese momento, el peer local pide todos los bloques restantes a
todos los peers remotos que lo tengan desbloqueado y tengan esos bloques. Para
evitar que esta situación, aunque relativamente breve, sature la red con bloques
redundantes, cuando el peer local recibe un bloque, envía un cancel a todos los
peers remotos a los que les pidió ese bloque.
5.12.
Conteo de las piezas en la red
Cuando el peer local está conectado a otros peers, en la red pueden haber muchas
piezas disponibles y, según la fase en que se encuentre la descarga del torrent,
convendrá usar una política de selección de piezas u otra a la hora de elegir el orden
en que se descargarán (como se explicó en el apartado 5.10). Una información
esencial para llevar esto a cabo es el número de ocurrencias de las piezas en el
vecindario. Así que es necesario tener un objeto al que podamos acceder para
52
que nos dé esa información y que nos permita establecer qué criterio queremos
para ordenarlas en cada momento. Estas funcionalidades las ofrecen las clases
BtPieceCounter y BtPiecesOnNetwork.
La clase BtPieceCounter simplemente representa el contador de una pieza cuyos miembros son dos enteros: uno para definir el índice de la pieza y otro para
actualizar la cuenta. Las instancias de esta clase disponen de un método de comparación rarestOrEqualThan: que recibe otro BtPieceCounter como argumento y
determina si el receptor aparece menos o igual veces en la red. También es posible
compararlas por índice con el método lowerOrEqualThan:.
La clase BtPiecesOnNetwork encapsula una SortedCollection que contiene
tantas instancias de BtPieceCounter como piezas tenga el torrent que se esté
compartiendo. Esta colección por defecto está ordenada utilizando la función de
comparación de conteo nombrada arriba. También es posible modificar explícitamente el tipo de ordenación que usará la colección con los métodos setIndexMode
y setRarityMode.
El conteo de las piezas de BtPiecesOnNetwork se actualiza con el método
modifyAt:by: indicando el conteo de qué pieza va a modificarse y cuánto va a
aumentar o disminuir. Esta actualización solo se produce en tres casos:
Un peer nuevo se une al vecindario, es decir, el peer local consigue establecer
una conexión nueva. En tal caso, el peer remoto envía los índices de las piezas
que tiene y el peer local debe aumentar en 1 el conteo de las piezas que posee
este nuevo peer .
Un peer remoto envía un mensaje indicando que tiene una pieza nueva. En
tal caso, hay que aumentar en 1 el conteo de la pieza cuyo índice ha llegado.
Cuando un peer remoto es descartado por el peer local, las copias de las
piezas que tiene ese peer ya no están disponibles en el vecindario, así que
hay que decrementar en 1 el conteo de esas piezas.
5.13.
La clase BtRemotePeerCollection
El peer local, por cada torrent, tiene asociada una colección de conexiones u objetos
BtRemotePeer con los que intercambia mensajes y bloques. El peer local tiene
que ser capaz de realizar una serie de operaciones básicas sobre estos peers como
seleccionar u ordenarlos según algunos criterios. Por ejemplo, cuando tiene que
decidir qué peers bloquear y cuales no, es necesario seleccionarlos según si los
peers remotos están interesados o no, y ordenarlos por tasa de transferencia.
Se ha decidido implementar una clase que tenga las características de
SortedCollection que permita ordenar los peers remotos por tasa de transfe53
rencia y que además ofrezca una serie de operaciones específicas de los objetos BtRemotePeer. Por ello, la clase BtRemotePeerCollection es subclase de
SortedCollection. Estas colecciones de peers remotos tienen dos modos: NotCompleteMode y CompleteMode. El primero mantiene los peer remotos ordenados
por tasa de subida desde el punto de vista del peer remoto, es decir, los peers que
más bloques comparten con el peer local están en las primeras posiciones de la colección. Este criterio de ordenación es necesario usarlo cuando el peer local todavía
no tiene todas las piezas del torrent. Sin embargo, cuando ya tiene todas las piezas
es necesario ordenarlos por tasa de bajada. Como el peer local ya no descarga
ninguna pieza, solo puede guiarse por cuanto descargan los peers remotos de él.
El segundo modo se corresponde con esta última situación. La forma de establecer
un modo u otro es con los métodos setCompleteMode y setNotCompleteMode.
Adicionalmente también dispone de otros métodos que permiten seleccionar o
descartar los peers según varios criterios que los propios nombres de los métodos indican claramente como selectAccepted, selectAcceptsMe, selectInterested,
selectPieceOwnersOf:, selectSeeders. . . Dos de estos métodos son útiles, por
ejemplo, cuando el peer local necesita seleccionar a qué peers puede pedir bloques
de una determinada pieza. Tal cosa se puede conseguir con la siguiente instrucción.
remotePeers selectAcceptsMe selectPieceOwnersOf: pieceIndex.
Primero selecciona los peers remotos que lo tienen desbloqueado mandando el
mensaje selectAcceptsMe a remotePeers. Y después, de entre esos, selecciona
aquellos que poseen la pieza con índice pieceIndex.
Otro método útil es ensureAcceptFirst:, que recibe un entero n y se asegura
de que los primeros n peers remotos de la lista estén desbloqueados por el peer local
y que el resto estén bloqueados. Este método se usa en el algoritmo de bloqueo.
Los métodos doHave: y doCancel:, por ejemplo, recorren toda la lista y envían
un mensaje have y cancel a cada peer , respectivamente. El primero se utiliza
cuando hay que comunicar a cada peer remoto que el peer local ha descargado y
validado una pieza nueva. Y el segundo se utiliza en el End Game cada vez que hay
que enviar un cancel a todos los peers a los cuales se ha enviado un request repetido.
Precisamente en esta última tarea, hay otro método de BtRemotePeerCollection
implicado, que es selectWhoIHaveRequested:, el cual recibe un BtBlockRequest
y devuelve aquellos peers de la colección a los cuales el peer local ha pedido ese
bloque. En el siguiente trozo de código se puede ver cómo se realiza esta tarea
combinando este último método con doCancel:.
blockRequest := BtBlockRequest pieceIndex: index offset: begin length: length.
(remotePeers selectWhoIHaveRequested: blockRequest) doCancel: blockRequest
Primero selecciona los peers a los que ha enviado el mensaje request representado por blockRequest, y después envía un mensaje cancel a cada uno de
54
ellos.
5.14.
La clase BtTorrent
La clase BtTorrent representa una tarea de descarga y compartición de un torrent
concreto. Sus variables más significativas son las siguientes.
metainfo. Un objeto de la clase BtMetainfo. Indispensable para unirse a
una red BitTorrent.
location. Un objeto FileReference que indica la ruta del disco donde se
almacena el contenido del torrent.
fileCollection. Un objeto de la clase BtFileCollection para acceder
físicamente a las piezas del torrent.
tracker. Un objeto BtHTTPTracker o BtUDPTracker dependiendo de la URL
que aparezca en la clave announce de metainfo.
multitracker. Un objeto BtMultitracker cuyos elementos son los trackers
que aparecen en la clave announce-list de metainfo.
bitfield. Un objeto BtBitfield para llevar el control de las piezas del
torrent que tiene el peer local.
piecesOnNetwork. Un objeto BtPiecesOnNetwork para llevar el conteo de
las piezas en el vecindario.
remotePeers. Un objeto BtRemotePeerCollection donde guardar y manejar las conexiones P2P.
temporaryPieces. Un objeto OrderedCollection cuyos elementos son instancias de BtTemporaryPiece, donde guardar los bloques de las piezas a
medio descargar.
Las instancias de BtTorrent también disponen de una serie de procesos que se
encargan de realizar todas las operaciones necesarias en la descarga y compartición
del torrent. Estas instancias disponen de tres operaciones básicas implementadas
en los métodos start, stop y delete. El método start inicia la compartición del
torrent y ejecuta todos los procesos necesarios durante su compartición. Para ello
ejecuta el proceso starting, que inicia esos procesos y termina inmediatamente.
El método stop detiene la compartición del torrent y elimina todos los procesos
en ejecución iniciados por la instancia. Para ello ejecuta el proceso stopping, que
ejecuta el método terminateProcesses y se asegura de comunicar al tracker que
55
abandona la red. El método delete simplemente elimina los ficheros del torrent
que se encuentran en el disco. En los subsiguientes apartados se explican todos los
procesos implicados durante la compartición del torrent.
5.14.1.
Proceso: Validación incial
Este proceso está definido en la variable initialChecking. Se ejecuta al iniciar el
torrent y comprueba qué piezas ya están descargadas. El método principal de este
proceso es checkPieces.
BtTorrent>>checkPieces
bitfield clearAll.
0 to: metainfo numPieces − 1 do: [ :index |
| piece |
piece := fileCollection readOffset: index ∗ metainfo pieceLength amount: (metainfo
pieceLengthOf: index).
piece
ifNotNil: [
(SHA1 hashStream: piece readStream) = (metainfo sha1Of: index)
ifTrue: [ bitfield set: index ] ] ]
Este método primero pone todos los bits de bitfield a 0 y después comprueba pieza por pieza si su resumen concuerda con el que aparece en metainfo.
En caso afirmativo, pone su bit a 1. Para acceder a las piezas utiliza el objeto
fileCollection.
5.14.2.
Proceso: Tracker requesting
Este proceso está definido en la variable trackerRequesting y se encarga de
determinar si es necesario realizar operaciones announce ya sea por que el peer
local necesita más peers remotos a los que conectarse o porque se ha producido un
evento determinado. Hace uso de los objetos tracker y multitracker. Su bloque
de ejecución es el siguiente.
[ self shouldAnnounce
ifTrue: [ self doAnnounce ].
(Delay forSeconds: 1) wait ] repeat
Este bucle primero comprueba si es necesario realizar una operación announce.
En caso afirmativo, invoca el método doAnnounce.
56
BtTorrent>>doAnnounce
self selectLastTracker.
[ self selectNextTracker.
self updateAnnounceRequest.
self currentTracker announce ] doWhileTrue: [ self announceHasFailed ].
self moveCurrentTrackerToFirstInTier.
self updateRemotePeers
Este método recorre la lista del multitracker mientras ninguna petición obtenga una respuesta satisfactoria. En cuanto la operación tiene éxito, mueve el tracker
que ha respondido a la primera posición de su tier e invoca updateRemotePeers.
Este último método se encarga de recuperar las direcciones IP y puertos de los
peers remotos que se encuentran en la respuesta del tracker e intenta abrir conexiones con ellos (respetando el número máximo de conexiones simultáneas que se
encuentra en la variable maxConnections).
Los
métodos
selectLastTracker,
selectNextTracker
y
moveCurrentTrackerToFirstInTier solo tienen efecto si el objeto metainfo
tiene clave announce-list y si el torrent está usando el «modo multitracker ». El método currentTracker devuelve el objeto tracker o el tracker
seleccionado de multitracker dependiendo del modo en que esté el torrent.
La forma de gestionar el modo de interacción con el(los) tracker (s) es con los
métodos supportMultitracker, setMultitrackerMode, setSingletrackerMode
y isInMultitrackerMode.
5.14.3.
Proceso: Choking
Este proceso está definido en la variable choking y se encarga de bloquear y
desbloquear a los peers remotos según el algoritmo de bloqueo «ojo por ojo, diente
por diente» que marca el protocolo.
[ self updateChokingStatus.
remotePeers doRestartMeasuringRates.
(Delay forSeconds: 10) wait ] repeat
57
1
2
3
4
5
6
7
8
9
10
BtTorrent>>updateChokingStatus
candidates := remotePeers reject: [ :peer | peer isLucky ].
candidatesThatDontCompete := candidates select: [ :peer | peer isInterested not or: [ peer
isSnubber ] ].
candidatesThatDontCompete ensureNotAccept.
competitors := candidates difference: candidatesThatDontCompete.
self isComplete
ifTrue: [ competitors setCompleteMode ]
ifFalse: [ competitors setNotCompleteMode ].
max := competitors size min: maxPeersAccepted.
competitors ensureAcceptFirst: max
El proceso invoca, cada 10 segundos, el método que actualiza el estado bloqueado/desbloqueado de los peers vecinos (updateChokingStatus). Este método
primero selecciona los candidatos a ser bloqueados/desbloqueados descartando los
peers que están desbloqueados de forma optimista en la línea 2. De estos, selecciona los que seguro no van a competir porque no están interesados en el peer local
o le hacen snubbing, y son bloqueados si no lo estaban ya (líneas 3 y 4). En la
línea 5 selecciona los peers que van a competir por ser desbloqueados calculando
la diferencia entre los candidatos iniciales y los peers que no compiten. A continuación, ordena la colección resultante dependiendo de la fase de compartición en
que se encuentre el torrent (líneas 6-8). Después desbloquea los primeros max peers
de la colección y el resto son bloqueados (líneas 9 y 10). Por último, el proceso
pone a 0 las cantidades de octetos descargados y subidos de cada peer conocido
para poder medir las tasas de transferencia de cada periodo de bloqueo/desbloqueo
(remotePeers doRestartMeasuringRates).
5.14.4.
Proceso: Optimistic Choking
Este proceso está definido en la variable optimisticChoking y se encarga de
bloquear y desbloquear a los peers remotos según el algoritmo de bloqueo optimista
que marca el protocolo.
[ self updateLuckyPeer.
(Delay forSeconds: 30) wait ] repeat
58
1
2
3
4
5
6
7
8
9
10
BtTorrent>>updateLuckyPeer
oldLuckyPeer := remotePeers detectLucky.
candidates := oldLuckyPeer
ifNil: [ remotePeers rejectAccepted ]
ifNotNil: [ remotePeers rejectAccepted add: oldLuckyPeer; yourself ].
newLuckyPeer := [ candidates atRandom ] on: Error do: [ :ex | ex return: nil ].
oldLuckyPeer ~= newLuckyPeer
ifTrue: [
oldLuckyPeer notAccept; isLucky: false .
newLuckyPeer accept; isLucky: true ]
El proceso invoca el método updateLuckyPeer cada 30 segundos. Este método
primero selecciona los candidatos a ser desbloqueados de forma optimista (líneas 25). Después selecciona aleatoriamente de entre los candidatos el nuevo peer que va
a ser desbloqueado (línea 6). Si es diferente al antiguo, entonces éste es bloqueado
y el nuevo desbloqueado (líneas 7-10).
5.14.5.
Proceso: Tratamiento de mensajes entrantes
Este proceso está definido en la variable receiving y se encarga de tratar todos
los mensajes entrantes de cada conexión.
[
peersToDiscard := OrderedCollection new.
remotePeers
do: [ :peer |
peer secondsSinceLastReceivedMessage > BtMessage connectionTimeOut
ifTrue: [ peersToDiscard add: peer ]
ifFalse: [
| message |
message := peer nextReceivedMessage.
message ifNotNil: [ self handleReceivedMessage: message remotePeer: peer ] ] ].
[ peersToDiscard isEmpty ]
whileFalse: [ self discardRemotePeer: (peersToDiscard remove: peersToDiscard first) ] ].
Processor yield.
true ] whileTrue
En cada iteración del bucle, recorre la lista de peers remotos y trata el primer
mensaje entrante de cada peer . En caso de que el peer local no haya recibido ni
un solo mensaje durante los últimos 2 minutos (BtMessage connectionTimeOut)
lo añade a una colección temporal de peers que serán descartados al final de la
iteración.
El
método
encargado
de
tratar
los
mensajes
es
handleReceivedMessage:remotePeer: que recibe un mensaje y un peer remoto como parámetros. En función del tipo de mensaje, este método invoca
a uno de los métodos restantes del protocolo handling received messages
59
(es un grupo de métodos dentro de la clase BtTorrent). Por ejemplo, si el
mensaje recibido es un request, invoca handleReceivedRequest:remotePeer:,
que accede a fileCollection para leer el bloque solicitado y lo añade a la
cola de salida del peer (o conexión P2P) que lo ha solicitado con el método
BtRemotePeer>>sendPiece:begin:block:
5.14.6.
Proceso: Petición de bloques
Este proceso está definido en la variable requesting y se encarga de solicitar los
bloques de las piezas a los peers remotos y de decidir la prioridad de las piezas a
la hora de ser descargadas. En total, es capaz de usar 4 políticas de selección de
piezas diferentes.
Rarest first Cuando se usa esta política, las piezas son seleccionadas con el método rarestPiecesOnNetwork, que devuelve una colección de índices de piezas
ordenada ascendientemente por número de ocurrencias en el vecindario.
Most common first Cuando se usa esta política, las piezas son seleccionadas
con el método mostCommonPiecesOnNetwork, que devuelve una colección de
índices de piezas ordenada descendientemente por número de ocurrencias en
la red.
Lowest first Cuando se usa esta política, las piezas son seleccionadas con el método lowestPiecesOnNetworkGreaterThan:, que devuelve una colección de
índices de piezas ordenada ascendientemente y mayores que el entero que
recibe.
Random Cuando se usa esta política, las piezas son seleccionadas con el método
randomPiecesOnNetwork, que devuelve una colección de índices de piezas
ordenada de forma aleatoria.
Todos los métodos nombrados arriba acceden al objeto piecesOnNetwork, que
es el que mantiene el control de las ocurrencias de las piezas en la red. Y todos
tienen en común que descartan las piezas sin ocurrencias o que el peer local ya
tiene, ya que esas piezas no le interesan.
Independientemente de las políticas de selección de piezas, los objetos BtTorrent
pueden usar dos modos de descarga, cada uno con propósitos distintos.
Smart (BtTorrent>>setSmartMode). Este modo tiene como objetivo descargar
el torrent de la forma más rápida posible. Este modo usa la política most
common first mientras no se haya descargado 4 piezas. El objetivo es maximizar las probabilidades de obtener las primeras piezas. Una vez tenga 4
piezas, entonces usa la política rarest first.
60
Streaming (BtTorrent>>setStreamingMode). Este modo usa durante toda la
vida del torrent la política lowest first. Este modo está pensado para los
torrents que, por algún motivo, tiene sentido acceder a algunas partes del
mismo sin necesidad de que la descarga haya acabado previamente.
Cuando un peer tiene una o más piezas a medio descargar, la prioridad es
acabar de completar esas piezas lo antes posible para así tener más piezas que
compartir y, por tanto, mejores tasas de subida que permitan al peer local ser correspondido (desbloqueado) por más peers. Un peer que mantiene muchas piezas
a medio descargar provoca ineficiencia en la red puesto que ralentiza la velocidad
de propagación de éstas. Por tanto, el proceso de petición de bloques siempre hace
una selección previa de piezas a medio descargar cuyos bloques no han sido solicitados por completo por el peer local; sus índices pueden obtenerse invocando el
método BtTorrent>>halfwayNotFullRequestedPieces. Después hace la selección «normal» que se use en cada momento, que depende del modo de descarga y
de la fase en que se encuentre ésta.
De forma totalmente independiente a las políticas de selección y los modos de
descarga, este proceso también implementa la fase End Game. En cada iteración
del bucle, si el torrent se encuentra en este modo, las políticas de selección y
los modos de descarga dejan de tener efecto y simplemente se solicitan todos los
bloques restantes de todas las piezas a todos los peers remotos que las tengan.
Hemos visto cómo este proceso selecciona las piezas que va a intentar descargar,
pero falta ver cómo solicita los bloques de las piezas que ha seleccionado a los peers
remotos que las tienen. El método broadcastRequestMissingBlocks: recibe una
colección de índices de piezas, y solicita todos los bloques de esas piezas (que el
peer local no tiene) a todos los peers remotos que los tienen. Este método es el
que se invoca cuando el torrent se encuentra en la fase End Game. El método
requestMissingAndNotRequestedBlocksOf: recibe una colección de índices de
piezas y solicita todos los bloques de esas piezas (que el peer local no tiene) y para
los que no hay una solicitud pendiente en la red. En este caso, el método asegura
que no habrán dos solicitudes pendientes en la red para el mismo bloque. Este
método es el que se invoca cuando el torrent no se encuentra en la fase End Game.
Todos los métodos de las políticas de selección, independientemente del orden
en que seleccione las piezas, tienen en común que devuelven los índices de todas
las piezas restantes que no tiene el peer local. Esto significa que si no se pone
ningún límite al número de peticiones de bloque salientes sin responder, supone un
problema porque nada más empezar la descarga del torrent, todos los bloques serán
solicitados. Teniendo en cuenta que un torrent de 1 GiB con piezas de 512 KiB y
bloques de 16 KiB tiene 65536 bloques, el peer local envía 65536 peticiones en
cuanto todas las piezas estén disponibles en el vecindario (hecho de lo más común
puesto que con la existencia de un solo seeder se cumple esta situación). Esto
61
supone que por cada conexión de la que el peer local descargue, habrá muchas
peticiones salientes sin responder. Esto supone un problema porque en caso de
recibir un choke, la inmensa mayoría de estás peticiones no serán respondidas
(como marca el protocolo) desperdiciando mucho ancho de banda. Para evitar
este problema, este proceso hace dos cosas:
Limitar el número de peticiones sin responder por conexión a 20.
Equilibrar el número de peticiones sin responder entre conexiones. Cuando
hay que pedir un determinado bloque y hay más de un peer remoto para
elegir, se elige el que tenga menos peticiones pendientes.
Esta estrategia tiende a la situación en que todas las conexiones de las que el
peer local está descargando alcancen el número máximo de peticiones pendientes
permitidas.
5.15.
La clase BtLocalPeer
La clase BtLocalPeer es subclase de BtPeer. Representa la aplicación BitTorrent
y su función es almacenar objetos BtTorrent para gestionarlos y recibir todas las
conexiones P2P entrantes. Las principales variables de esta clase son las siguientes.
torrents. Un objeto OrderedCollection para almacenar instancias de
BtTorrent.
receiverSocket. Un objeto Socket sobre TCP para recibir conexiones entrantes.
Como esta clase representa la aplicación BitTorrent, es conveniente que solo
pueda existir una instancia de esta clase dentro de Pharo-Smalltalk (lo que se
conoce en inglés como singleton pattern). Para ello se ha sobrescrito el método
constructor new de la superclase, que asegura que no se instanciará un nuevo
objeto si ya existe otro previamente. En tal caso, devuelve ese objeto, como se
puede ver en su implementación:
BtLocalPeer class>>new
instances := self allInstances.
^ instances ifEmpty: [ super new ] ifNotEmpty: [ instances first ]
Cuando hay que añadir un nuevo torrent a la colección torrents, hay que establecer en el nuevo torrent el identificador y el puerto que está usando la instancia
de BtLocalPeer antes de añadirlo a la colección. De esto se encarga el método
BtLocalPeer>>addTorrent:, como se ve en el siguiente trozo de código.
62
BtLocalPeer>>addTorrent: aTorrent
(torrents includes: aTorrent)
ifFalse: [
aTorrent id: id; port: port.
torrents add: aTorrent ]
La forma de iniciar y detener los torrents es accediendo a ellos con la interfaz estándar de torrents (que es una OrderedCollection) y usar los métodos BtTorrent>>start y BtTorrent>>stop, respectivamente. Además, también
existe el método BtTorrent>>isActive que permite saber si el torrent en cuestión está trabajando o no.
La instancia única de BtLocalPeer ejecuta dos procesos que se inician con el
método BtLocalPeer>>start. Uno se encarga de recibir las conexiones TCP
entrantes, y el otro se encarga de establecer el número máximo de conexiones permitidas a cada torrent de la colección torrents. En cambio, el método
BtLocalPeer>>stop se encarga de detener los procesos y detener todos los torrents que gestiona. Ambos procesos se explican en los siguientes apartados.
5.15.1.
Proceso: Escucha de puerto
Este proceso se encuentra en la variable de instancia listening, y se encarga de
invocar constantemente el método receiveConnection, que se encarga de escuchar
el puerto usado por la aplicación y recibir las conexiones TCP entrantes que el peer
local recibe durante la compartición de los torrents que gestiona.
1
2
3
4
5
6
7
BtLocalPeer>>receiveConnection
remotePeer := self waitForConnectionFor: 1.
remotePeer ifNotNil: [
[ torrent := self giveHandshake: remotePeer.
torrent addRemotePeer: remotePeer .
] forkAt: Processor userBackgroundPriority
].
El método receiveConnection primero escucha el puerto que usa la aplicación BitTorrent durante un segundo. Si se ha producido un intento remoto de
conexión, entonces el método waitForConnectionFor: devuelve un nuevo objeto
BtRemotePeer que encapsula el SocketStream creado a partir del Socket de la
nueva conexión remota, como puede verse en su implementación:
BtLocalPeer>>waitForConnectionFor: seconds
newSocket := receiverSocket waitForAcceptFor: seconds ifTimedOut: [ nil ].
^ newSocket
ifNil:[ nil ]
ifNotNil: [ (BtRemotePeer on: (SocketStream on: newSocket)) debug: debug ]
63
Otra vez de vuelta en receiveConnection, el método giveHandshake: recibe
el peer remoto como parámetro y se encarga de realizar el handshake, y devuelve
el objeto BtTorrent de la colección cuyo info_hash concuerda con el recibido
durante el handshake. La conexión es rechazada si
alguna parte del handshake no es correcta,
el objeto BtLocalPeer no tiene ningún torrent con el info_hash del handshake o
el torrent al que va dirigido la conexión ha alcanzado el número máximo de
conexiones.
Por último, el nuevo objeto BtRemotePeer es añadido al objeto torrent con
el método BtTorrent>>addRemotePeer:.
Para poder recibir y tratar otras conexiones TCP cuanto antes, una vez se ha
recibido una nueva conexión, el resto de las instrucciones se ejecutan en un proceso
nuevo (líneas 4-6) para volver a invocar waitForConnectionFor: inmediatamente.
Y, así, receiverSocket puede seguir recibiendo conexiones entrantes.
Como se puede ver, la instancia de BtLocalPeer solo recibe conexiones entrantes y delega su gestión en los torrents que corresponda de forma que cada
torrent es responsable de su propio conjunto de conexiones. Sin embargo, esta
dependencia de los torrents en BtLocalPeer solo se da con las conexiones entrantes. Cuando es el peer local el que inicia una conexión con un peer remoto, es la
instancia de BtTorrent la responsable de hacerlo sin necesidad de que intervenga
BtLocalPeer, ya que dispone del método BtTorrent>>connectTo:, que recibe
un objeto BtRemotePeer y establece una conexión en una dirección IP y un puerto
determinado.
5.15.2.
Proceso: Gestión de torrents
Este proceso se encuentra en la variable de instancia managing, y se encarga de
repartir las conexiones que puede mantener cada torrent que gestiona. La aplicación BitTorrent, representada por la instancia única BtLocalPeer, tiene un límite
de conexiones impuesto en la variable maxConnections que no puede ser sobrepasado por la suma de conexiones de los torrents. Esta limitación es necesaria
para evitar sobrecargar el sistema. Para respetar este límite, invoca el método
updateConnectionsPerTorrent cada segundo, cuya implementación se puede ver
a continuación.
64
1
2
3
4
5
6
7
8
9
10
11
12
13
BtLocalPeer>>updateConnectionsPerTorrent
remaining := maxConnections.
activeTorrents := torrents select: [ :torrent | torrent isActive ].
numTorrents := activeTorrents size.
connectionsPerTorrent := maxConnections quo: numTorrents.
activeTorrents do: [ :torrent | torrent maxConnections: connectionsPerTorrent ].
remaining := maxConnections rem: numTorrents.
index := 1.
[ remaining > 0 and: [ index <= numTorrents ] ]
whileTrue: [
(activeTorrents at: index) modifyMaxConnectionsBy: 1.
remaining := remaining − 1.
index := index + 1 ]
Primero selecciona los torrents de la colección que están activos (línea
3), y después asigna el número de máximo de conexiones permitidas a cada torrent de forma equitativa (líneas 6-13). Entonces, cada instancia de
BtTorrent se encarga internamente de respetar esta restricción con el método
BtTorrent>>reduceExcessOfRemotePeers, que es invocado cada vez que se invoca BtTorrent>>maxConnections:.
BtTorrent>>maxConnections: anInteger
maxConnections := anInteger.
self reduceExcessOfRemotePeers
5.15.3.
Justificación de la necesidad de BtLocalPeer
Podría ser deseable que los torrents ni siquiera necesitaran a BtLocalPeer para
recibir conexiones entrantes siguiendo el principio de que cada objeto sea responsable de las funciones que le corresponden. De esta forma, BtTorrent se encargaría
completamente de gestionar todas sus conexiones y BtLocalPeer no intervendría
en absoluto. En tal caso, cada torrent tendría su particular instancia de Socket
(como el objeto receiverSocket de BtLocalPeer) para escuchar sus conexiones
TCP entrantes. Lamentablemente, existe una limitación técnica que lo hace inviable. No es posible tener varios sockets escuchando en el mismo puerto y conseguir
que cada conexión entrante la trate el socket del torrent al que va dirigida. Suponiendo que técnicamente fuera posible, también existiría un inconveniente que no
haría esta opción tan deseable. Teniendo en cuenta que las aplicaciones BitTorrent
conviven con muchas más en el sistema, no sería «justo» acaparar tantos puertos
como torrents se estuvieran compartiendo.
Además, existe un argumento de peso a favor de la existencia de BtLocalPeer.
Sin la existencia de un objeto capaz de contener varios objetos BtTorrent no es
posible imponer un límite de conexiones global para evitar sobrecargar el sistema.
65
Por estos motivos, la decisión tomada ha sido implementar la clase BtLocalPeer,
que representa a toda la aplicación BitTorrent.
6.
6.1.
Planificación
Planificación inicial
El proyecto se planificó para ser presentado en el turno de junio, comenzando
el 28 de marzo (después de finalizar el GEP) y finalizando el 15 de junio, una
semana antes de la fecha límite para entregar la memoria. El proyecto se dividió
en cuatro tareas principales, las cuales se corresponden con las implementaciones
de los diferentes prototipos previstos inicialmente:
Prototipo 1 La aplicación BitTorrent implementa bencoding, lee y genera ficheros .torrent, y puede comunicarse con trackers y peers remotos (que implementen cualquier otra aplicación BitTorrent válida).
Prototipo 2 La aplicación BitTorrent puede acceder físicamente a los torrents. La
aplicación puede detenerse y reanudarse sin necesidad de volver a descargar
las mismas piezas. La aplicación implementa un algoritmo de bloqueo y de
selección de piezas. La aplicación puede mantener múltiples conexiones con
peers remotos a la vez.
Prototipo 3 La aplicación puede gestionar varios torrents a la vez. Puede gestionar los directorios de descarga y limitar el ancho de banda utilizado.
Prototipo 4 (y final) La aplicación BitTorrent implementa DHT. En concreto,
Mainline DHT, especificado en BEP5.
Se programó una reunión de seguimiento al finalizar cada prototipo. Además,
en las tareas de los prototipos 2 y 4, que son las más largas, se programaron dos
reuniones adicionales alrededor del 50 % de su realización. Solo se dispuso de un
programador, por lo que no hubo posibilidad de paralelizar la programación de las
tareas, resultando en una programación estrictamente secuencial. La reunión del
Hito de seguimiento se programó para el 6 de mayo. En la tabla 2 se pueden
ver las fechas de inicio y fin de las tareas previstas inicialmente, y en la figura 14
el correspondiente diagrama de Gantt.
66
Nombre
Hito inicial
Prototipo 1
Reunión seguimiento
Prototipo 2
Reunión seguimiento
Informe seguimiento
Hito seguimiento (Reunión Director)
Prototipo 3
Reunión seguimiento
Prototipo 4
Reunión seguimiento
Límite seguimiento turno junio
Límite turno junio
Reunión seguimiento
Límite entrega memoria
Periodo lectura
Duración
0d
10d
0d
30d
0d
0d
0d
10d
0d
30d
0d
0d
0d
0d
0d
5d
Inicio
28/03/2015
28/03/2015
06/04/2015
07/04/2015
21/04/2015
29/04/2015
06/05/2015
07/05/2015
18/05/2015
17/05/2015
29/05/2015
29/05/2015
29/05/2015
15/06/2015
21/06/2015
29/06/2015
Tabla 2: Planificación de las tareas
67
Fin
28/03/2015
06/04/2015
06/04/2015
06/05/2015
21/04/2015
29/04/2015
06/05/2015
16/05/2015
18/05/2015
15/06/2015
29/05/2015
29/05/2015
29/05/2015
15/06/2015
21/06/2015
03/07/2015
Figura 14: Diagrama de Gantt del proyecto
68
6.2.
Modificaciones a la planificación inicial
La planificación inicial tuvo que modificarse dejando de implementar el BEP5
(DHT) y retrasando la presentación del proyecto al turno de octubre debido a los
siguientes motivos:
Se tuvieron que implementar varios BEP inicialmente no previstos, que a
priori no se consideraron necesarios por no estar especificadas en el BEP3
(el núcleo de BitTorrent), pero, de facto, totalmente necesarios para el funcionamiento de la aplicación. Estas ampliaciones fueron los BEP23 (información
de contacto compacta de los peers), BEP12 (multitracker ) y BEP15 (trackers
UDP).
El software, al ser una aplicación compleja y distribuida, fue necesario más
tiempo del previsto para adquirir un conocimiento en profundidad del funcionamiento de Smalltalk, muy distinto a los lenguajes de programación y
herramientas usados durante el transcurso del Grado.
7.
7.1.
Presupuesto y sostenibilidad
Identificación de recursos y estimación de costes
Los recursos humanos han constado de una sola persona, que ha desempeñado las
funciones de Jefe de Proyecto, diseñador/programador y probador. Se ha estimado
un sueldo de 40A
C la hora. La impresión de la memoria se ha estimado en 10A
C por
copia. El consumo eléctrico del equipo de trabajo se ha calculado haciendo uso de
los sitios web especializados
http://www.pcsilencioso.com y
http://www.extreme.outervision.com,
que calculan el coste de forma precisa según el hardware del equipo. El consumo
de la conexión a Internet se ha estimado convirtiendo A
C/mes a A
C/hora. La fuente usada han sido las facturas de los últimos meses previos a la realización del
proyecto.
69
Prototipo 1
Prototipo 2
Prototipo 3
Prototipo 4
Impresión de la memoria
Unidades
Precio Información de la unidad
Tiempo (horas)
Total
1
1
1
1
3
40A
C
40A
C
40A
C
40A
C
10A
C
Programador.
Programador.
Programador.
Programador.
hora
hora
hora
hora
80A
C
240A
C
80A
C
240A
C
0.032A
C
0.042A
C
Por hora. Ordenador encendido 8h/dia durante 3 meses
Por hora. 30A
C/mes = 0.042A
C/h
720
2160
3200A
C
9600A
C
3200A
C
9600A
C
30A
C
25600A
C
23.04A
C
90.72A
C
Sueldo
Sueldo
Sueldo
Sueldo
a
a
a
a
la
la
la
la
Costes directos
Consumo eléctrico
Consumo ADSL
113.76A
C
3857.06A
C
Costes indirectos
Contingencia (CD + CI × 0.15)
Reimpresión de la memoria
3
10A
C
Probabilidad = 0.2
6A
C
6A
C
29576.82A
C
Imprevistos
Presupuesto
Tabla 3: Presupuesto
7.2.
Viabilidad económica
Desde el punto de vista económico, el proyecto es totalmente viable. Solo se ha
requerido una persona y un equipo de trabajo para desarrollar la aplicación. Suponiendo que el objetivo del proyecto fuera comercial, el mercado sería global puesto
que cualquier usuario con un equipo portátil o de sobremesa modesto puede participar y beneficiarse de las redes BitTorrent. Si comparamos las posibilidades
potenciales de venta con la poca cantidad de recursos utilizados para desarrollar
la aplicación, el proyecto es altamente rentable.
7.3.
Impacto social y ambiental
La aplicación desarrollada tiene claramente ventajas e inconvenientes muy marcados. La principal ventaja de BitTorrent es la evolución tecnológica respecto a las
redes centralizadas o cliente-servidor. Los dos principales puntos fuertes de este
tipo de redes son la escalabilidad y la robustez. Mientras las redes cliente-servidor
pueden colapsar llegado a cierto número de participantes, las redes BitTorrent pueden escalar teóricamente de forma infinita ya que la carga de trabajo se reparte
de forma homogénea entre todos los peers. Su naturaleza descentralizada también las hace robustas ya que no existe un punto vulnerable como con las redes
cliente-servidor. Gracias a estos avances tecnológicos los usuarios pueden intercambiar ficheros con una seguridad muy alta de que ningún agente externo podrá
impedirlo.
Otra posible utilidad de la aplicación que puede ser beneficiosa para la sociedad
es la de herramienta ilustrativa para la enseñanza del funcionamiento de BitTorrent
o el P2P en general. Gracias a herramientas gráficas de Pharo desarrolladas por
la comunidad, sería posible adaptar la aplicación sin demasiado esfuerzo para que
mostrara gráficamente el funcionamiento de BitTorrent con un propósito didáctico.
El principal inconveniente de BitTorrent se produce cuando se explotan sus
70
características tecnológicas para intercambiar ficheros protegidos con derechos de
autor como pueden ser películas, series, videojuegos. . .
Desde el punto vista ambiental, la aplicación no va a aumentar las emisiones
de CO2 de una forma que pueda tenerse en cuenta, puesto que no van a encenderse
muchos más ordenadores en el mundo solo para ejecutar esta aplicación, sino que
los usuarios normalmente la pondrán en marcha cuando tengan su equipo personal
encendido por otros motivos ajenos al intercambio de ficheros. Así que se puede
afirmar que la aplicación no perjudica el medio ambiente a una escala que merezca
la pena considerar.
8.
Conclusiones
Al finalizar el proyecto, la aplicación BitTorrent desarrollada es totalmente funcional. Cualquier usuario puede unirse a cualquier red BitTorrent con facilidad. La
aplicación ha sido diseñada e implementada de forma que cualquier programador
de la comunidad Pharo-Smalltalk pueda entender fácilmente su funcionamiento
gracias a un lenguaje claro y altamente expresivo. También se ha conseguido representar virtualmente dentro de Smalltalk la naturaleza de BitTorrent. Por tanto,
Los objetivos generales del proyecto se han cumplido.
Se ha implementado el BEP3 (núcleo de BitTorrent) y otras ampliaciones que
han acabado siendo necesarias como los trackers UDP (BEP15) y el uso de multitracker (BEP12). Debido a esto y al hecho de tener que desarrollar una aplicación
compleja y distribuida en un sistema muy diferente a todos los lenguajes y herramientas utilizadas hasta ahora como ha sido Smalltalk, no ha sido posible implementar la DHT (BEP5). Aún así, la implementación de DHT se encuentra bastante
avanzada en el momento de la entrega del proyecto, como se puede comprobar en
el paquete BitTalk-DHT.
9.
Posibles ampliaciones
Por supuesto, la primera ampliación propuesta sería acabar de implementar DHT
(BEP5).
Al ser la primera librería sobre BitTorrent para Pharo-Smalltalk, el rendimiento
de la aplicación no ha sido una prioridad, si no que la prioridad ha sido obtener una
aplicación funcional. Una posible mejora sería mejorar la eficiencia y la velocidad
de descarga para llegar a ofrecer un rendimiento parecido al de otras aplicaciones
BitTorrent más populares.
Actualmente no existen más BEP con estatus final o aceptado que no estén implementados en esta aplicación. Pero existen algunos BEP con estatus de borrador
71
que se usan como si fueran definitivos. Si hubiera que elegir dos, probablemente
uno sería el que sustituye la comunicación peer-to-peer sobre TCP por el protocolo
uTP (BEP29), que funciona sobre UDP. De esta forma, todas las comunicaciones serían sobre UDP, que es a lo que tiende BitTorrent desde hace años. El otro
sería implementar superseeding (BEP16), que ayuda al primer peer del la red a
distribuir más rápidamente las piezas.
La aplicación, actualmente tiene un límite fijo impuesto al número de peticiones sin responder por cada conexión. Una posible mejora sería implementar una
estrategia más inteligente que adaptara en tiempo real este límite a la velocidad
de subida del peer remoto. Si esto se hiciera correctamente, se maximizaría la velocidad de descarga y el cuello de botella dependería exclusivamente o bien de la
aplicación, o bien de la velocidad de bajada de la conexión a Internet del peer
local, pero no de un límite impuesto que desperdicia la potencia del peer remoto.
72
Adenda
A.
A.1.
Manual de usuario
Instalación de Pharo
Pharo es compatible con OS X, Windows y GNU/Linux. La versión 3.0 puede
descargase de
http://files.pharo.org/platform/Pharo3.0-mac.zip,
http://files.pharo.org/platform/Pharo3.0-win.zip,
o http://files.pharo.org/platform/Pharo3.0-linux.zip,
según corresponda.
Una vez descargado el fichero .zip, se extrae en un directorio cualquiera y ya
estará listo para ejecutarlo.
A.2.
Ejecución de Pharo
Una vez dentro del directorio «pharo3.0», solo hay que ejecutar el fichero «pharo»
(la extensión depende del sistema operativo). Cuando lo hagamos, si solo existe
la imagen por defecto (el fichero «Pharo3.0.image»), ejecutará esa imagen en la
máquina virtual directamente. Si existen otras imágenes a parte de la mencionada, entonces aparecerá una ventana ofreciendo la posibilidad de elegir la imagen
deseada, como se puede ver en la figura 15.
73
Figura 15: Selección de imagen en Pharo (Linux Mint)
A.3.
Importación de la librería
Una vez dentro de Pharo, hacemos clic izquierdo en el fondo de escritorio de Pharo
para abrir el menú global (ver figura 16) y seleccionamos Monticello Browser. En
la ventana del Monticello hacemos clic izquierdo en +Repository, después seleccionamos HTTP como tipo de repositorio y copiamos y pegamos entre las comillas
simples de location la siguiente URL (ver figura 17).
http://smalltalkhub.com/mc/BitTalkDev/BitTalk/main
74
Figura 16: Menú global de Pharo
Figura 17: Añadir un repositorio HTTP público a Pharo
Aparecerá otra ventana con las versiones del repositorio; seleccionamos la más
reciente con el ratón y hacemos clic izquierdo en Load. Después de pocos segundos
75
el repositorio ya estará en el sistema.
Ahora es recomendable guardar la imagen. Como es típico en las aplicaciones
de escritorio, se puede sobrescribir la imagen con la opción Save del menú global,
o guardar la imagen con un nombre distinto con la opción Save as. . . .
Llegados a este punto, se pueden explorar las clases de la aplicación BitTorrent
abriendo el System Browser a través del menú global. Las clases de la aplicación
se encuentran en el paquete BitTalk.
Figura 18: Exploración del paquete BitTalk en Pharo
A.4.
A.4.1.
Pruebas
Pruebas unitarias
Todas las pruebas unitarias se encuentran en el paquete BitTalk-Tests. La forma
más rápida de ejecutar todas las prueba de una vez es, con el System Browser
abierto, hacer clic con el botón secundario del ratón sobre el paquete de pruebas
y hacer clic en Run tests. . . (ver figura 19). También es posible ejecutar cada una
de las clases Bt...Test por separado haciendo clic en el botón que aparece a la
izquierda del nombre de la clase en cuestión. Y si se quiere ser aún más preciso,
también se pueden ejecutar los métodos de prueba por separado haciendo clic en
el botón a la izquierda del nombre, como con las clases.
76
Figura 19: Captura de imágen de Pharo mostrando cómo ejecutar todas las pruebas
unitarias de una vez.
A.4.2.
Prueba global
Para probar la aplicación lo que hay que hacer es abrir un Workspace con el menú
global y pegar el siguiente código dentro de él. Una vez el código esté seleccionado con el ratón, hacemos clic derecho y presionamos Do it (ver figura 20). A
la variable metainfoString hay que asignarle la cadena de caracteres de la ruta
del fichero .torrent (pueden descargarse de http://www.legittorrents.info), y
a la variable locationString hay que asignarle la cadena de caracteres de la ruta
donde se quiere almacenar el torrent.
77
metainfoString:=’/path/name.torrent’.
locationString:=’/path/sharing’.
torrent:= BtTorrent metainfoFileString: metainfoString locationString: locationString.
localPeer:= BtLocalPeer start.
localPeer addTorrent: torrent.
localPeer torrents last
start;
inspect.
La última instrucción abre una ventana con la que se puede inspeccionar en
tiempo real el estado interno del torrent que acabamos de añadir. Entre otras cosas,
podemos ver el conjunto de peers remotos que conoce el peer local (e inspeccionarlos a ellos también), el BtBitfield, los trackers, etc. En definitiva, se pueden
examinar todos los objetos descritos en el apartado de diseño e implementación.
Figura 20: Ejecución del código de la prueba global
Si se quiere detener el torrent sin detener la aplicación, simplemente hay que
acceder a él y detenerlo con
localPeer torrents last stop.
Sin embargo, si lo que se quiere es detener la aplicación en su totalidad, solo
hay que detener al peer local con
78
localPeer stop.
, y la aplicación se encargará de detener todos los torrents de su colección.
79
Acrónimos
BBS Bulletin Board System. 15, 16
BDFL Benevolent Dictator for Life. 8
BEP BitTorrent Enhancement Proposal. 8, 9, 21, 69, 71
CAN Content Addressable Network. 13
DHT distributed hash table. 9, 13, 14, 18, 21, 22, 66, 69, 71
DMCA Digital Millennium Copyright Act. 17
DSL domain-specific language. 9
ESUG European Smalltalk User Group. 8
FTP File Transfer Protocol. 15, 21, 83, 86
GUI graphical user interface. 10
HTTP Hypertext Transfer Protocol. 19–21, 31, 32, 34, 75
I2P Invisible Internet Project. 14
IP Internet Protocol. 20, 31, 33, 42, 57, 64, 86
IRC Internet Relay Chat. 15, 16, 84
ISP Internet service provider. 18
LAN local area network . 22
MPAA Motion Picture Association of America. 19
P2P peer-to-peer . 7, 8, 10–15, 17–19, 21, 42–47, 55, 60, 62, 70, 81, 82, 84, 85
PEX Peer Exchange. 21, 22
RIAA Recording Industry Association of America. 17–19
TCP Transmission Control Protocol. 21, 34, 42, 50, 62–65, 72, 83, 86
80
TFG Trabajo de Fin de Grado. 7
UDP User Datagram Protocol. 21, 31, 34–38, 45, 69, 71, 72
URI Uniform Resource Identifier. 21
URL Uniform Resource Locator. 19, 20, 28, 32, 40, 55, 74, 84
uTP uTorrent Transport Protocol. 21, 22
Glosario
A|C|D|E|F|M|P|R|S|T|W
A
aplicación BitTorrent
programa que ejecutan los peers en una red BitTorrent. Normalmente se
usa de forma incorrecta la expresión «cliente BitTorrent» para referirse a
este tipo de aplicaciones. Las redes que usan BitTorrent son P2P, donde no
existen los roles exclusivos de cliente y servidor. 1, 2, 7, 9, 21, 23, 26, 29, 31,
34, 40, 62–64, 66, 71, 76
ataque de denegación de servicio
ataque a un sistema que provoca la inaccesibilidad de un servicio o recurso
por parte de los usuarios legítimos por el consumo del ancho de banda de la
red de la víctima o sobrecarga de los recursos computacionales del sistema
de la víctima. 14
B
backtracking
algoritmo que busca todas las soluciones posibles (o una parte) de algunos
problemas computacionales. 6, 12, 14
bencoding
sistema de codificación diseñado para BitTorrent. Los tipos de datos soportados son cadenas de caracteres, enteros, listas y diccionarios. 26, 37, 45,
66
81
Benevolent Dictator for Life
título que se otorga a ciertos individuos de la comunidad de desarrolladores
de software de código abierto, normalmente fundadores de proyectos que
tienen la última palabra en disputas o discusiones dentro de la comunidad.
8, 80
BitTorrent
protocolo diseñado para el intercambio de ficheros sobre una red P2P que se
usa para distribuir gran cantidad de información por Internet. 1, 2, 6–9, 13,
14, 18–23, 26, 28–32, 34, 40, 42, 43, 50–52, 55, 62–66, 69–72, 76, 80–84, 86
BitTorrent Enhancement Proposal
propuesta de ampliación de alguna parte de BitTorrent, que especifica una
nueva funcionalidad o comportamiento de la red o de los actores implicados.
8, 80
bloque
parte contigua de tamaño variable de una pieza. 25, 39, 43, 44, 47, 49, 50,
52–55, 60–62
Blu-ray
formato de almacenamiento digital para discos ópticos. 15
Bulletin Board System
servidor que permite a usuarios conectarse al sistema usando un terminal.
Una vez dentro del sistema, el usuario puede realizar una serie de operaciones como subir y descargar software y ficheros, leer noticias y boletines, e
intercambiar mensajes con otros usuarios vía correo electrónico y chat. 15,
80
C
churn
término usado para referirse a la situación donde los peers de la supercapa
se unen y abandonan la red con mucha frecuencia. 11, 13, 14
cliente-servidor
arquitectura de red que consiste en un sistema de alto de rendimiento, el
servidor, y varios sistemas habitualmente de menor rendimiento, los clientes.
El servidor es el único proveedor de servicios. Los clientes solo consumen
servicios sin compartir ninguno de sus recursos. 2, 10, 11, 17, 70, 83, 84
82
courier group
grupo de personas que distribuyen entre topsites el material publicado por
los release groups. 15, 16, 86
D
distributed hash table
sistema distribuido que ofrece un servicio de búsqueda similar al de las tablas
de hash, en el que cada nodo de la red es responsable del mantenimiento de
una parte de todas las asociaciones <clave,valor>. 9, 13, 80
E
enjambre
conjunto de peers participantes de una red BitTorrent. 6, 20, 21, 25, 41, 50,
51, 83
F
fibrillation
situación que se da cuando un peer es bloqueado y desbloqueado sucesivamente con tanta frecuencia que provoca ineficiencia en el enjambre. 50
fichero .torrent
fichero que contiene los metadatos del torrent. 20, 21, 24, 26, 29, 34, 40, 66,
77, 86
File Transfer Protocol
protocolo para tranferir ficheros de un ordenador a otro sobre una red TCP
con arquitectura cliente-servidor. 15, 80
FlashXP
cliente FTP que admite transferencias entre cliente y servidor, y entre servidor y servidor. 15
flooding
algoritmo de encaminamiento en el que cada nodo reenvía cada paquete
recibido por todas sus conexiones menos por la que ha llegado. 12, 14
free rider
actor que consume recursos pero no sirve ninguno a cambio. 50
83
I
Internet Protocol
protocolo de comunicación principal de Internet Protocol Suite. 20, 80
Internet Protocol Suite
conjunto de protocolos de comunicación usado en Internet. 84, 86
Internet Relay Chat
protocolo de la capa de aplicación que permite la comunicación en forma de
texto. Funciona sobre una arquitectura cliente-servidor. Los clientes IRC son
programas que los usuarios pueden instalar en su sistema. Estos clientes se
comunican con el servidor para transferir mensajes a otros clientes. IRC está
principalmente diseñado para la comunicación grupal en foros de discusión,
llamados canales, pero también permite mensajería privada e intercambio de
ficheros. 15, 80
Invisible Internet Project
supercapa P2P de Internet que proporciona cierto grado de anonimato a sus
usuarios. 14, 80
L
leecher
peer que no tiene todas las piezas del torrent. 19, 20, 22, 33, 35
M
metadatos
información esencial para el funcionamiento de una red BitTorrent. Identifican de forma única a los torrents. Entre otras cosas, contienen la dirección
URL del tracker , la colección de ficheros que forman el torrent, el tamaño
de las piezas y el resumen de cada una de ellas, necesario para verificar su
integridad. 20, 24, 26, 28, 29, 83, 84
metainfo
metadatos. 29, 30, 34, 40
P
84
peer
nombre con que se denominan los nodos de una red con arquitectura P2P. La
traducción al castellano sería «igual» o «par». 6, 7, 11–13, 17–25, 29, 31–35,
37–44, 46–64, 66, 69, 70, 72, 78, 81–86
peer-to-peer
arquitectura de red donde los participantes comparten parte de sus recursos
hardware (tiempo de procesador, espacio de almacenamiento, ancho de banda, dispositivos. . . ). Estos recursos son necesarios para proveer el servicio
que ofrece la red y son accesibles directamente por cualquier participante sin
necesidad de otra entidad mediadora. Los participantes de estas redes son
tanto proveedores como consumidores de servicios. 2, 6, 7, 10, 11, 72, 80
pieza
parte en que se divide un torrent. 19, 20, 24, 25, 28–30, 41–44, 47, 49–56, 60,
61, 66, 72, 82, 84, 85
R
RAR
formato de archivo que admite compresión de la información, recuperación
ante errores y partición de la información. 16
release group
grupo de personas que publica material con derechos de autor en los topsites.
15, 83, 86
S
seeder
peer que tiene todas las piezas del torrent. 19, 20, 22, 33, 35, 61
singleton pattern
patrón de diseño que limita el número de instancias de una clase a uno. 62
supercapa
capa de aplicación virtual, o red lógica, que ofrece servicios normalmente no
disponibles en la capa física subyacente. 11–14, 19, 82, 84
T
85
topsite
término usado por la warez scene para referirse a los servidores FTP clandestinos, altamente secretos y con gran ancho de banda utilizados por los
release groups y courier groups para almacenar y distribuir ficheros. 15, 16,
19, 83, 85
torrent
Puede referirse a:
1. colección de ficheros que se comparten en una red BitTorrent.
2. fichero .torrent.
. 6, 19, 20, 24, 25, 28–30, 32, 33, 35, 42, 43, 49, 50, 52–58, 60–66, 77–79,
83–85
tracker
servidor que coordina la distribución de los ficheros en una red BitTorrent.
19–22, 25, 28, 31–35, 37–41, 43, 45, 55, 57, 66, 69, 71, 78, 84
Transmission Control Protocol
protocolo perteneciente a la capa de transporte del Internet Protocol Suite.
TCP ofrece un sistema fiable y ordenado de entrega de secuencias de bytes
entre aplicaciones que se comunican sobre la red IP. 21, 80
U
Usenet
sistema mundial distribuido de debate y discusión. Sus usuarios pueden leer y
enviar mensajes (llamados articles o posts, y colectivamente llamados news)
según categorías, conocidas como newsgroups. Los servidores redistribuyen
los mensajes a otros servidores creando múltiples copias. 16, 17, 19
V
vecindario
conjunto de peers remotos que conoce el peer local. 24, 25, 50–53, 55, 60, 61
W
86
warez scene
comunidad clandestina y oculta al gran público, especializada en la distribución de material con derechos de autor, incluyendo programas y series de
televisión, películas, música, videoclips, videojuegos, aplicaciones, ebooks y
pornografía. 6, 15, 16, 86
87
Referencias
[1] Bandara, H. M N Dilum y Anura P. Jayasumana: Collaborative applications
over peer-to-peer systems-challenges and solutions. Peer-to-Peer Networking
and Applications, 6(3):257–276, 2013, ISSN 19366442.
[2] Clarke, Ian, Oskar Sandberg, Brandon Wiley y Tw Hong: Freenet: A distributed anonymous information storage and retrieval system. Designing Privacy
Enhancing . . . , páginas 46–66, 2001. http://link.springer.com/chapter/
10.1007/3-540-44702-4_4.
[3] Cohen, Bram: Incentives Build Robustness in BitTorrent.
Workshop on Economics of PeertoPeer systems, 6:68–72, 2003.
http:
//citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.14.1911&
amp;rep=rep1&type=pdf.
[4] Goldberg, A y D Robson: Smalltalk-80: the language and its implementation.
Addison-Wesley, 1983, ISBN 0-201-11371-6.
[5] Kent, B: Smalltalk Best Practice Patterns. Prentice Hall PTR, Upper Saddle
River, 1997, ISBN 013476904X.
[6] King, Sunny y Scott Nadal: PPCoin: Peer-to-Peer Crypto-Currency
with Proof-of-Stake.
Ppcoin.Org, 2012.
http://ppcoin.org/static/
ppcoin-paper.pdf.
[7] Lua, Eng Keong Lua Eng Keong, J. Crowcroft, M. Pias, R. Sharma y S. Lim:
A survey and comparison of peer-to-peer overlay network schemes. IEEE
Communications Surveys & Tutorials, 7(2), 2005, ISSN 1553-877X.
[8] Malkhi, Dahlia, Moni Naor y David Ratajczak: Viceroy: A Scalable and Dynamic Emulation of the Butterfly. Proceedings of the 21st annual ACM symposium on Principles of distributed computing, páginas 183–192, 2002.
[9] Maymounkov, Petar y D Mazieres: Kademlia: A peer-to-peer information system based on the xor metric. First International Workshop on Peer-to-Peer
Systems, páginas 53–65, 2002, ISSN 3540441794. http://link.springer.
com/chapter/10.1007/3-540-45748-8_5.
[10] Nakamoto, Satoshi: Bitcoin: A Peer-to-Peer Electronic Cash System. Consulted, páginas 1–9, 2008, ISSN 09254560. http://s.kwma.kr/pdf/Bitcoin/
bitcoin.pdf.
88
[11] Ratnasamy, S, P Francis, M Handley, R Karp y S Shenker: A Scalable Content
Addressable Network. Informe técnico TR-00-010, University of Berkeley, CA,
2000. http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.60.
3260.
[12] Rowstron, A y P Druschel: Pastry: Scalable, distributed object location and
routing for large-scale peer-to-peer systems. IFIPACM International Conference on Distributed Systems Platforms Middleware, 11(November 2001):329–
350, 2001. http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.
1.28.5987.
[13] Schollmeier, Rüdiger.: A definition of peer-to-peer networking for the classification of peer-to-peer architectures and applications. Proceedings First International Conference on Peer-to-Peer Computing, 2001.
[14] Shen, Xuemin, John Buford, Heather Yu y Mursalin Akon: Handbook of peerto-peer networking. Springer US, New York, 2010, ISBN 978-0-387-09750-3.
[15] Stoica, Ion, Robert Morris, David Liben-Nowell, David R. Karger, M. Frans
Kaashoek, Frank Dabek y Hari Balakrishnan: Chord: A scalable peer-to-peer
lookup protocol for Internet applications. IEEE/ACM Transactions on Networking, 11(1):17–32, 2003.
[16] Wang, Liang y Jussi Kangasharju: Measuring large-scale distributed systems:
Case of BitTorrent Mainline DHT. En 13th IEEE International Conference
on Peer-to-Peer Computing, IEEE P2P 2013 - Proceedings, páginas 1–10,
2013, ISBN 978-1-4799-0515-7.
[17] Zhao, Ben Y., Ling Huang, Jeremy Stribling, Sean C. Rhea, Anthony D.
Joseph y John D. Kubiatowicz: Tapestry: A resilient global-scale overlay for
service deployment. IEEE Journal on Selected Areas in Communications,
22(1):41–53, 2004.
89
Descargar