/BitTorrent311

An experimental BitTorrent client in Python 3.5

Primary LanguagePythonApache License 2.0Apache-2.0

pieces

An experimental BitTorrent client implemented in Python 3 using asyncio.

The client is not a practical BitTorrent client, it lacks too many features to really be useful. It was implemented for fun in order to learn more about BitTorrent as well as Python's asyncio library.

See http://markuseliasson.se/article/bittorrent-in-python/ for a walkthrough on the BitTorrent protocol and how pieces works under the hood.

The client currently only support downloading of data, although adding the remaining features regarding seeding and multi-file should not be that hard.

Current features:

  • Download pieces (leeching)
  • Contact tracker periodically
  • Seed (upload) pieces
  • Support multi-file torrents
  • Resume a download

Even though it's not practical at this point, feel free to learn from it, steal from it, improve it, laugh at it or just ignore it.

Known issues:

  • Sometimes the client hangs at startup. It seems to relate to the number of concurrent peer connections.

Getting started

Install the needed dependencies and run the unit tests with:

$ make init
$ make test

In order to download a torrent file, run this command:

$ python pieces.py -v tests/data/bootfloppy-utils.img.torrent

If everything goes well, your torrent should be downloaded and the program terminated. You can stop the client using Ctrl + C.

Design considerations

The purpose with implementing this client was to learn myself some asyncio (and other Python 3.5 features, such as type hinting) together with my old itch of implementing the BitTorrent protocol.

Thus, the code have been written to be as clear and simple as possible, not bothering about efficiency or performance. E.g. the pieces are all requested in order, not implementing a rares first algorithm, and the pieces are all kept in memory until the entire torrent is downloaded.

Code walkthrough

The pieces.client.TorrentClient is the center piece, it:

  • Connects to the tracker in order to receive the peers to connect to.

  • Based on that result, creates a Queue of peers that can be connected to.

  • Determine the order in which the pieces should be requested from the remote peers.

  • Shuts down the client once the download is complete.

The strategy on which piece to request next and the assembly of retrieved pieces is implemented in the pieces.client.PieceManager. As previously stated, the strategy implemented is the simplest one possible.

Notice, the file writing is synchronous something that could be improved.

The BitTorrent specifics is implemented in the pieces.protocol module where the pieces.protocol.PeerConnection sets up a connection to one of the remote peers retrieved from the tracker. This class handles the control flow of messages between the two peers.

BitTorrent is a binary protocol, and all decoding of messages is implemented as a async iterator under he name pieces.protocol.PeerStreamIterator. The async part is that this iterator will keep reading and parsing the raw data received from the socket until the connection is closed.

Each of BitTorrents messages is implemented as separate classes, each with a encode and a decode method. However, since this client currently does not support seeding - not all of the messages goes in both ways.

References

There is plenty of information on how to write a BitTorrent client available on the Internet. These two articles were the real enablers for my implementation:

Asyncio is fairly new and I have not seen that many articles about it, at least not where the code examples are a little bit more elaborate than having a few coroutines sleep. Out of the ones I read and can recommend these are on the top of my list:

License

The client is released under the Apache v2 license, see LICENCE.