<!DOCTYPE html><html><head><metacharSet="UTF-8"/><title>Building a BitTorrent Client</title><metaname="description"content="Learn everything you need to know about BitTorrent by writing a client in Go"/><metaname="author"content="Kamran Ahmed"/><metaname="keywords"content="roadmap,developer roadmaps,developer roadmap,frontend developer,frontend developer roadmap,frontend,frontend roadmap,backend,backend developer,backend developer roadmap,devops,devops roadmap,fullstack developer roadmap,guide to becoming a developer,sre roadmap,sre,operations roadmap,qa roadmap,android roadmap,android developer roadmap,react roadmap,react developer roadmap,dba roadmap,postgresql dba roadmap"/><metaname="viewport"content="width=device-width, user-scalable=yes, initial-scale=1.0, maximum-scale=3.0, minimum-scale=1.0"/><metahttp-equiv="Content-Language"content="en"/><metaproperty="og:title"content="Building a BitTorrent Client"/><metaproperty="og:description"content="Learn everything you need to know about BitTorrent by writing a client in Go"/><metaproperty="og:image"content="https://roadmap.sh/brand-square.png"/><metaproperty="og:url"content="https://roadmap.sh"/><metaproperty="og:type"content="website"/><metaproperty="article:publisher"content="https://facebook.com/kamranahmedse"/><metaproperty="og:site_name"content="roadmap.sh"/><metaproperty="article:author"content="Kamran Ahmed"/><metaname="twitter:card"content="summary"/><metaname="twitter:site"content="@kamranahmedse"/><metaname="twitter:title"content="Building a BitTorrent Client"/><metaname="twitter:description"content="Learn everything you need to know about BitTorrent by writing a client in Go"/><metaname="twitter:image"content="https://roadmap.sh/brand-square.png"/><metaname="twitter:image:alt"content="roadmap.sh"/><metaname="mobile-web-app-capable"content="yes"/><metaname="apple-mobile-web-app-capable"content="yes"/><metaname="apple-mobile-web-app-status-bar-style"content="black-translucent"/><linkrel="apple-touch-icon"sizes="180x180"href="/manifest/apple-touch-icon.png"/><metaname="msapplication-TileColor"content="#101010"/><metaname="theme-color"content="#848a9a"/><linkrel="manifest"href="/manifest/manifest.json"/><linkrel="icon"type="image/png"sizes="32x32"href="/manifest/icon32.png"/><linkrel="icon"type="image/png"sizes="16x16"href="/manifest/icon16.png"/><linkrel="shortcut icon"href="/manifest/favicon.ico"type="image/x-icon"/><linkrel="icon"href="/manifest/favicon.ico"type="image/x-icon"/><scriptasync=""src="https://www.googletagmanager.com/gtag/js?id=UA-139582634-1"></script><script>
</code></pre><pclass="sc-bdvvtL kiIUjN">That mess is encoded in a format called <strong>Bencode</strong> (pronounced <em>bee-encode</em>), and we'll need to decode it.</p><pclass="sc-bdvvtL kiIUjN">Bencode can encode roughly the same types of structures as JSON—strings, integers, lists, and dictionaries. Bencoded data is not as human-readable/writable as JSON, but it can efficiently handle binary data and it's really simple to parse from a stream. Strings come with a length prefix, and look like <code>4:spam</code>. Integers go between <em>start</em> and <em>end</em> markers, so <code>7</code> would encode to <code>i7e</code>. Lists and dictionaries work in a similar way: <code>l4:spami7ee</code> represents <code>['spam', 7]</code>, while <code>d4:spami7ee</code> means <code>{spam: 7}</code>.</p><pclass="sc-bdvvtL kiIUjN">In a prettier format, our .torrent file looks like this:</p><preclass="sc-furwcr jiGIaj language-markdown"><codeclass="chakra-code language-markdown css-4m8w8z">d
8:announce
41:http://bttracker.debian.org:6969/announce
7:comment
35:"Debian CD from cdimage.debian.org"
13:creation date
i1573903810e
4:info
d
6:length
i351272960e
4:name
31:debian-10.2.0-amd64-netinst.iso
12:piece length
i262144e
6:pieces
26800:<3A><1F><0F><><EFBFBD>PS<50>^<5E><> (binary blob of the hashes of each piece)
e
e
</code></pre><pclass="sc-bdvvtL kiIUjN">In this file, we can spot the URL of the tracker, the creation date (as a Unix timestamp), the name and size of the file, and a big binary blob containing the SHA-1 hashes of each <strong>piece</strong>, which are equally-sized parts of the file we want to download. The exact size of a piece varies between torrents, but they are usually somewhere between 256KB and 1MB. This means that a large file might be made up of <em>thousands</em> of pieces. We'll download these pieces from our peers, check them against the hashes from our torrent file, assemble them together, and boom, we've got a file!</p><pclass="sc-bdvvtL kiIUjN"><imgsrc="/guides/torrent-client/pieces.png"alt=""illustration of a file being cut with scissors into multiple pieces, starting with piece 0"class="sc-iqseJM bIxQHp"/></p><pclass="sc-bdvvtL kiIUjN">This mechanism allows us to verify the integrity of each piece as we go. It makes BitTorrent resistant to accidental corruption or intentional <strong>torrent poisoning</strong>. Unless an attacker is capable of breaking SHA-1 with a preimage attack, we will get exactly the content we asked for.</p><pclass="sc-bdvvtL kiIUjN">It would be really fun to write a bencode parser, but parsing isn't our focus today. But I found Fredrik Lundh's <ahref="https://effbot.org/zone/bencode.htm"target="_blank"rel="nofollow"class="sc-crHmcD gJMBjK">50 line parser</a> to be especially illuminating. For this project, I used <ahref="https://github.com/jackpal/bencode-go"target="_blank"rel="nofollow"class="sc-crHmcD gJMBjK">github.com/jackpal/bencode-go</a>:</p><preclass="sc-furwcr jiGIaj language-go"><codeclass="chakra-code language-go css-4m8w8z"><spanclass="token keyword">import</span><spanclass="token punctuation">(</span>
</code></pre><pclass="sc-bdvvtL kiIUjN">Because I like to keep my structures relatively flat, and I like to keep my application structs separate from my serialization structs, I exported a different, flatter struct named <code>TorrentFile</code> and wrote a few helper functions to convert between the two.</p><pclass="sc-bdvvtL kiIUjN">Notably, I split <code>pieces</code> (previously a string) into a slice of hashes (each <code>[20]byte</code>) so that I can easily access individual hashes later. I also computed the SHA-1 hash of the entire bencoded <code>info</code> dict (the one which contained the name, size, and piece hashes). We know this as the <strong>infohash</strong> and it uniquely identifies files when we talk to trackers and peers. More on this later.</p><pclass="sc-bdvvtL kiIUjN"><imgsrc="/guides/torrent-client/info-hash.png"alt="a name tag saying 'Hello my name is 86d4c80024a469be4c50bc5a102cf71780310074'"class="sc-iqseJM bIxQHp"/></p><preclass="sc-furwcr jiGIaj language-go"><codeclass="chakra-code language-go css-4m8w8z"><spanclass="token keyword">type</span> TorrentFile <spanclass="token keyword">struct</span><spanclass="token punctuation">{</span>
</code></pre><h3class="sc-dkPtRN sc-eCImPb dKUOdJ cabxUq">Retrieving peers from the tracker</h3><pclass="sc-bdvvtL kiIUjN">Now that we have information about the file and its tracker, let's talk to the tracker to <strong>announce</strong> our presence as a peer and to retrieve a list of other peers. We just need to make a GET request to the <code>announce</code> URL supplied in the .torrent file, with a few query parameters:</p><preclass="sc-furwcr jiGIaj language-go"><codeclass="chakra-code language-go css-4m8w8z"><spanclass="token keyword">func</span><spanclass="token punctuation">(</span>t <spanclass="token operator">*</span>TorrentFile<spanclass="token punctuation">)</span><spanclass="token function">buildTrackerURL</span><spanclass="token punctuation">(</span>peerID <spanclass="token punctuation">[</span><spanclass="token number">20</span><spanclass="token punctuation">]</span><spanclass="token builtin">byte</span><spanclass="token punctuation">,</span> port <spanclass="token builtin">uint16</span><spanclass="token punctuation">)</span><spanclass="token punctuation">(</span><spanclass="token builtin">string</span><spanclass="token punctuation">,</span><spanclass="token builtin">error</span><spanclass="token punctuation">)</span><spanclass="token punctuation">{</span>
</code></pre><pclass="sc-bdvvtL kiIUjN">The important ones:</p><ulclass="sc-egiyK gfrORe"><liclass="sc-bqiRlB xTMXv"><strong>info_hash</strong>: Identifies the <em>file</em> we're trying to download. It's the infohash we calculated earlier from the bencoded <code>info</code> dict. The tracker will use this to figure out which peers to show us.</li><liclass="sc-bqiRlB xTMXv"><strong>peer_id</strong>: A 20 byte name to identify <em>ourselves</em> to trackers and peers. We'll just generate 20 random bytes for this. Real BitTorrent clients have IDs like <code>-TR2940-k8hj0wgej6ch</code> which identify the client software and version—in this case, TR2940 stands for Transmission client 2.94.</li></ul><pclass="sc-bdvvtL kiIUjN"><imgsrc="/guides/torrent-client/info-hash-peer-id.png"alt="a file with a name tag saying 'info_hash' and a person with a name tag 'peer_id'"class="sc-iqseJM bIxQHp"/></p><h3class="sc-dkPtRN sc-eCImPb dKUOdJ cabxUq">Parsing the tracker response</h3><pclass="sc-bdvvtL kiIUjN">We get back a bencoded response:</p><preclass="sc-furwcr jiGIaj language-markdown"><codeclass="chakra-code language-markdown css-4m8w8z">d
8:interval
i900e
5:peers
252:(another long binary blob)
e
</code></pre><pclass="sc-bdvvtL kiIUjN"><code>Interval</code> tells us how often we're supposed to connect to the tracker again to refresh our list of peers. A value of 900 means we should reconnect every 15 minutes (900 seconds).</p><pclass="sc-bdvvtL kiIUjN"><code>Peers</code> is another long binary blob containing the IP addresses of each peer. It's made out of <strong>groups of six bytes</strong>. The first four bytes in each group represent the peer's IP address—each byte represents a number in the IP. The last two bytes represent the port, as a big-endian <code>uint16</code>. <strong>Big-endian</strong>, or <strong>network order</strong>, means that we can interpret a group of bytes as an integer by just squishing them together left to right. For example, the bytes <code>0x1A</code>, <code>0xE1</code> make <code>0x1AE1</code>, or 6881 in decimal.</p><pclass="sc-bdvvtL kiIUjN"><imgsrc="/guides/torrent-client/address.png"alt="diagram showing how 192, 0, 2, 123, 0x1A, 0xE1 can be interpreted as 192.0.1.123:6881"class="sc-iqseJM bIxQHp"/></p><preclass="sc-furwcr jiGIaj language-go"><codeclass="chakra-code language-go css-4m8w8z"><spanclass="token comment">// Peer encodes connection information for a peer</span>
</code></pre><h2class="sc-dkPtRN sc-hKwDye dKUOdJ kqfgwy">Downloading from peers</h2><pclass="sc-bdvvtL kiIUjN">Now that we have a list of peers, it's time to connect with them and start downloading pieces! We can break down the process into a few steps. For each peer, we want to:</p><ol><liclass="sc-bqiRlB xTMXv">Start a TCP connection with the peer. This is like starting a phone call.</li><liclass="sc-bqiRlB xTMXv">Complete a two-way BitTorrent <strong>handshake</strong>. <em>"Hello?""Hello."</em></li><liclass="sc-bqiRlB xTMXv">Exchange <strong>messages</strong> to download <strong>pieces</strong>. <em>"I'd like piece #231 please."</em></li></ol><h2class="sc-dkPtRN sc-hKwDye dKUOdJ kqfgwy">Start a TCP connection</h2><preclass="sc-furwcr jiGIaj language-go"><codeclass="chakra-code language-go css-4m8w8z">conn<spanclass="token punctuation">,</span> err <spanclass="token operator">:=</span> net<spanclass="token punctuation">.</span><spanclass="token function">DialTimeout</span><spanclass="token punctuation">(</span><spanclass="token string">"tcp"</span><spanclass="token punctuation">,</span> peer<spanclass="token punctuation">.</span><spanclass="token function">String</span><spanclass="token punctuation">(</span><spanclass="token punctuation">)</span><spanclass="token punctuation">,</span><spanclass="token number">3</span><spanclass="token operator">*</span>time<spanclass="token punctuation">.</span>Second<spanclass="token punctuation">)</span>
</code></pre><pclass="sc-bdvvtL kiIUjN">I set a timeout so that I don't waste too much time on peers that aren't going to let me connect. For the most part, it's a pretty standard TCP connection.</p><h3class="sc-dkPtRN sc-eCImPb dKUOdJ cabxUq">Complete the handshake</h3><pclass="sc-bdvvtL kiIUjN">We've just set up a connection with a peer, but we want do a handshake to validate our assumptions that the peer</p><ulclass="sc-egiyK gfrORe"><liclass="sc-bqiRlB xTMXv">can communicate using the BitTorrent protocol</li><liclass="sc-bqiRlB xTMXv">is able to understand and respond to our messages</li><liclass="sc-bqiRlB xTMXv">has the file that we want, or at least knows what we're talking about</li></ul><pclass="sc-bdvvtL kiIUjN"><imgsrc="/guides/torrent-client/handshake.png"alt="Two computers communicating. One asks 'do you speak BitTorrent and have this file?' and the other replies 'I speak BitTorrent and have that file'"class="sc-iqseJM bIxQHp"/></p><pclass="sc-bdvvtL kiIUjN">My father told me that the secret to a good handshake is a firm grip and eye contact. The secret to a good BitTorrent handshake is that it's made up of five parts:</p><ol><liclass="sc-bqiRlB xTMXv">The length of the protocol identifier, which is always 19 (0x13 in hex)</li><liclass="sc-bqiRlB xTMXv">The protocol identifier, called the <strong>pstr</strong> which is always <code>BitTorrent protocol</code></li><liclass="sc-bqiRlB xTMXv">Eight <strong>reserved bytes</strong>, all set to 0. We'd flip some of them to 1 to indicate that we support certain <ahref="http://www.bittorrent.org/beps/bep_0010.html"target="_blank"rel="nofollow"class="sc-crHmcD gJMBjK">extensions</a>. But we don't, so we'll keep them at 0.</li><liclass="sc-bqiRlB xTMXv">The <strong>infohash</strong> that we calculated earlier to identify which file we want</li><liclass="sc-bqiRlB xTMXv">The <strong>Peer ID</strong> that we made up to identify ourselves</li></ol><pclass="sc-bdvvtL kiIUjN">Put together, a handshake string might look like this:</p><preclass="sc-furwcr jiGIaj language-markdown"><codeclass="chakra-code language-markdown css-4m8w8z">\x13BitTorrent protocol\x00\x00\x00\x00\x00\x00\x00\x00\x86\xd4\xc8\x00\x24\xa4\x69\xbe\x4c\x50\xbc\x5a\x10\x2c\xf7\x17\x80\x31\x00\x74-TR2940-k8hj0wgej6ch
</code></pre><pclass="sc-bdvvtL kiIUjN">After we send a handshake to our peer, we should receive a handshake back in the same format. The infohash we get back should match the one we sent so that we know that we're talking about the same file. If everything goes as planned, we're good to go. If not, we can sever the connection because there's something wrong. <em>"Hello?""这是谁? 你想要什么?""Okay, wow, wrong number."</em></p><pclass="sc-bdvvtL kiIUjN">In our code, let's make a struct to represent a handshake, and write a few methods for serializing and reading them:</p><preclass="sc-furwcr jiGIaj language-go"><codeclass="chakra-code language-go css-4m8w8z"><spanclass="token comment">// A Handshake is a special message that a peer uses to identify itself</span>
<spanclass="token comment">// Do Serialize(), but backwards</span>
<spanclass="token comment">// ...</span>
<spanclass="token punctuation">}</span>
</code></pre><h3class="sc-dkPtRN sc-eCImPb dKUOdJ cabxUq">Send and receive messages</h3><pclass="sc-bdvvtL kiIUjN">Once we've completed the initial handshake, we can send and receive <strong>messages</strong>. Well, not quite—if the other peer isn't ready to accept messages, we can't send any until they tell us they're ready. In this state, we're considered <strong>choked</strong> by the other peer. They'll send us an <strong>unchoke</strong> message to let us know that we can begin asking them for data. By default, we assume that we're choked until proven otherwise.</p><pclass="sc-bdvvtL kiIUjN">Once we've been unchoked, we can then begin sending <strong>requests</strong> for pieces, and they can send us messages back containing pieces.</p><pclass="sc-bdvvtL kiIUjN"><imgsrc="/guides/torrent-client/choke.png"alt=""A cartoon in which person 1 says 'hello I would like piece number—' and person 2 grabs him by the neck and says '00 00 00 01 00 (choke)'"class="sc-iqseJM bIxQHp"/></p><h4class="sc-dkPtRN sc-jRQBWg dKUOdJ gRXaYU">Interpreting messages</h4><pclass="sc-bdvvtL kiIUjN">A message has a length, an <strong>ID</strong> and a <strong>payload</strong>. On the wire, it looks like:</p><pclass="sc-bdvvtL kiIUjN"><imgsrc="/guides/torrent-client/message.png"alt="A message with 4 byte for the length, 1 byte for ID, and an optional payload"class="sc-iqseJM bIxQHp"/></p><pclass="sc-bdvvtL kiIUjN">A message starts with a length indicator which tells us how many bytes long the message will be. It's a 32-bit integer, meaning it's made out of four bytes smooshed together in big-endian order. The next byte, the <strong>ID</strong>, tells us which type of message we're receiving—for example, a <code>2</code> byte means "interested." Finally, the optional <strong>payload</strong> fills out the remaining length of the message.</p><preclass="sc-furwcr jiGIaj language-go"><codeclass="chakra-code language-go css-4m8w8z"><spanclass="token keyword">type</span> messageID <spanclass="token builtin">uint8</span>
</code></pre><pclass="sc-bdvvtL kiIUjN">To read a message from a stream, we just follow the format of a message. We read four bytes and interpret them as a <code>uint32</code> to get the <strong>length</strong> of the message. Then, we read that number of bytes to get the <strong>ID</strong> (the first byte) and the <strong>payload</strong> (the remaining bytes).</p><preclass="sc-furwcr jiGIaj language-go"><codeclass="chakra-code language-go css-4m8w8z"><spanclass="token comment">// Read parses a message from a stream. Returns `nil` on keep-alive message</span>
</code></pre><h4class="sc-dkPtRN sc-jRQBWg dKUOdJ gRXaYU">Bitfields</h4><pclass="sc-bdvvtL kiIUjN">One of the most interesting types of message is the <strong>bitfield</strong>, which is a data structure that peers use to efficiently encode which pieces they are able to send us. A bitfield looks like a byte array, and to check which pieces they have, we just need to look at the positions of the <em>bits</em> set to 1. You can think of it like the digital equivalent of a coffee shop loyalty card. We start with a blank card of all <code>0</code>, and flip bits to <code>1</code> to mark their positions as "stamped."</p><pclass="sc-bdvvtL kiIUjN"><imgsrc="/guides/torrent-client/bitfield.png"alt="a coffee shop loyalty card with eight slots, with stamps on the first four slots and a stamp on the second to last slot, represented as 11110010"class="sc-iqseJM bIxQHp"/></p><pclass="sc-bdvvtL kiIUjN">By working with <em>bits</em> instead of <em>bytes</em>, this data structure is super compact. We can stuff information about eight pieces in the space of a single byte—the size of a <code>bool</code>. The tradeoff is that accessing values becomes a little more tricky. The smallest unit of memory that computers can address are bytes, so to get to our bits, we have to do some bitwise manipulation:</p><preclass="sc-furwcr jiGIaj language-go"><codeclass="chakra-code language-go css-4m8w8z"><spanclass="token comment">// A Bitfield represents the pieces that a peer has</span>
</code></pre><h3class="sc-dkPtRN sc-eCImPb dKUOdJ cabxUq">Putting it all together</h3><pclass="sc-bdvvtL kiIUjN">We now have all the tools we need to download a torrent: we have a list of peers obtained from the tracker, and we can communicate with them by dialing a TCP connection, initiating a handshake, and sending and receiving messages. Our last big problems are handling the <strong>concurrency</strong> involved in talking to multiple peers at once, and managing the <strong>state</strong> of our peers as we interact with them. These are both classically Hard problems.</p><h4class="sc-dkPtRN sc-jRQBWg dKUOdJ gRXaYU">Managing concurrency: channels as queues</h4><pclass="sc-bdvvtL kiIUjN">In Go, we <ahref="https://blog.golang.org/share-memory-by-communicating"target="_blank"rel="nofollow"class="sc-crHmcD gJMBjK">share memory by communicating</a>, and we can think of a Go channel as a cheap thread-safe queue.</p><pclass="sc-bdvvtL kiIUjN">We'll set up two channels to synchronize our concurrent workers: one for dishing out work (pieces to download) between peers, and another for collecting downloaded pieces. As downloaded pieces come in through the results channel, we can copy them into a buffer to start assembling our complete file.</p><preclass="sc-furwcr jiGIaj language-go"><codeclass="chakra-code language-go css-4m8w8z"><spanclass="token comment">// Init queues for workers to retrieve work and send results</span>
</code></pre><pclass="sc-bdvvtL kiIUjN">We'll spawn a worker goroutine for each peer we've received from the tracker. It'll connect and handshake with the peer, and then start retrieving work from the <code>workQueue</code>, attempting to download it, and sending downloaded pieces back through the <code>results</code> channel.</p><pclass="sc-bdvvtL kiIUjN"><imgsrc="/guides/torrent-client/download.png"alt="a flow chart of the download strategy"class="sc-iqseJM bIxQHp"/></p><preclass="sc-furwcr jiGIaj language-go"><codeclass="chakra-code language-go css-4m8w8z"><spanclass="token keyword">func</span><spanclass="token punctuation">(</span>t <spanclass="token operator">*</span>Torrent<spanclass="token punctuation">)</span><spanclass="token function">startDownloadWorker</span><spanclass="token punctuation">(</span>peer peers<spanclass="token punctuation">.</span>Peer<spanclass="token punctuation">,</span> workQueue <spanclass="token keyword">chan</span><spanclass="token operator">*</span>pieceWork<spanclass="token punctuation">,</span> results <spanclass="token keyword">chan</span><spanclass="token operator">*</span>pieceResult<spanclass="token punctuation">)</span><spanclass="token punctuation">{</span>
</code></pre><h4class="sc-dkPtRN sc-jRQBWg dKUOdJ gRXaYU">Managing state</h4><pclass="sc-bdvvtL kiIUjN">We'll keep track of each peer in a struct, and modify that struct as we read messages. It'll include data like how much we've downloaded from the peer, how much we've requested from them, and whether we're choked. If we wanted to scale this further, we could formalize this as a finite state machine. But a struct and a switch are good enough for now.</p><preclass="sc-furwcr jiGIaj language-go"><codeclass="chakra-code language-go css-4m8w8z"><spanclass="token keyword">type</span> pieceProgress <spanclass="token keyword">struct</span><spanclass="token punctuation">{</span>
</code></pre><h4class="sc-dkPtRN sc-jRQBWg dKUOdJ gRXaYU">Time to make requests!</h4><pclass="sc-bdvvtL kiIUjN">Files, pieces, and piece hashes aren't the full story—we can go further by breaking down pieces into <strong>blocks</strong>. A block is a part of a piece, and we can fully define a block by the <strong>index</strong> of the piece it's part of, its byte <strong>offset</strong> within the piece, and its <strong>length</strong>. When we make requests for data from peers, we are actually requesting <em>blocks</em>. A block is usually 16KB large, meaning that a single 256 KB piece might actually require 16 requests.</p><pclass="sc-bdvvtL kiIUjN">A peer is supposed to sever the connection if they receive a request for a block larger than 16KB. However, based on my experience, they're often perfectly happy to satisfy requests up to 128KB. I only got moderate gains in overall speed with larger block sizes, so it's probably better to stick with the spec.</p><h4class="sc-dkPtRN sc-jRQBWg dKUOdJ gRXaYU">Pipelining</h4><pclass="sc-bdvvtL kiIUjN">Network round-trips are expensive, and requesting each block one by one will absolutely tank the performance of our download. Therefore, it's important to <strong>pipeline</strong> our requests such that we keep up a constant pressure of some number of unfulfilled requests. This can increase the throughput of our connection by an order of magnitude.</p><pclass="sc-bdvvtL kiIUjN"><imgsrc="/guides/torrent-client/pipelining.png"alt="Two email threads simulating peer connections. The thread on the left shows a request followed by a reply, repeated three times. The thread on the left sends three requests, and receives three replies in quick succession."class="sc-iqseJM bIxQHp"/></p><pclass="sc-bdvvtL kiIUjN">Classically, BitTorrent clients kept a queue of five pipelined requests, and that's the value I'll be using. I found that increasing it can up to double the speed of a download. Newer clients use an <ahref="https://luminarys.com/posts/writing-a-bittorrent-client.html"target="_blank"rel="nofollow"class="sc-crHmcD gJMBjK">adaptive</a> queue size to better accommodate modern network speeds and conditions. This is definitely a parameter worth tweaking, and it's pretty low hanging fruit for future performance optimization.</p><preclass="sc-furwcr jiGIaj language-go"><codeclass="chakra-code language-go css-4m8w8z"><spanclass="token comment">// MaxBlockSize is the largest number of bytes a request can ask for</span>
</code></pre><h4class="sc-dkPtRN sc-jRQBWg dKUOdJ gRXaYU">main.go</h4><pclass="sc-bdvvtL kiIUjN">This is a short one. We're almost there.</p><preclass="sc-furwcr jiGIaj language-go"><codeclass="chakra-code language-go css-4m8w8z"><spanclass="token keyword">package</span> main
</code></pre><scriptid="asciicast-xqRSB0Jec8RN91Zt89rbb9PcL"src="https://asciinema.org/a/xqRSB0Jec8RN91Zt89rbb9PcL.js"async=""></script><h2class="sc-dkPtRN sc-hKwDye dKUOdJ kqfgwy">This isn't the full story</h2><pclass="sc-bdvvtL kiIUjN">For brevity, I included only a few of the important snippets of code. Notably, I left out all the glue code, parsing, unit tests, and the boring parts that build character. View my <ahref="https://github.com/veggiedefender/torrent-client"target="_blank"rel="nofollow"class="sc-crHmcD gJMBjK">full implementation</a> if you're interested.</p><span></span></div></div><styledata-emotion="css zeuzl6">.css-zeuzl6{background:var(--chakra-colors-white);border-top-width:1px;padding-top:45px;padding-bottom:60px;text-align:center;}@mediascreenand(min-width:30em){.css-zeuzl6{padding-top:45px;padding-bottom:60px;}}@mediascreenand(min-width:48em){.css-zeuzl6{padding-top:70px;padding-bottom:90px;}}</style><divclass="css-zeuzl6"><divclass="chakra-container css-nm5t63"><styledata-emotion="css 482acf">.css-482acf{font-family:var(--chakra-fonts-heading);font-weight:var(--chakra-fontWeights-bold);font-size:25px;line-height:1.33;margin-bottom:10px;}@mediascreenand(min-width:30em){.css-482acf{font-size:25px;margin-bottom:10px;}}@mediascreenand(min-width:48em){.css-482acf{font-size:35px;line-height:1.2;margin-bottom:20px;}}</style><h2class="chakra-heading css-482acf">Open Source</h2><styledata-emotion="css tmji1h">.css-tmji1h{line-height:26px;font-size:15px;margin-bottom:20px;}@mediascreenand(min-width:30em){.css-tmji1h{font-size:15px;}}@mediascreenand(min-width:48em){.css-tmji1h{font-size:16px;}}</style><pclass="chakra-text css-tmji1h">The project is OpenSource,<styledata-emotion="css 1om4i6h">.css-1om4i6h{transition-property:var(--chakra-transition-property-common);transition-duration:var(--chakra-transition-duration-fast);transition-timing-function:var(--chakra-transition-easing-ease-out);cursor:pointer;-webkit-text-decoration:none;text-decoration:none;outline:2pxsolidtransparent;outline-offset:2px;color:inherit;border-bottom-width:1px;font-weight:600;}.css-1om4i6h:hover,.css-1om4i6h[data-hover]{-webkit-text-decoration:none;text-decoration:none;}.css-1om4i6h:focus,.css-1om4i6h[data-focus]{box-shadow:var(--chakra-shadows-outline);}</style><atarget="_blank"class="chakra-link css-1om4i6h"href="https://github.com/search?o=desc&q=stars%3A%3E100000&s=stars&type=Repositories">7th most starred project on GitHub</a> and is visited by hundreds of thousands of developers every month.</p><iframesrc="https://ghbtns.com/github-btn.html?user=kamranahmedse&repo=developer-roadmap&type=star&count=true&size=large"frameBorder="0"scrolling="0"width="170"height="30"style="margin:auto;margin-bottom:30px"title="GitHub"></iframe><styledata-emotion="css mz2q9v">.css-mz2q9v{line-height:25px;font-size:15px;margin-bottom:15px;}@mediascreenand(min-width:30em){.css-mz2q9v{line-height:25px;font-size:15px;}}@mediascreenand(min-width:48em){.css-mz2q9v{line-height:26px;font-size:16px;}}</style><pclass="chakra-text css-mz2q9v">A considerable amount of my time is spent doing unpaid community work on things that I hope will help humanity in some way. Your sponsorship helps me continue to produce more open-source and free educational material consumed by hundreds of thousands of developers every month.</p><divclass="css-0"><iframesrc="https://ghbtns.com/github-btn.html?user=kamranahmedse&type=sponsor&size=large"frameBorder="0"scrolling="0"width="260"height="30"title="GitHub"style="margin:auto"></iframe></div></div></div><styledata-emotion="css llwly4">.css-llwly4{border-top-width:1px;padding-top:40px;padding-bottom:40px;text-align:left;background:var(--chakra-colors-brand-footer);}@mediascreenand(min-width:30em){.css-llwly4{padding-top:40px;padding-bottom:45px;}}@mediascreenand(min-width:48em){.css-llwly4{padding-top:70px;padding-bottom:80px;}}</style><divclass="css-llwly4"><divclass="chakra-container css-nm5t63"><sty