This presentation includes an overview of the BitTorrent protocol and shows my current approach and progress towards implementing a client with Scala and Akka.
3. • Actor Model for the JVM
• Scala & Java API
• Part of the Typesafe Platform
• http://akka.io
Dominik scala-torrent Gruber • @the_dom
4. • Simple Concurrency & Distribution
Asynchronous and Distributed by design. High-level abstractions like Actors,
Futures and STM.
• Elastic & Decentralized
Adaptive load balancing, routing, partitioning and configuration-driven
remoting.
• Resilient by Design
Write systems that self-heal. Remote and/or local supervisor hierarchies.
• High Performance
50 million msg/sec on a single machine. Small memory footprint; ~2.5 million
actors per GB of heap.
Dominik scala-torrent Gruber • @the_dom
6. BitTorrent
• Most popular protocol for peer-to-peer file sharing
• Designed in 2001
• Est. over 250 million users/month
• Currently responsible for 3.35% of all worldwide
bandwidth
Dominik scala-torrent Gruber • @the_dom
8. Basic Procedure
1. Load information from a metainfo (.torrent) file
2. Request peers from tracker
3. Connect to peers via handshake
4. Exchange data via predefined messages (Peer Wire
Protocol)
Dominik scala-torrent Gruber • @the_dom
9. Metainfo File
• In Bencode format
• Includes URL of the tracker, list of files / pieces,…
• Info hash has to be calculated from the file to identify
the torrent for communication with tracker and peers
Dominik scala-torrent Gruber • @the_dom
10. Bencoding
• Integers
i<integer encoded in base ten ASCII>e
e.g. i42e
• Strings
<string length>:<string data>
e.g. 4:spam; 12:scala vienna
Dominik scala-torrent Gruber • @the_dom
13. Parsing Bencode with Scala
• Scala Parser Combinators
• Since Scala 2.11 a separate library
• Any structured format can be defined and parsed
through its custom DSL
Dominik scala-torrent Gruber • @the_dom
18. Tracker
• HTTP service which holds information about a torrent
• Communication via GET-Request if a transfer is
started, stopped, or completed
• Responds with a list of peers as a bencoded
dictionary
• Tracker is a single point of failure -> DHT extension
Dominik scala-torrent Gruber • @the_dom
23. PWP: Bitfield
• Sent immediately after handshake (optional)
• <len=0001+X><id=5><bitfield>
• X: length of bitfield
• Bitfield represents the pieces that have successfully
been downloaded
Dominik scala-torrent Gruber • @the_dom
24. PWP: Request
• <len=0013><id=6><index><begin><length>
• Request a block of a piece
• index: Piece number
• begin / length: Specifies block with the piece
• Requests can be canceled via the CANCEL message
Dominik scala-torrent Gruber • @the_dom
25. PWP: Piece
• <len=0009+X><id=7><index><begin><block>
• X: Length of block
• If a piece has been fully received, it is acknowledged
via the HAVE message
Dominik scala-torrent Gruber • @the_dom
27. scala-torrent
• Akka/the actor model is a good fit for this project
• The components can be clearly separated
• Supervision is needed
• TCP communication via Akka I/O
• HTTP communication via spray
Dominik scala-torrent Gruber • @the_dom
29. Connection Handler
object ConnectionHandler {
case class CreatePeerConnection(peer: PeerInformation)
case class PeerConnectionCreated(connection: ActorRef, peer:
PeerInformation)
}
class ConnectionHandler(endpoint: InetSocketAddress, internalPeerId: String)
extends Actor {
import Tcp._
import context.system
import ConnectionHandler._
// Torrent coordinator actor
val coordinator = context.parent
// Start listening to incoming connections
IO(Tcp) ! Tcp.Bind(self, endpoint)
// (…)
}
Dominik scala-torrent Gruber • @the_dom
30. Connection Handler
class ConnectionHandler(endpoint: InetSocketAddress, internalPeerId: String)
extends Actor {
// (…)
override def receive = {
case CommandFailed(_: Bind) =>
// TODO: Handle failure
case c @ Connected(remoteAddress, _) =>
val handler = createPeerConnectionActor(remoteAddress)
sender ! Register(handler)
case CreatePeerConnection(peer) =>
val peerConnection = createPeerConnectionActor(peer.inetSocketAddress)
sender ! PeerConnectionCreated(peerConnection, peer)
}
private def createPeerConnectionActor(remoteAddress: InetSocketAddress) =
context.actorOf(Props(classOf[PeerConnection], remoteAddress,
internalPeerId, coordinator), "peer-connection-" +
remoteAddress.toString.replace("/", ""))
}
Dominik scala-torrent Gruber • @the_dom
31. Peer Connection
class PeerConnection(remoteAddress: InetSocketAddress, internalPeerId: String,
coordinator: ActorRef) extends Actor {
// (…)
override def receive: Receive = initialStage
def initialStage: Receive = {
case AttachToTorrent(t, m) =>
torrent = Some(t)
metainfo = Some(m)
case SendHandshake if torrent.isDefined =>
if (connection.isDefined) sendHandshake()
else {
IO(Tcp) ! Connect(remoteAddress)
context become awaitConnectionForHandshake
}
case Received(data) => handleHandshakeIn(data)
case PeerClosed => handlePeerClosed()
}
// (…)
}
Dominik scala-torrent Gruber • @the_dom
32. Peer Connection
class PeerConnection(remoteAddress: InetSocketAddress, internalPeerId:
String, coordinator: ActorRef) extends Actor {
// (…)
def handleHandshakeIn(data: ByteString) = {
Handshake.unmarshal(data.toVector) match {
case Some(handshake: Handshake) =>
connection = Some(context.sender())
if (torrent.isDefined) context become connected
else coordinator ! IncomingPeerConnection(self, handshake)
case None =>
// TODO: Handle failure
}
}
def sendHandshake() = {
val handshake = Handshake(metainfo.get.info.infoHash, internalPeerId)
connection.get ! Write(ByteString(handshake.marshal.toArray))
}
}
Dominik scala-torrent Gruber • @the_dom
33. Current Status
• Bencode parsing (and encoding)
• Communication with tracker
• Modelling of the PWP messages
• Handshake with clients
• TODO: File exchange (= the core)
Dominik scala-torrent Gruber • @the_dom