391 lines
160 KiB
HTML
391 lines
160 KiB
HTML
<!DOCTYPE html><html><head><meta charSet="UTF-8"/><title>Building a BitTorrent Client</title><meta name="description" content="Learn everything you need to know about BitTorrent by writing a client in Go"/><meta name="author" content="Kamran Ahmed"/><meta name="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"/><meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1.0, maximum-scale=3.0, minimum-scale=1.0"/><meta http-equiv="Content-Language" content="en"/><meta property="og:title" content="Building a BitTorrent Client"/><meta property="og:description" content="Learn everything you need to know about BitTorrent by writing a client in Go"/><meta property="og:image" content="https://roadmap.sh/brand-square.png"/><meta property="og:url" content="https://roadmap.sh"/><meta property="og:type" content="website"/><meta property="article:publisher" content="https://facebook.com/kamranahmedse"/><meta property="og:site_name" content="roadmap.sh"/><meta property="article:author" content="Kamran Ahmed"/><meta name="twitter:card" content="summary"/><meta name="twitter:site" content="@kamranahmedse"/><meta name="twitter:title" content="Building a BitTorrent Client"/><meta name="twitter:description" content="Learn everything you need to know about BitTorrent by writing a client in Go"/><meta name="twitter:image" content="https://roadmap.sh/brand-square.png"/><meta name="twitter:image:alt" content="roadmap.sh"/><meta name="mobile-web-app-capable" content="yes"/><meta name="apple-mobile-web-app-capable" content="yes"/><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/><link rel="apple-touch-icon" sizes="180x180" href="/manifest/apple-touch-icon.png"/><meta name="msapplication-TileColor" content="#101010"/><meta name="theme-color" content="#848a9a"/><link rel="manifest" href="/manifest/manifest.json"/><link rel="icon" type="image/png" sizes="32x32" href="/manifest/icon32.png"/><link rel="icon" type="image/png" sizes="16x16" href="/manifest/icon16.png"/><link rel="shortcut icon" href="/manifest/favicon.ico" type="image/x-icon"/><link rel="icon" href="/manifest/favicon.ico" type="image/x-icon"/><script async="" src="https://www.googletagmanager.com/gtag/js?id=UA-139582634-1"></script><script>
|
||
window.dataLayer = window.dataLayer || [];
|
||
function gtag(){dataLayer.push(arguments);}
|
||
gtag('js', new Date());
|
||
gtag('config', 'UA-139582634-1');
|
||
</script><meta name="next-head-count" content="34"/><link rel="preload" href="/_next/static/css/9630938f8579f20e.css" as="style"/><link rel="stylesheet" href="/_next/static/css/9630938f8579f20e.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/_next/static/chunks/polyfills-5cd94c89d3acac5f.js"></script><script src="/_next/static/chunks/webpack-378e68e29c265886.js" defer=""></script><script src="/_next/static/chunks/framework-dc33c0b5493501f0.js" defer=""></script><script src="/_next/static/chunks/main-056d531d8af152ad.js" defer=""></script><script src="/_next/static/chunks/pages/_app-981db2c906b1d6be.js" defer=""></script><script src="/_next/static/chunks/680-f2ef73bf59f135ea.js" defer=""></script><script src="/_next/static/chunks/22-14757297dd54265a.js" defer=""></script><script src="/_next/static/chunks/515-79757bf997a2c9cf.js" defer=""></script><script src="/_next/static/chunks/13-b259223214a65539.js" defer=""></script><script src="/_next/static/chunks/pages/guides/%5Bguide%5D-522b73f8763bfcbb.js" defer=""></script><script src="/_next/static/ooSnr_WJpF8nzY9n_DSAx/_buildManifest.js" defer=""></script><script src="/_next/static/ooSnr_WJpF8nzY9n_DSAx/_ssgManifest.js" defer=""></script><script src="/_next/static/ooSnr_WJpF8nzY9n_DSAx/_middlewareManifest.js" defer=""></script><style data-styled="" data-styled-version="5.3.3">.kiIUjN{line-height:27px;font-size:16px;color:black;margin-bottom:18px;}/*!sc*/
|
||
data-styled.g1[id="sc-bdvvtL"]{content:"kiIUjN,"}/*!sc*/
|
||
.dKUOdJ{position:relative;font-size:32px;line-height:40px;font-weight:700;margin:20px 0 10px !important;}/*!sc*/
|
||
.dKUOdJ:hover .sc-gsDKAQ{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}/*!sc*/
|
||
data-styled.g3[id="sc-dkPtRN"]{content:"dKUOdJ,"}/*!sc*/
|
||
.kqfgwy{font-size:30px;}/*!sc*/
|
||
data-styled.g4[id="sc-hKwDye"]{content:"kqfgwy,"}/*!sc*/
|
||
.cabxUq{margin:22px 0 8px;font-size:28px;}/*!sc*/
|
||
data-styled.g5[id="sc-eCImPb"]{content:"cabxUq,"}/*!sc*/
|
||
.gRXaYU{margin:18px 0 8px;font-size:24px;}/*!sc*/
|
||
data-styled.g6[id="sc-jRQBWg"]{content:"gRXaYU,"}/*!sc*/
|
||
.jiGIaj{margin:25px -25px 25px -25px !important;padding:20px 25px !important;border-radius:10px;line-height:1.5 !important;}/*!sc*/
|
||
.jiGIaj code{background:transparent;}/*!sc*/
|
||
data-styled.g9[id="sc-furwcr"]{content:"jiGIaj,"}/*!sc*/
|
||
.bIxQHp{max-width:100%;margin:25px auto;display:block;}/*!sc*/
|
||
data-styled.g13[id="sc-iqseJM"]{content:"bIxQHp,"}/*!sc*/
|
||
.gJMBjK{font-weight:600;-webkit-text-decoration:underline;text-decoration:underline;}/*!sc*/
|
||
data-styled.g14[id="sc-crHmcD"]{content:"gJMBjK,"}/*!sc*/
|
||
.gfrORe{margin-left:40px;margin-bottom:18px;}/*!sc*/
|
||
.gfrORe ul{margin-top:18px;}/*!sc*/
|
||
data-styled.g15[id="sc-egiyK"]{content:"gfrORe,"}/*!sc*/
|
||
.xTMXv{margin-bottom:7px;}/*!sc*/
|
||
data-styled.g16[id="sc-bqiRlB"]{content:"xTMXv,"}/*!sc*/
|
||
</style></head><body><div id="__next" data-reactroot=""><style data-emotion="css-global brzcv3">:host,:root{--chakra-ring-inset:var(--chakra-empty,/*!*/ /*!*/);--chakra-ring-offset-width:0px;--chakra-ring-offset-color:#fff;--chakra-ring-color:rgba(66, 153, 225, 0.6);--chakra-ring-offset-shadow:0 0 #0000;--chakra-ring-shadow:0 0 #0000;--chakra-space-x-reverse:0;--chakra-space-y-reverse:0;--chakra-colors-transparent:transparent;--chakra-colors-current:currentColor;--chakra-colors-black:#000000;--chakra-colors-white:#FFFFFF;--chakra-colors-whiteAlpha-50:rgba(255, 255, 255, 0.04);--chakra-colors-whiteAlpha-100:rgba(255, 255, 255, 0.06);--chakra-colors-whiteAlpha-200:rgba(255, 255, 255, 0.08);--chakra-colors-whiteAlpha-300:rgba(255, 255, 255, 0.16);--chakra-colors-whiteAlpha-400:rgba(255, 255, 255, 0.24);--chakra-colors-whiteAlpha-500:rgba(255, 255, 255, 0.36);--chakra-colors-whiteAlpha-600:rgba(255, 255, 255, 0.48);--chakra-colors-whiteAlpha-700:rgba(255, 255, 255, 0.64);--chakra-colors-whiteAlpha-800:rgba(255, 255, 255, 0.80);--chakra-colors-whiteAlpha-900:rgba(255, 255, 255, 0.92);--chakra-colors-blackAlpha-50:rgba(0, 0, 0, 0.04);--chakra-colors-blackAlpha-100:rgba(0, 0, 0, 0.06);--chakra-colors-blackAlpha-200:rgba(0, 0, 0, 0.08);--chakra-colors-blackAlpha-300:rgba(0, 0, 0, 0.16);--chakra-colors-blackAlpha-400:rgba(0, 0, 0, 0.24);--chakra-colors-blackAlpha-500:rgba(0, 0, 0, 0.36);--chakra-colors-blackAlpha-600:rgba(0, 0, 0, 0.48);--chakra-colors-blackAlpha-700:rgba(0, 0, 0, 0.64);--chakra-colors-blackAlpha-800:rgba(0, 0, 0, 0.80);--chakra-colors-blackAlpha-900:rgba(0, 0, 0, 0.92);--chakra-colors-gray-50:#F7FAFC;--chakra-colors-gray-100:#EDF2F7;--chakra-colors-gray-200:#E2E8F0;--chakra-colors-gray-300:#CBD5E0;--chakra-colors-gray-400:#A0AEC0;--chakra-colors-gray-500:#718096;--chakra-colors-gray-600:#4A5568;--chakra-colors-gray-700:#2D3748;--chakra-colors-gray-800:#1A202C;--chakra-colors-gray-900:#171923;--chakra-colors-red-50:#FFF5F5;--chakra-colors-red-100:#FED7D7;--chakra-colors-red-200:#FEB2B2;--chakra-colors-red-300:#FC8181;--chakra-colors-red-400:#F56565;--chakra-colors-red-500:#E53E3E;--chakra-colors-red-600:#C53030;--chakra-colors-red-700:#9B2C2C;--chakra-colors-red-800:#822727;--chakra-colors-red-900:#63171B;--chakra-colors-orange-50:#FFFAF0;--chakra-colors-orange-100:#FEEBC8;--chakra-colors-orange-200:#FBD38D;--chakra-colors-orange-300:#F6AD55;--chakra-colors-orange-400:#ED8936;--chakra-colors-orange-500:#DD6B20;--chakra-colors-orange-600:#C05621;--chakra-colors-orange-700:#9C4221;--chakra-colors-orange-800:#7B341E;--chakra-colors-orange-900:#652B19;--chakra-colors-yellow-50:#FFFFF0;--chakra-colors-yellow-100:#FEFCBF;--chakra-colors-yellow-200:#FAF089;--chakra-colors-yellow-300:#F6E05E;--chakra-colors-yellow-400:#ECC94B;--chakra-colors-yellow-500:#D69E2E;--chakra-colors-yellow-600:#B7791F;--chakra-colors-yellow-700:#975A16;--chakra-colors-yellow-800:#744210;--chakra-colors-yellow-900:#5F370E;--chakra-colors-green-50:#F0FFF4;--chakra-colors-green-100:#C6F6D5;--chakra-colors-green-200:#9AE6B4;--chakra-colors-green-300:#68D391;--chakra-colors-green-400:#48BB78;--chakra-colors-green-500:#38A169;--chakra-colors-green-600:#2F855A;--chakra-colors-green-700:#276749;--chakra-colors-green-800:#22543D;--chakra-colors-green-900:#1C4532;--chakra-colors-teal-50:#E6FFFA;--chakra-colors-teal-100:#B2F5EA;--chakra-colors-teal-200:#81E6D9;--chakra-colors-teal-300:#4FD1C5;--chakra-colors-teal-400:#38B2AC;--chakra-colors-teal-500:#319795;--chakra-colors-teal-600:#2C7A7B;--chakra-colors-teal-700:#285E61;--chakra-colors-teal-800:#234E52;--chakra-colors-teal-900:#1D4044;--chakra-colors-blue-50:#ebf8ff;--chakra-colors-blue-100:#bee3f8;--chakra-colors-blue-200:#90cdf4;--chakra-colors-blue-300:#63b3ed;--chakra-colors-blue-400:#4299e1;--chakra-colors-blue-500:#3182ce;--chakra-colors-blue-600:#2b6cb0;--chakra-colors-blue-700:#2c5282;--chakra-colors-blue-800:#2a4365;--chakra-colors-blue-900:#1A365D;--chakra-colors-cyan-50:#EDFDFD;--chakra-colors-cyan-100:#C4F1F9;--chakra-colors-cyan-200:#9DECF9;--chakra-colors-cyan-300:#76E4F7;--chakra-colors-cyan-400:#0BC5EA;--chakra-colors-cyan-500:#00B5D8;--chakra-colors-cyan-600:#00A3C4;--chakra-colors-cyan-700:#0987A0;--chakra-colors-cyan-800:#086F83;--chakra-colors-cyan-900:#065666;--chakra-colors-purple-50:#FAF5FF;--chakra-colors-purple-100:#E9D8FD;--chakra-colors-purple-200:#D6BCFA;--chakra-colors-purple-300:#B794F4;--chakra-colors-purple-400:#9F7AEA;--chakra-colors-purple-500:#805AD5;--chakra-colors-purple-600:#6B46C1;--chakra-colors-purple-700:#553C9A;--chakra-colors-purple-800:#44337A;--chakra-colors-purple-900:#322659;--chakra-colors-pink-50:#FFF5F7;--chakra-colors-pink-100:#FED7E2;--chakra-colors-pink-200:#FBB6CE;--chakra-colors-pink-300:#F687B3;--chakra-colors-pink-400:#ED64A6;--chakra-colors-pink-500:#D53F8C;--chakra-colors-pink-600:#B83280;--chakra-colors-pink-700:#97266D;--chakra-colors-pink-800:#702459;--chakra-colors-pink-900:#521B41;--chakra-colors-linkedin-50:#E8F4F9;--chakra-colors-linkedin-100:#CFEDFB;--chakra-colors-linkedin-200:#9BDAF3;--chakra-colors-linkedin-300:#68C7EC;--chakra-colors-linkedin-400:#34B3E4;--chakra-colors-linkedin-500:#00A0DC;--chakra-colors-linkedin-600:#008CC9;--chakra-colors-linkedin-700:#0077B5;--chakra-colors-linkedin-800:#005E93;--chakra-colors-linkedin-900:#004471;--chakra-colors-facebook-50:#E8F4F9;--chakra-colors-facebook-100:#D9DEE9;--chakra-colors-facebook-200:#B7C2DA;--chakra-colors-facebook-300:#6482C0;--chakra-colors-facebook-400:#4267B2;--chakra-colors-facebook-500:#385898;--chakra-colors-facebook-600:#314E89;--chakra-colors-facebook-700:#29487D;--chakra-colors-facebook-800:#223B67;--chakra-colors-facebook-900:#1E355B;--chakra-colors-messenger-50:#D0E6FF;--chakra-colors-messenger-100:#B9DAFF;--chakra-colors-messenger-200:#A2CDFF;--chakra-colors-messenger-300:#7AB8FF;--chakra-colors-messenger-400:#2E90FF;--chakra-colors-messenger-500:#0078FF;--chakra-colors-messenger-600:#0063D1;--chakra-colors-messenger-700:#0052AC;--chakra-colors-messenger-800:#003C7E;--chakra-colors-messenger-900:#002C5C;--chakra-colors-whatsapp-50:#dffeec;--chakra-colors-whatsapp-100:#b9f5d0;--chakra-colors-whatsapp-200:#90edb3;--chakra-colors-whatsapp-300:#65e495;--chakra-colors-whatsapp-400:#3cdd78;--chakra-colors-whatsapp-500:#22c35e;--chakra-colors-whatsapp-600:#179848;--chakra-colors-whatsapp-700:#0c6c33;--chakra-colors-whatsapp-800:#01421c;--chakra-colors-whatsapp-900:#001803;--chakra-colors-twitter-50:#E5F4FD;--chakra-colors-twitter-100:#C8E9FB;--chakra-colors-twitter-200:#A8DCFA;--chakra-colors-twitter-300:#83CDF7;--chakra-colors-twitter-400:#57BBF5;--chakra-colors-twitter-500:#1DA1F2;--chakra-colors-twitter-600:#1A94DA;--chakra-colors-twitter-700:#1681BF;--chakra-colors-twitter-800:#136B9E;--chakra-colors-twitter-900:#0D4D71;--chakra-colors-telegram-50:#E3F2F9;--chakra-colors-telegram-100:#C5E4F3;--chakra-colors-telegram-200:#A2D4EC;--chakra-colors-telegram-300:#7AC1E4;--chakra-colors-telegram-400:#47A9DA;--chakra-colors-telegram-500:#0088CC;--chakra-colors-telegram-600:#007AB8;--chakra-colors-telegram-700:#006BA1;--chakra-colors-telegram-800:#005885;--chakra-colors-telegram-900:#003F5E;--chakra-colors-brand-bg:#06020d;--chakra-colors-brand-hero:#06020d;--chakra-colors-brand-footer:#0d041e;--chakra-borders-none:0;--chakra-borders-1px:1px solid;--chakra-borders-2px:2px solid;--chakra-borders-4px:4px solid;--chakra-borders-8px:8px solid;--chakra-fonts-heading:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--chakra-fonts-body:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--chakra-fonts-mono:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--chakra-fontSizes-xs:0.75rem;--chakra-fontSizes-sm:0.875rem;--chakra-fontSizes-md:1rem;--chakra-fontSizes-lg:1.125rem;--chakra-fontSizes-xl:1.25rem;--chakra-fontSizes-2xl:1.5rem;--chakra-fontSizes-3xl:1.875rem;--chakra-fontSizes-4xl:2.25rem;--chakra-fontSizes-5xl:3rem;--chakra-fontSizes-6xl:3.75rem;--chakra-fontSizes-7xl:4.5rem;--chakra-fontSizes-8xl:6rem;--chakra-fontSizes-9xl:8rem;--chakra-fontWeights-hairline:100;--chakra-fontWeights-thin:200;--chakra-fontWeights-light:300;--chakra-fontWeights-normal:400;--chakra-fontWeights-medium:500;--chakra-fontWeights-semibold:600;--chakra-fontWeights-bold:700;--chakra-fontWeights-extrabold:800;--chakra-fontWeights-black:900;--chakra-letterSpacings-tighter:-0.05em;--chakra-letterSpacings-tight:-0.025em;--chakra-letterSpacings-normal:0;--chakra-letterSpacings-wide:0.025em;--chakra-letterSpacings-wider:0.05em;--chakra-letterSpacings-widest:0.1em;--chakra-lineHeights-3:.75rem;--chakra-lineHeights-4:1rem;--chakra-lineHeights-5:1.25rem;--chakra-lineHeights-6:1.5rem;--chakra-lineHeights-7:1.75rem;--chakra-lineHeights-8:2rem;--chakra-lineHeights-9:2.25rem;--chakra-lineHeights-10:2.5rem;--chakra-lineHeights-normal:normal;--chakra-lineHeights-none:1;--chakra-lineHeights-shorter:1.25;--chakra-lineHeights-short:1.375;--chakra-lineHeights-base:1.5;--chakra-lineHeights-tall:1.625;--chakra-lineHeights-taller:2;--chakra-radii-none:0;--chakra-radii-sm:0.125rem;--chakra-radii-base:0.25rem;--chakra-radii-md:0.375rem;--chakra-radii-lg:0.5rem;--chakra-radii-xl:0.75rem;--chakra-radii-2xl:1rem;--chakra-radii-3xl:1.5rem;--chakra-radii-full:9999px;--chakra-space-1:0.25rem;--chakra-space-2:0.5rem;--chakra-space-3:0.75rem;--chakra-space-4:1rem;--chakra-space-5:1.25rem;--chakra-space-6:1.5rem;--chakra-space-7:1.75rem;--chakra-space-8:2rem;--chakra-space-9:2.25rem;--chakra-space-10:2.5rem;--chakra-space-12:3rem;--chakra-space-14:3.5rem;--chakra-space-16:4rem;--chakra-space-20:5rem;--chakra-space-24:6rem;--chakra-space-28:7rem;--chakra-space-32:8rem;--chakra-space-36:9rem;--chakra-space-40:10rem;--chakra-space-44:11rem;--chakra-space-48:12rem;--chakra-space-52:13rem;--chakra-space-56:14rem;--chakra-space-60:15rem;--chakra-space-64:16rem;--chakra-space-72:18rem;--chakra-space-80:20rem;--chakra-space-96:24rem;--chakra-space-px:1px;--chakra-space-0\.5:0.125rem;--chakra-space-1\.5:0.375rem;--chakra-space-2\.5:0.625rem;--chakra-space-3\.5:0.875rem;--chakra-shadows-xs:0 0 0 1px rgba(0, 0, 0, 0.05);--chakra-shadows-sm:0 1px 2px 0 rgba(0, 0, 0, 0.05);--chakra-shadows-base:0 1px 3px 0 rgba(0, 0, 0, 0.1),0 1px 2px 0 rgba(0, 0, 0, 0.06);--chakra-shadows-md:0 4px 6px -1px rgba(0, 0, 0, 0.1),0 2px 4px -1px rgba(0, 0, 0, 0.06);--chakra-shadows-lg:0 10px 15px -3px rgba(0, 0, 0, 0.1),0 4px 6px -2px rgba(0, 0, 0, 0.05);--chakra-shadows-xl:0 20px 25px -5px rgba(0, 0, 0, 0.1),0 10px 10px -5px rgba(0, 0, 0, 0.04);--chakra-shadows-2xl:0 25px 50px -12px rgba(0, 0, 0, 0.25);--chakra-shadows-outline:0 0 0 3px rgba(66, 153, 225, 0.6);--chakra-shadows-inner:inset 0 2px 4px 0 rgba(0,0,0,0.06);--chakra-shadows-none:none;--chakra-shadows-dark-lg:rgba(0, 0, 0, 0.1) 0px 0px 0px 1px,rgba(0, 0, 0, 0.2) 0px 5px 10px,rgba(0, 0, 0, 0.4) 0px 15px 40px;--chakra-sizes-1:0.25rem;--chakra-sizes-2:0.5rem;--chakra-sizes-3:0.75rem;--chakra-sizes-4:1rem;--chakra-sizes-5:1.25rem;--chakra-sizes-6:1.5rem;--chakra-sizes-7:1.75rem;--chakra-sizes-8:2rem;--chakra-sizes-9:2.25rem;--chakra-sizes-10:2.5rem;--chakra-sizes-12:3rem;--chakra-sizes-14:3.5rem;--chakra-sizes-16:4rem;--chakra-sizes-20:5rem;--chakra-sizes-24:6rem;--chakra-sizes-28:7rem;--chakra-sizes-32:8rem;--chakra-sizes-36:9rem;--chakra-sizes-40:10rem;--chakra-sizes-44:11rem;--chakra-sizes-48:12rem;--chakra-sizes-52:13rem;--chakra-sizes-56:14rem;--chakra-sizes-60:15rem;--chakra-sizes-64:16rem;--chakra-sizes-72:18rem;--chakra-sizes-80:20rem;--chakra-sizes-96:24rem;--chakra-sizes-px:1px;--chakra-sizes-0\.5:0.125rem;--chakra-sizes-1\.5:0.375rem;--chakra-sizes-2\.5:0.625rem;--chakra-sizes-3\.5:0.875rem;--chakra-sizes-max:max-content;--chakra-sizes-min:min-content;--chakra-sizes-full:100%;--chakra-sizes-3xs:14rem;--chakra-sizes-2xs:16rem;--chakra-sizes-xs:20rem;--chakra-sizes-sm:24rem;--chakra-sizes-md:28rem;--chakra-sizes-lg:32rem;--chakra-sizes-xl:36rem;--chakra-sizes-2xl:42rem;--chakra-sizes-3xl:48rem;--chakra-sizes-4xl:56rem;--chakra-sizes-5xl:64rem;--chakra-sizes-6xl:72rem;--chakra-sizes-7xl:80rem;--chakra-sizes-8xl:90rem;--chakra-sizes-container-sm:640px;--chakra-sizes-container-md:768px;--chakra-sizes-container-lg:1024px;--chakra-sizes-container-xl:1280px;--chakra-zIndices-hide:-1;--chakra-zIndices-auto:auto;--chakra-zIndices-base:0;--chakra-zIndices-docked:10;--chakra-zIndices-dropdown:1000;--chakra-zIndices-sticky:1100;--chakra-zIndices-banner:1200;--chakra-zIndices-overlay:1300;--chakra-zIndices-modal:1400;--chakra-zIndices-popover:1500;--chakra-zIndices-skipLink:1600;--chakra-zIndices-toast:1700;--chakra-zIndices-tooltip:1800;--chakra-transition-property-common:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform;--chakra-transition-property-colors:background-color,border-color,color,fill,stroke;--chakra-transition-property-dimensions:width,height;--chakra-transition-property-position:left,right,top,bottom;--chakra-transition-property-background:background-color,background-image,background-position;--chakra-transition-easing-ease-in:cubic-bezier(0.4, 0, 1, 1);--chakra-transition-easing-ease-out:cubic-bezier(0, 0, 0.2, 1);--chakra-transition-easing-ease-in-out:cubic-bezier(0.4, 0, 0.2, 1);--chakra-transition-duration-ultra-fast:50ms;--chakra-transition-duration-faster:100ms;--chakra-transition-duration-fast:150ms;--chakra-transition-duration-normal:200ms;--chakra-transition-duration-slow:300ms;--chakra-transition-duration-slower:400ms;--chakra-transition-duration-ultra-slow:500ms;--chakra-blur-none:0;--chakra-blur-sm:4px;--chakra-blur-base:8px;--chakra-blur-md:12px;--chakra-blur-lg:16px;--chakra-blur-xl:24px;--chakra-blur-2xl:40px;--chakra-blur-3xl:64px;}</style><style data-emotion="css-global 1jqlf9g">html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:system-ui,sans-serif;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;touch-action:manipulation;}body{position:relative;min-height:100%;font-feature-settings:'kern';}*,*::before,*::after{border-width:0;border-style:solid;box-sizing:border-box;}main{display:block;}hr{border-top-width:1px;box-sizing:content-box;height:0;overflow:visible;}pre,code,kbd,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:1em;}a{background-color:transparent;color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit;}abbr[title]{border-bottom:none;-webkit-text-decoration:underline;text-decoration:underline;-webkit-text-decoration:underline dotted;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;}b,strong{font-weight:bold;}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sub{bottom:-0.25em;}sup{top:-0.5em;}img{border-style:none;}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0;}button,input{overflow:visible;}button,select{text-transform:none;}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{border-style:none;padding:0;}fieldset{padding:0.35em 0.75em 0.625em;}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal;}progress{vertical-align:baseline;}textarea{overflow:auto;}[type="checkbox"],[type="radio"]{box-sizing:border-box;padding:0;}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{-webkit-appearance:none!important;}input[type="number"]{-moz-appearance:textfield;}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px;}[type="search"]::-webkit-search-decoration{-webkit-appearance:none!important;}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit;}details{display:block;}summary{display:-webkit-box;display:-webkit-list-item;display:-ms-list-itembox;display:list-item;}template{display:none;}[hidden]{display:none!important;}body,blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0;}button{background:transparent;padding:0;}fieldset{margin:0;padding:0;}ol,ul{margin:0;padding:0;}textarea{resize:vertical;}button,[role="button"]{cursor:pointer;}button::-moz-focus-inner{border:0!important;}table{border-collapse:collapse;}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit;}button,input,optgroup,select,textarea{padding:0;line-height:inherit;color:inherit;}img,svg,video,canvas,audio,iframe,embed,object{display:block;}img,video{max-width:100%;height:auto;}[data-js-focus-visible] :focus:not([data-focus-visible-added]){outline:none;box-shadow:none;}select::-ms-expand{display:none;}</style><style data-emotion="css-global 1baqkrf">body{font-family:var(--chakra-fonts-body);color:var(--chakra-colors-gray-800);background:var(--chakra-colors-white);transition-property:background-color;transition-duration:var(--chakra-transition-duration-normal);line-height:var(--chakra-lineHeights-base);}*::-webkit-input-placeholder{color:var(--chakra-colors-gray-400);}*::-moz-placeholder{color:var(--chakra-colors-gray-400);}*:-ms-input-placeholder{color:var(--chakra-colors-gray-400);}*::placeholder{color:var(--chakra-colors-gray-400);}*,*::before,::after{border-color:var(--chakra-colors-gray-200);word-wrap:break-word;}</style><style data-emotion="css-global 1v09d1a">.js-focus-visible :focus:not([data-focus-visible-added]){outline:none;box-shadow:none;}svg text tspan{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeSpeed;}code{background:#1e1e3f;color:#9efeff;padding:3px 5px;font-size:14px;border-radius:3px;}svg .clickable-group{cursor:pointer;}svg .clickable-group:hover>[fill="rgb(65,53,214)"]{fill:#232381;stroke:#232381;}svg .clickable-group:hover>[fill="rgb(255,255,0)"]{fill:#d6d700;}svg .clickable-group:hover>[fill="rgb(255,229,153)"]{fill:#f3c950;}svg .clickable-group:hover>[fill="rgb(153,153,153)"]{fill:#646464;}svg .clickable-group:hover>[fill="rgb(255,255,255)"]{fill:#d7d7d7;}svg .done rect{fill:#cbcbcb!important;}svg .done text{-webkit-text-decoration:line-through;text-decoration:line-through;}</style><style data-emotion="css gk6shj">.css-gk6shj{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;background:var(--chakra-colors-yellow-200);color:var(--chakra-colors-gray-900);position:-webkit-sticky;position:sticky;top:0px;z-index:999;padding-top:8px;padding-bottom:8px;}.css-gk6shj:hover,.css-gk6shj[data-hover]{-webkit-text-decoration:none;text-decoration:none;background:var(--chakra-colors-yellow-400);}</style><style data-emotion="css n1ed3e">.css-n1ed3e{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:2px solid transparent;outline-offset:2px;color:inherit;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;background:var(--chakra-colors-yellow-200);color:var(--chakra-colors-gray-900);position:-webkit-sticky;position:sticky;top:0px;z-index:999;padding-top:8px;padding-bottom:8px;}.css-n1ed3e:hover,.css-n1ed3e[data-hover]{-webkit-text-decoration:underline;text-decoration:underline;}.css-n1ed3e:focus,.css-n1ed3e[data-focus]{box-shadow:var(--chakra-shadows-outline);}.css-n1ed3e:hover,.css-n1ed3e[data-hover]{-webkit-text-decoration:none;text-decoration:none;background:var(--chakra-colors-yellow-400);}</style><a target="_blank" class="chakra-link css-n1ed3e" href="https://youtube.com/theroadmap?sub_confirmation=1"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" style="height:20px;display:inline-block;margin-right:7px"><path d="M19.615 3.184c-3.604-.246-11.631-.245-15.23 0C.488 3.45.029 5.804 0 12c.029 6.185.484 8.549 4.385 8.816 3.6.245 11.626.246 15.23 0C23.512 20.55 23.971 18.196 24 12c-.029-6.185-.484-8.549-4.385-8.816zM9 16V8l8 3.993L9 16z"></path></svg><style data-emotion="css 17vuvug">.css-17vuvug{font-weight:500;font-size:14px;}</style><span class="chakra-text css-17vuvug"><span class="chakra-text css-0">We now have a YouTube Channel. <style data-emotion="css 13wfqy8">.css-13wfqy8{display:none;}@media screen and (min-width: 30em){.css-13wfqy8{display:inline;}}</style><span class="chakra-text css-13wfqy8">Subscribe for the video content.</span></span></span></a><style data-emotion="css 123acky">.css-123acky{background:var(--chakra-colors-white);min-height:100vh;}</style><div class="css-123acky"><style data-emotion="css sttgv9">.css-sttgv9{background:var(--chakra-colors-gray-900);padding:20px 0;}</style><div class="css-sttgv9"><style data-emotion="css nm5t63">.css-nm5t63{width:100%;-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto;max-width:var(--chakra-sizes-container-md);-webkit-padding-start:1rem;padding-inline-start:1rem;-webkit-padding-end:1rem;padding-inline-end:1rem;}</style><div class="chakra-container css-nm5t63"><style data-emotion="css 1lekzkb">.css-1lekzkb{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;justify-content:space-between;}</style><div class="css-1lekzkb"><div class="css-0"><style data-emotion="css 140fodl">.css-140fodl{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:2px solid transparent;outline-offset:2px;color:var(--chakra-colors-white);width:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-weight:600;font-size:18px;}.css-140fodl:hover,.css-140fodl[data-hover]{-webkit-text-decoration:none;text-decoration:none;}.css-140fodl:focus,.css-140fodl[data-focus]{box-shadow:var(--chakra-shadows-outline);}</style><a class="chakra-link css-140fodl" href="/"><svg width="30" height="30" viewBox="0 0 283 283" fill="none" xmlns="http://www.w3.org/2000/svg" style="height:30px;width:30px;margin-right:10px"><path d="M0 39C0 17.46 17.46 0 39 0h205c21.539 0 39 17.46 39 39v205c0 21.539-17.461 39-39 39H39c-21.54 0-39-17.461-39-39V39Z" fill="#000"></path><path d="M121.215 210.72c-1.867.56-4.854 1.12-8.96 1.68-3.92.56-8.027.84-12.32.84-4.107 0-7.84-.28-11.2-.84-3.174-.56-5.88-1.68-8.12-3.36-2.24-1.68-4.014-3.92-5.32-6.72-1.12-2.987-1.68-6.813-1.68-11.48v-84c0-4.293.746-7.933 2.24-10.92 1.68-3.173 4.013-5.973 7-8.4 2.986-2.427 6.626-4.573 10.92-6.44 4.48-2.053 9.24-3.827 14.28-5.32a106.176 106.176 0 0 1 15.68-3.36 95.412 95.412 0 0 1 16.24-1.4c8.96 0 16.053 1.773 21.28 5.32 5.226 3.36 7.84 8.96 7.84 16.8 0 2.613-.374 5.227-1.12 7.84-.747 2.427-1.68 4.667-2.8 6.72-3.92 0-7.934.187-12.04.56-4.107.373-8.12.933-12.04 1.68-3.92.747-7.654 1.587-11.2 2.52-3.36.747-6.254 1.68-8.68 2.8v95.48Zm45.172-22.4c0-7.84 2.426-14.373 7.28-19.6 4.853-5.227 11.48-7.84 19.88-7.84 8.4 0 15.026 2.613 19.88 7.84 4.853 5.227 7.28 11.76 7.28 19.6 0 7.84-2.427 14.373-7.28 19.6-4.854 5.227-11.48 7.84-19.88 7.84-8.4 0-15.027-2.613-19.88-7.84-4.854-5.227-7.28-11.76-7.28-19.6Z" fill="#fff"></path></svg><style data-emotion="css 1youuz0">.css-1youuz0{display:block;}@media screen and (min-width: 30em){.css-1youuz0{display:none;}}@media screen and (min-width: 48em){.css-1youuz0{display:block;}}</style><span class="chakra-text css-1youuz0">roadmap.sh</span></a></div><style data-emotion="css 1w38872">.css-1w38872{display:none;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;color:var(--chakra-colors-gray-50);font-size:15px;}.css-1w38872>*:not(style)~*:not(style){margin-top:0px;-webkit-margin-end:0px;margin-inline-end:0px;margin-bottom:0px;-webkit-margin-start:15px;margin-inline-start:15px;}@media screen and (min-width: 30em){.css-1w38872{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}}@media screen and (min-width: 48em){.css-1w38872{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}}</style><div class="chakra-stack css-1w38872"><style data-emotion="css 1qy82gu">.css-1qy82gu{display:inline-block;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;min-width:0px;}</style><div class="chakra-stack__item css-1qy82gu"><style data-emotion="css lstwx6">.css-lstwx6{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:2px solid transparent;outline-offset:2px;color:inherit;border-bottom-width:0;border-bottom-color:var(--chakra-colors-gray-500);font-weight:500;}.css-lstwx6:hover,.css-lstwx6[data-hover]{-webkit-text-decoration:none;text-decoration:none;border-bottom-color:var(--chakra-colors-white);}.css-lstwx6:focus,.css-lstwx6[data-focus]{box-shadow:var(--chakra-shadows-outline);}</style><a class="chakra-link css-lstwx6" href="/roadmaps">Roadmaps</a></div><div class="chakra-stack__item css-1qy82gu"><a class="chakra-link css-lstwx6" href="/guides">Guides</a></div><div class="chakra-stack__item css-1qy82gu"><a class="chakra-link css-lstwx6" href="/watch">Videos</a></div><div class="chakra-stack__item css-1qy82gu"><a class="chakra-link css-lstwx6" href="/thanks">Thanks</a></div><div class="chakra-stack__item css-1qy82gu"><style data-emotion="css t88ydv">.css-t88ydv{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:2px solid transparent;outline-offset:2px;color:inherit;margin-left:10px;background-image:linear-gradient(to left, var(--chakra-colors-yellow-700), var(--chakra-colors-red-600));padding:7px 10px;border-radius:4px;font-weight:500;}.css-t88ydv:hover,.css-t88ydv[data-hover]{-webkit-text-decoration:none;text-decoration:none;background-image:linear-gradient(to left, var(--chakra-colors-red-800), var(--chakra-colors-yellow-700));}.css-t88ydv:focus,.css-t88ydv[data-focus]{box-shadow:var(--chakra-shadows-outline);}</style><a class="chakra-link css-t88ydv" href="/signup">Subscribe</a></div></div><style data-emotion="css lpr56g">.css-lpr56g{display:block;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:relative;white-space:nowrap;vertical-align:middle;outline:2px solid transparent;outline-offset:2px;width:auto;line-height:1.2;border-radius:5px;font-weight:var(--chakra-fontWeights-semibold);transition-property:var(--chakra-transition-property-common);transition-duration:var(--chakra-transition-duration-normal);height:auto;min-width:var(--chakra-sizes-10);font-size:var(--chakra-fontSizes-md);-webkit-padding-start:var(--chakra-space-4);padding-inline-start:var(--chakra-space-4);-webkit-padding-end:var(--chakra-space-4);padding-inline-end:var(--chakra-space-4);background:var(--chakra-colors-transparent);padding:0px;color:var(--chakra-colors-white);cursor:pointer;}.css-lpr56g:focus,.css-lpr56g[data-focus]{background:var(--chakra-colors-transparent);}.css-lpr56g[disabled],.css-lpr56g[aria-disabled=true],.css-lpr56g[data-disabled]{opacity:0.4;cursor:not-allowed;box-shadow:var(--chakra-shadows-none);}.css-lpr56g:hover,.css-lpr56g[data-hover]{background:var(--chakra-colors-transparent);}.css-lpr56g:active,.css-lpr56g[data-active]{background:var(--chakra-colors-transparent);}@media screen and (min-width: 30em){.css-lpr56g{display:none;}}@media screen and (min-width: 48em){.css-lpr56g{display:none;}}</style><button type="button" class="chakra-button css-lpr56g" aria-label="Menu"><style data-emotion="css 17elikm">.css-17elikm{width:25px;height:25px;display:inline-block;line-height:1em;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;color:var(--chakra-colors-white);vertical-align:middle;}</style><svg viewBox="0 0 24 24" focusable="false" class="chakra-icon css-17elikm" aria-hidden="true"><path fill="currentColor" d="M 3 5 A 1.0001 1.0001 0 1 0 3 7 L 21 7 A 1.0001 1.0001 0 1 0 21 5 L 3 5 z M 3 11 A 1.0001 1.0001 0 1 0 3 13 L 21 13 A 1.0001 1.0001 0 1 0 21 11 L 3 11 z M 3 17 A 1.0001 1.0001 0 1 0 3 19 L 21 19 A 1.0001 1.0001 0 1 0 21 17 L 3 17 z"></path></svg></button></div></div></div><style data-emotion="css flpniz">.css-flpniz{margin-bottom:60px;}</style><div class="css-flpniz"><style data-emotion="css 1yls08e">.css-1yls08e{padding-top:35px;padding-bottom:35px;border-bottom-width:1px;margin-bottom:30px;}@media screen and (min-width: 30em){.css-1yls08e{padding-top:35px;padding-bottom:35px;}}@media screen and (min-width: 48em){.css-1yls08e{padding-top:70px;padding-bottom:55px;}}</style><div class="css-1yls08e"><style data-emotion="css 3iv5ap">.css-3iv5ap{width:100%;-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto;max-width:var(--chakra-sizes-container-md);-webkit-padding-start:1rem;padding-inline-start:1rem;-webkit-padding-end:1rem;padding-inline-end:1rem;position:relative;text-align:left;}@media screen and (min-width: 30em){.css-3iv5ap{text-align:left;}}@media screen and (min-width: 48em){.css-3iv5ap{text-align:center;}}</style><div class="chakra-container css-3iv5ap"><style data-emotion="css 1s6l7oz">.css-1s6l7oz{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:start;-ms-flex-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start;font-size:12px;}@media screen and (min-width: 30em){.css-1s6l7oz{-webkit-box-pack:start;-ms-flex-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start;font-size:12px;}}@media screen and (min-width: 48em){.css-1s6l7oz{-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;font-size:14px;}}</style><div class="css-1s6l7oz"><style data-emotion="css tomzwu">.css-tomzwu{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:2px solid transparent;outline-offset:2px;color:var(--chakra-colors-gray-500);display:none;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-weight:600;}.css-tomzwu:hover,.css-tomzwu[data-hover]{-webkit-text-decoration:underline;text-decoration:underline;}.css-tomzwu:focus,.css-tomzwu[data-focus]{box-shadow:var(--chakra-shadows-outline);}@media screen and (min-width: 30em){.css-tomzwu{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}}@media screen and (min-width: 48em){.css-tomzwu{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}}</style><a target="_blank" class="chakra-link css-tomzwu" href="https://twitter.com/__jesse_li"><style data-emotion="css 1bgbs7z">.css-1bgbs7z{border-radius:var(--chakra-radii-full);margin-right:7px;width:22px;}</style><img alt="" src="/authors/jesse.png" class="chakra-image css-1bgbs7z"/>Jesse Li</a><style data-emotion="css 16hbmpr">.css-16hbmpr{display:none;-webkit-margin-start:7px;margin-inline-start:7px;-webkit-margin-end:7px;margin-inline-end:7px;color:var(--chakra-colors-gray-500);}@media screen and (min-width: 30em){.css-16hbmpr{display:inline;}}@media screen and (min-width: 48em){.css-16hbmpr{display:inline;}}</style><span class="chakra-text css-16hbmpr">·</span><style data-emotion="css q9k0mw">.css-q9k0mw{color:var(--chakra-colors-gray-500);}</style><span class="chakra-text css-q9k0mw">January 17, 2021</span><style data-emotion="css hdjuk6">.css-hdjuk6{display:none;-webkit-margin-start:7px;margin-inline-start:7px;-webkit-margin-end:7px;margin-inline-end:7px;color:var(--chakra-colors-gray-500);}@media screen and (min-width: 30em){.css-hdjuk6{display:none;}}@media screen and (min-width: 48em){.css-hdjuk6{display:inline;}}</style><span class="chakra-text css-hdjuk6">·</span><style data-emotion="css 44g9xm">.css-44g9xm{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:2px solid transparent;outline-offset:2px;color:var(--chakra-colors-blue-500);display:none;font-weight:500;}.css-44g9xm:hover,.css-44g9xm[data-hover]{-webkit-text-decoration:underline;text-decoration:underline;}.css-44g9xm:focus,.css-44g9xm[data-focus]{box-shadow:var(--chakra-shadows-outline);}@media screen and (min-width: 30em){.css-44g9xm{display:none;}}@media screen and (min-width: 48em){.css-44g9xm{display:inline;}}</style><a target="_blank" class="chakra-link css-44g9xm" href="https://github.com/kamranahmedse/developer-roadmap/tree/master/content/guides/torrent-client.md">Improve this Guide</a></div><style data-emotion="css 114bhkb">.css-114bhkb{font-family:var(--chakra-fonts-heading);font-weight:700;font-size:30px;line-height:40px;color:var(--chakra-colors-black);margin-top:5px;margin-bottom:5px;}@media screen and (min-width: 30em){.css-114bhkb{font-size:30px;line-height:40px;margin-top:5px;margin-bottom:5px;}}@media screen and (min-width: 48em){.css-114bhkb{font-size:45px;line-height:53px;margin-top:10px;margin-bottom:10px;}}</style><h1 class="chakra-heading css-114bhkb">Building a BitTorrent Client</h1><style data-emotion="css 1vh4uni">.css-1vh4uni{font-size:14px;color:var(--chakra-colors-gray-700);}@media screen and (min-width: 30em){.css-1vh4uni{font-size:14px;}}@media screen and (min-width: 48em){.css-1vh4uni{font-size:16px;}}</style><p class="chakra-text css-1vh4uni">Learn everything you need to know about BitTorrent by writing a client in Go</p></div></div><style data-emotion="css ms16h5">.css-ms16h5{width:100%;-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto;max-width:var(--chakra-sizes-container-md);-webkit-padding-start:1rem;padding-inline-start:1rem;-webkit-padding-end:1rem;padding-inline-end:1rem;position:relative;}</style><div class="chakra-container css-ms16h5"><style data-emotion="css-global brzcv3">:host,:root{--chakra-ring-inset:var(--chakra-empty,/*!*/ /*!*/);--chakra-ring-offset-width:0px;--chakra-ring-offset-color:#fff;--chakra-ring-color:rgba(66, 153, 225, 0.6);--chakra-ring-offset-shadow:0 0 #0000;--chakra-ring-shadow:0 0 #0000;--chakra-space-x-reverse:0;--chakra-space-y-reverse:0;--chakra-colors-transparent:transparent;--chakra-colors-current:currentColor;--chakra-colors-black:#000000;--chakra-colors-white:#FFFFFF;--chakra-colors-whiteAlpha-50:rgba(255, 255, 255, 0.04);--chakra-colors-whiteAlpha-100:rgba(255, 255, 255, 0.06);--chakra-colors-whiteAlpha-200:rgba(255, 255, 255, 0.08);--chakra-colors-whiteAlpha-300:rgba(255, 255, 255, 0.16);--chakra-colors-whiteAlpha-400:rgba(255, 255, 255, 0.24);--chakra-colors-whiteAlpha-500:rgba(255, 255, 255, 0.36);--chakra-colors-whiteAlpha-600:rgba(255, 255, 255, 0.48);--chakra-colors-whiteAlpha-700:rgba(255, 255, 255, 0.64);--chakra-colors-whiteAlpha-800:rgba(255, 255, 255, 0.80);--chakra-colors-whiteAlpha-900:rgba(255, 255, 255, 0.92);--chakra-colors-blackAlpha-50:rgba(0, 0, 0, 0.04);--chakra-colors-blackAlpha-100:rgba(0, 0, 0, 0.06);--chakra-colors-blackAlpha-200:rgba(0, 0, 0, 0.08);--chakra-colors-blackAlpha-300:rgba(0, 0, 0, 0.16);--chakra-colors-blackAlpha-400:rgba(0, 0, 0, 0.24);--chakra-colors-blackAlpha-500:rgba(0, 0, 0, 0.36);--chakra-colors-blackAlpha-600:rgba(0, 0, 0, 0.48);--chakra-colors-blackAlpha-700:rgba(0, 0, 0, 0.64);--chakra-colors-blackAlpha-800:rgba(0, 0, 0, 0.80);--chakra-colors-blackAlpha-900:rgba(0, 0, 0, 0.92);--chakra-colors-gray-50:#F7FAFC;--chakra-colors-gray-100:#EDF2F7;--chakra-colors-gray-200:#E2E8F0;--chakra-colors-gray-300:#CBD5E0;--chakra-colors-gray-400:#A0AEC0;--chakra-colors-gray-500:#718096;--chakra-colors-gray-600:#4A5568;--chakra-colors-gray-700:#2D3748;--chakra-colors-gray-800:#1A202C;--chakra-colors-gray-900:#171923;--chakra-colors-red-50:#FFF5F5;--chakra-colors-red-100:#FED7D7;--chakra-colors-red-200:#FEB2B2;--chakra-colors-red-300:#FC8181;--chakra-colors-red-400:#F56565;--chakra-colors-red-500:#E53E3E;--chakra-colors-red-600:#C53030;--chakra-colors-red-700:#9B2C2C;--chakra-colors-red-800:#822727;--chakra-colors-red-900:#63171B;--chakra-colors-orange-50:#FFFAF0;--chakra-colors-orange-100:#FEEBC8;--chakra-colors-orange-200:#FBD38D;--chakra-colors-orange-300:#F6AD55;--chakra-colors-orange-400:#ED8936;--chakra-colors-orange-500:#DD6B20;--chakra-colors-orange-600:#C05621;--chakra-colors-orange-700:#9C4221;--chakra-colors-orange-800:#7B341E;--chakra-colors-orange-900:#652B19;--chakra-colors-yellow-50:#FFFFF0;--chakra-colors-yellow-100:#FEFCBF;--chakra-colors-yellow-200:#FAF089;--chakra-colors-yellow-300:#F6E05E;--chakra-colors-yellow-400:#ECC94B;--chakra-colors-yellow-500:#D69E2E;--chakra-colors-yellow-600:#B7791F;--chakra-colors-yellow-700:#975A16;--chakra-colors-yellow-800:#744210;--chakra-colors-yellow-900:#5F370E;--chakra-colors-green-50:#F0FFF4;--chakra-colors-green-100:#C6F6D5;--chakra-colors-green-200:#9AE6B4;--chakra-colors-green-300:#68D391;--chakra-colors-green-400:#48BB78;--chakra-colors-green-500:#38A169;--chakra-colors-green-600:#2F855A;--chakra-colors-green-700:#276749;--chakra-colors-green-800:#22543D;--chakra-colors-green-900:#1C4532;--chakra-colors-teal-50:#E6FFFA;--chakra-colors-teal-100:#B2F5EA;--chakra-colors-teal-200:#81E6D9;--chakra-colors-teal-300:#4FD1C5;--chakra-colors-teal-400:#38B2AC;--chakra-colors-teal-500:#319795;--chakra-colors-teal-600:#2C7A7B;--chakra-colors-teal-700:#285E61;--chakra-colors-teal-800:#234E52;--chakra-colors-teal-900:#1D4044;--chakra-colors-blue-50:#ebf8ff;--chakra-colors-blue-100:#bee3f8;--chakra-colors-blue-200:#90cdf4;--chakra-colors-blue-300:#63b3ed;--chakra-colors-blue-400:#4299e1;--chakra-colors-blue-500:#3182ce;--chakra-colors-blue-600:#2b6cb0;--chakra-colors-blue-700:#2c5282;--chakra-colors-blue-800:#2a4365;--chakra-colors-blue-900:#1A365D;--chakra-colors-cyan-50:#EDFDFD;--chakra-colors-cyan-100:#C4F1F9;--chakra-colors-cyan-200:#9DECF9;--chakra-colors-cyan-300:#76E4F7;--chakra-colors-cyan-400:#0BC5EA;--chakra-colors-cyan-500:#00B5D8;--chakra-colors-cyan-600:#00A3C4;--chakra-colors-cyan-700:#0987A0;--chakra-colors-cyan-800:#086F83;--chakra-colors-cyan-900:#065666;--chakra-colors-purple-50:#FAF5FF;--chakra-colors-purple-100:#E9D8FD;--chakra-colors-purple-200:#D6BCFA;--chakra-colors-purple-300:#B794F4;--chakra-colors-purple-400:#9F7AEA;--chakra-colors-purple-500:#805AD5;--chakra-colors-purple-600:#6B46C1;--chakra-colors-purple-700:#553C9A;--chakra-colors-purple-800:#44337A;--chakra-colors-purple-900:#322659;--chakra-colors-pink-50:#FFF5F7;--chakra-colors-pink-100:#FED7E2;--chakra-colors-pink-200:#FBB6CE;--chakra-colors-pink-300:#F687B3;--chakra-colors-pink-400:#ED64A6;--chakra-colors-pink-500:#D53F8C;--chakra-colors-pink-600:#B83280;--chakra-colors-pink-700:#97266D;--chakra-colors-pink-800:#702459;--chakra-colors-pink-900:#521B41;--chakra-colors-linkedin-50:#E8F4F9;--chakra-colors-linkedin-100:#CFEDFB;--chakra-colors-linkedin-200:#9BDAF3;--chakra-colors-linkedin-300:#68C7EC;--chakra-colors-linkedin-400:#34B3E4;--chakra-colors-linkedin-500:#00A0DC;--chakra-colors-linkedin-600:#008CC9;--chakra-colors-linkedin-700:#0077B5;--chakra-colors-linkedin-800:#005E93;--chakra-colors-linkedin-900:#004471;--chakra-colors-facebook-50:#E8F4F9;--chakra-colors-facebook-100:#D9DEE9;--chakra-colors-facebook-200:#B7C2DA;--chakra-colors-facebook-300:#6482C0;--chakra-colors-facebook-400:#4267B2;--chakra-colors-facebook-500:#385898;--chakra-colors-facebook-600:#314E89;--chakra-colors-facebook-700:#29487D;--chakra-colors-facebook-800:#223B67;--chakra-colors-facebook-900:#1E355B;--chakra-colors-messenger-50:#D0E6FF;--chakra-colors-messenger-100:#B9DAFF;--chakra-colors-messenger-200:#A2CDFF;--chakra-colors-messenger-300:#7AB8FF;--chakra-colors-messenger-400:#2E90FF;--chakra-colors-messenger-500:#0078FF;--chakra-colors-messenger-600:#0063D1;--chakra-colors-messenger-700:#0052AC;--chakra-colors-messenger-800:#003C7E;--chakra-colors-messenger-900:#002C5C;--chakra-colors-whatsapp-50:#dffeec;--chakra-colors-whatsapp-100:#b9f5d0;--chakra-colors-whatsapp-200:#90edb3;--chakra-colors-whatsapp-300:#65e495;--chakra-colors-whatsapp-400:#3cdd78;--chakra-colors-whatsapp-500:#22c35e;--chakra-colors-whatsapp-600:#179848;--chakra-colors-whatsapp-700:#0c6c33;--chakra-colors-whatsapp-800:#01421c;--chakra-colors-whatsapp-900:#001803;--chakra-colors-twitter-50:#E5F4FD;--chakra-colors-twitter-100:#C8E9FB;--chakra-colors-twitter-200:#A8DCFA;--chakra-colors-twitter-300:#83CDF7;--chakra-colors-twitter-400:#57BBF5;--chakra-colors-twitter-500:#1DA1F2;--chakra-colors-twitter-600:#1A94DA;--chakra-colors-twitter-700:#1681BF;--chakra-colors-twitter-800:#136B9E;--chakra-colors-twitter-900:#0D4D71;--chakra-colors-telegram-50:#E3F2F9;--chakra-colors-telegram-100:#C5E4F3;--chakra-colors-telegram-200:#A2D4EC;--chakra-colors-telegram-300:#7AC1E4;--chakra-colors-telegram-400:#47A9DA;--chakra-colors-telegram-500:#0088CC;--chakra-colors-telegram-600:#007AB8;--chakra-colors-telegram-700:#006BA1;--chakra-colors-telegram-800:#005885;--chakra-colors-telegram-900:#003F5E;--chakra-colors-brand-bg:#06020d;--chakra-colors-brand-hero:#06020d;--chakra-colors-brand-footer:#0d041e;--chakra-borders-none:0;--chakra-borders-1px:1px solid;--chakra-borders-2px:2px solid;--chakra-borders-4px:4px solid;--chakra-borders-8px:8px solid;--chakra-fonts-heading:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--chakra-fonts-body:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--chakra-fonts-mono:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--chakra-fontSizes-xs:0.75rem;--chakra-fontSizes-sm:0.875rem;--chakra-fontSizes-md:1rem;--chakra-fontSizes-lg:1.125rem;--chakra-fontSizes-xl:1.25rem;--chakra-fontSizes-2xl:1.5rem;--chakra-fontSizes-3xl:1.875rem;--chakra-fontSizes-4xl:2.25rem;--chakra-fontSizes-5xl:3rem;--chakra-fontSizes-6xl:3.75rem;--chakra-fontSizes-7xl:4.5rem;--chakra-fontSizes-8xl:6rem;--chakra-fontSizes-9xl:8rem;--chakra-fontWeights-hairline:100;--chakra-fontWeights-thin:200;--chakra-fontWeights-light:300;--chakra-fontWeights-normal:400;--chakra-fontWeights-medium:500;--chakra-fontWeights-semibold:600;--chakra-fontWeights-bold:700;--chakra-fontWeights-extrabold:800;--chakra-fontWeights-black:900;--chakra-letterSpacings-tighter:-0.05em;--chakra-letterSpacings-tight:-0.025em;--chakra-letterSpacings-normal:0;--chakra-letterSpacings-wide:0.025em;--chakra-letterSpacings-wider:0.05em;--chakra-letterSpacings-widest:0.1em;--chakra-lineHeights-3:.75rem;--chakra-lineHeights-4:1rem;--chakra-lineHeights-5:1.25rem;--chakra-lineHeights-6:1.5rem;--chakra-lineHeights-7:1.75rem;--chakra-lineHeights-8:2rem;--chakra-lineHeights-9:2.25rem;--chakra-lineHeights-10:2.5rem;--chakra-lineHeights-normal:normal;--chakra-lineHeights-none:1;--chakra-lineHeights-shorter:1.25;--chakra-lineHeights-short:1.375;--chakra-lineHeights-base:1.5;--chakra-lineHeights-tall:1.625;--chakra-lineHeights-taller:2;--chakra-radii-none:0;--chakra-radii-sm:0.125rem;--chakra-radii-base:0.25rem;--chakra-radii-md:0.375rem;--chakra-radii-lg:0.5rem;--chakra-radii-xl:0.75rem;--chakra-radii-2xl:1rem;--chakra-radii-3xl:1.5rem;--chakra-radii-full:9999px;--chakra-space-1:0.25rem;--chakra-space-2:0.5rem;--chakra-space-3:0.75rem;--chakra-space-4:1rem;--chakra-space-5:1.25rem;--chakra-space-6:1.5rem;--chakra-space-7:1.75rem;--chakra-space-8:2rem;--chakra-space-9:2.25rem;--chakra-space-10:2.5rem;--chakra-space-12:3rem;--chakra-space-14:3.5rem;--chakra-space-16:4rem;--chakra-space-20:5rem;--chakra-space-24:6rem;--chakra-space-28:7rem;--chakra-space-32:8rem;--chakra-space-36:9rem;--chakra-space-40:10rem;--chakra-space-44:11rem;--chakra-space-48:12rem;--chakra-space-52:13rem;--chakra-space-56:14rem;--chakra-space-60:15rem;--chakra-space-64:16rem;--chakra-space-72:18rem;--chakra-space-80:20rem;--chakra-space-96:24rem;--chakra-space-px:1px;--chakra-space-0\.5:0.125rem;--chakra-space-1\.5:0.375rem;--chakra-space-2\.5:0.625rem;--chakra-space-3\.5:0.875rem;--chakra-shadows-xs:0 0 0 1px rgba(0, 0, 0, 0.05);--chakra-shadows-sm:0 1px 2px 0 rgba(0, 0, 0, 0.05);--chakra-shadows-base:0 1px 3px 0 rgba(0, 0, 0, 0.1),0 1px 2px 0 rgba(0, 0, 0, 0.06);--chakra-shadows-md:0 4px 6px -1px rgba(0, 0, 0, 0.1),0 2px 4px -1px rgba(0, 0, 0, 0.06);--chakra-shadows-lg:0 10px 15px -3px rgba(0, 0, 0, 0.1),0 4px 6px -2px rgba(0, 0, 0, 0.05);--chakra-shadows-xl:0 20px 25px -5px rgba(0, 0, 0, 0.1),0 10px 10px -5px rgba(0, 0, 0, 0.04);--chakra-shadows-2xl:0 25px 50px -12px rgba(0, 0, 0, 0.25);--chakra-shadows-outline:0 0 0 3px rgba(66, 153, 225, 0.6);--chakra-shadows-inner:inset 0 2px 4px 0 rgba(0,0,0,0.06);--chakra-shadows-none:none;--chakra-shadows-dark-lg:rgba(0, 0, 0, 0.1) 0px 0px 0px 1px,rgba(0, 0, 0, 0.2) 0px 5px 10px,rgba(0, 0, 0, 0.4) 0px 15px 40px;--chakra-sizes-1:0.25rem;--chakra-sizes-2:0.5rem;--chakra-sizes-3:0.75rem;--chakra-sizes-4:1rem;--chakra-sizes-5:1.25rem;--chakra-sizes-6:1.5rem;--chakra-sizes-7:1.75rem;--chakra-sizes-8:2rem;--chakra-sizes-9:2.25rem;--chakra-sizes-10:2.5rem;--chakra-sizes-12:3rem;--chakra-sizes-14:3.5rem;--chakra-sizes-16:4rem;--chakra-sizes-20:5rem;--chakra-sizes-24:6rem;--chakra-sizes-28:7rem;--chakra-sizes-32:8rem;--chakra-sizes-36:9rem;--chakra-sizes-40:10rem;--chakra-sizes-44:11rem;--chakra-sizes-48:12rem;--chakra-sizes-52:13rem;--chakra-sizes-56:14rem;--chakra-sizes-60:15rem;--chakra-sizes-64:16rem;--chakra-sizes-72:18rem;--chakra-sizes-80:20rem;--chakra-sizes-96:24rem;--chakra-sizes-px:1px;--chakra-sizes-0\.5:0.125rem;--chakra-sizes-1\.5:0.375rem;--chakra-sizes-2\.5:0.625rem;--chakra-sizes-3\.5:0.875rem;--chakra-sizes-max:max-content;--chakra-sizes-min:min-content;--chakra-sizes-full:100%;--chakra-sizes-3xs:14rem;--chakra-sizes-2xs:16rem;--chakra-sizes-xs:20rem;--chakra-sizes-sm:24rem;--chakra-sizes-md:28rem;--chakra-sizes-lg:32rem;--chakra-sizes-xl:36rem;--chakra-sizes-2xl:42rem;--chakra-sizes-3xl:48rem;--chakra-sizes-4xl:56rem;--chakra-sizes-5xl:64rem;--chakra-sizes-6xl:72rem;--chakra-sizes-7xl:80rem;--chakra-sizes-8xl:90rem;--chakra-sizes-container-sm:640px;--chakra-sizes-container-md:768px;--chakra-sizes-container-lg:1024px;--chakra-sizes-container-xl:1280px;--chakra-zIndices-hide:-1;--chakra-zIndices-auto:auto;--chakra-zIndices-base:0;--chakra-zIndices-docked:10;--chakra-zIndices-dropdown:1000;--chakra-zIndices-sticky:1100;--chakra-zIndices-banner:1200;--chakra-zIndices-overlay:1300;--chakra-zIndices-modal:1400;--chakra-zIndices-popover:1500;--chakra-zIndices-skipLink:1600;--chakra-zIndices-toast:1700;--chakra-zIndices-tooltip:1800;--chakra-transition-property-common:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform;--chakra-transition-property-colors:background-color,border-color,color,fill,stroke;--chakra-transition-property-dimensions:width,height;--chakra-transition-property-position:left,right,top,bottom;--chakra-transition-property-background:background-color,background-image,background-position;--chakra-transition-easing-ease-in:cubic-bezier(0.4, 0, 1, 1);--chakra-transition-easing-ease-out:cubic-bezier(0, 0, 0.2, 1);--chakra-transition-easing-ease-in-out:cubic-bezier(0.4, 0, 0.2, 1);--chakra-transition-duration-ultra-fast:50ms;--chakra-transition-duration-faster:100ms;--chakra-transition-duration-fast:150ms;--chakra-transition-duration-normal:200ms;--chakra-transition-duration-slow:300ms;--chakra-transition-duration-slower:400ms;--chakra-transition-duration-ultra-slow:500ms;--chakra-blur-none:0;--chakra-blur-sm:4px;--chakra-blur-base:8px;--chakra-blur-md:12px;--chakra-blur-lg:16px;--chakra-blur-xl:24px;--chakra-blur-2xl:40px;--chakra-blur-3xl:64px;}</style><style data-emotion="css-global 1jqlf9g">html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:system-ui,sans-serif;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;touch-action:manipulation;}body{position:relative;min-height:100%;font-feature-settings:'kern';}*,*::before,*::after{border-width:0;border-style:solid;box-sizing:border-box;}main{display:block;}hr{border-top-width:1px;box-sizing:content-box;height:0;overflow:visible;}pre,code,kbd,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:1em;}a{background-color:transparent;color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit;}abbr[title]{border-bottom:none;-webkit-text-decoration:underline;text-decoration:underline;-webkit-text-decoration:underline dotted;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;}b,strong{font-weight:bold;}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sub{bottom:-0.25em;}sup{top:-0.5em;}img{border-style:none;}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0;}button,input{overflow:visible;}button,select{text-transform:none;}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{border-style:none;padding:0;}fieldset{padding:0.35em 0.75em 0.625em;}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal;}progress{vertical-align:baseline;}textarea{overflow:auto;}[type="checkbox"],[type="radio"]{box-sizing:border-box;padding:0;}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{-webkit-appearance:none!important;}input[type="number"]{-moz-appearance:textfield;}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px;}[type="search"]::-webkit-search-decoration{-webkit-appearance:none!important;}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit;}details{display:block;}summary{display:-webkit-box;display:-webkit-list-item;display:-ms-list-itembox;display:list-item;}template{display:none;}[hidden]{display:none!important;}body,blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0;}button{background:transparent;padding:0;}fieldset{margin:0;padding:0;}ol,ul{margin:0;padding:0;}textarea{resize:vertical;}button,[role="button"]{cursor:pointer;}button::-moz-focus-inner{border:0!important;}table{border-collapse:collapse;}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit;}button,input,optgroup,select,textarea{padding:0;line-height:inherit;color:inherit;}img,svg,video,canvas,audio,iframe,embed,object{display:block;}img,video{max-width:100%;height:auto;}[data-js-focus-visible] :focus:not([data-focus-visible-added]){outline:none;box-shadow:none;}select::-ms-expand{display:none;}</style><style data-emotion="css-global 1baqkrf">body{font-family:var(--chakra-fonts-body);color:var(--chakra-colors-gray-800);background:var(--chakra-colors-white);transition-property:background-color;transition-duration:var(--chakra-transition-duration-normal);line-height:var(--chakra-lineHeights-base);}*::-webkit-input-placeholder{color:var(--chakra-colors-gray-400);}*::-moz-placeholder{color:var(--chakra-colors-gray-400);}*:-ms-input-placeholder{color:var(--chakra-colors-gray-400);}*::placeholder{color:var(--chakra-colors-gray-400);}*,*::before,::after{border-color:var(--chakra-colors-gray-200);word-wrap:break-word;}</style><p class="sc-bdvvtL kiIUjN">BitTorrent is a protocol for downloading and distributing files across the Internet. In contrast with the traditional client/server relationship, in which downloaders connect to a central server (for example: watching a movie on Netflix, or loading the web page you're reading now), participants in the BitTorrent network, called <strong>peers</strong>, download pieces of files from <em>each other</em>—this is what makes it a <strong>peer-to-peer</strong> protocol. In this article we will investigate how this works, and build our own client that can find peers and exchange data between them.</p><p class="sc-bdvvtL kiIUjN"><img src="/guides/torrent-client/client-server-p2p.png" alt="diagram showing the difference between client/server (all clients connecting to one server) and peer-to-peer (peers connecting to each other) relationships" class="sc-iqseJM bIxQHp"/></p><p class="sc-bdvvtL kiIUjN">The protocol evolved organically over the past 20 years, and various people and organizations added extensions for features like encryption, private torrents, and new ways of finding peers. We'll be implementing the <a href="https://www.bittorrent.org/beps/bep_0003.html" target="_blank" rel="nofollow" class="sc-crHmcD gJMBjK">original spec</a> from 2001 to keep this a weekend-sized project.</p><p class="sc-bdvvtL kiIUjN">I'll be using a <a href="https://cdimage.debian.org/debian-cd/current/amd64/bt-cd/#indexlist" target="_blank" rel="nofollow" class="sc-crHmcD gJMBjK">Debian ISO</a> file as my guinea pig because it's big, but not huge, at 350MB. As a popular Linux distribution, there will be lots of fast and cooperative peers for us to connect to. And we'll avoid the legal and ethical issues related to downloading pirated content.</p><h2 class="sc-dkPtRN sc-hKwDye dKUOdJ kqfgwy">Finding peers</h2><p class="sc-bdvvtL kiIUjN">Here’s a problem: we want to download a file with BitTorrent, but it’s a peer-to-peer protocol and we have no idea where to find peers to download it from. This is a lot like moving to a new city and trying to make friends—maybe we’ll hit up a local pub or a meetup group! Centralized locations like these are the big idea behind trackers, which are central servers that introduce peers to each other. They’re just web servers running over HTTP, and you can find Debian’s at <a href="http://bttracker.debian.org:6969/" target="_blank" rel="nofollow" class="sc-crHmcD gJMBjK">http://bttracker.debian.org:6969/</a></p><p class="sc-bdvvtL kiIUjN"><img src="/guides/torrent-client/trackers.png" alt="illustration of a desktop computer and laptop sitting at a pub" class="sc-iqseJM bIxQHp"/></p><p class="sc-bdvvtL kiIUjN">Of course, these central servers are liable to get raided by the feds if they facilitate peers exchanging illegal content. You may remember reading about trackers like TorrentSpy, Popcorn Time, and KickassTorrents getting seized and shut down. New methods cut out the middleman by making even <strong>peer discovery</strong> a distributed process. We won't be implementing them, but if you're interested, some terms you can research are <strong>DHT</strong>, <strong>PEX</strong>, and <strong>magnet links</strong>.</p><h3 class="sc-dkPtRN sc-eCImPb dKUOdJ cabxUq">Parsing a .torrent file</h3><p class="sc-bdvvtL kiIUjN">A .torrent file describes the contents of a torrentable file and information for connecting to a tracker. It's all we need in order to kickstart the process of downloading a torrent. Debian's .torrent file looks like this:</p><pre class="sc-furwcr jiGIaj language-markdown"><style data-emotion="css 4m8w8z">.css-4m8w8z{display:inline-block;font-family:var(--chakra-fonts-mono);font-size:var(--chakra-fontSizes-sm);-webkit-padding-start:0.2em;padding-inline-start:0.2em;-webkit-padding-end:0.2em;padding-inline-end:0.2em;border-radius:var(--chakra-radii-sm);background:var(--chakra-colors-gray-100);color:var(--chakra-colors-gray-800);}</style><code class="chakra-code language-markdown css-4m8w8z">d8:announce41:http://bttracker.debian.org:6969/announce7:comment35:"Debian CD from cdimage.debian.org"13:creation datei1573903810e9:httpseedsl145:https://cdimage.debian.org/cdimage/release/10.2.0//srv/cdbuilder.debian.org/dst/deb-cd/weekly-builds/amd64/iso-cd/debian-10.2.0-amd64-netinst.iso145:https://cdimage.debian.org/cdimage/archive/10.2.0//srv/cdbuilder.debian.org/dst/deb-cd/weekly-builds/amd64/iso-cd/debian-10.2.0-amd64-netinst.isoe4:infod6:lengthi351272960e4:name31:debian-10.2.0-amd64-netinst.iso12:piece lengthi262144e6:pieces26800:<3A><1F><0F><><EFBFBD>PS<50>^<5E><> (binary blob of the hashes of each piece)ee
|
||
</code></pre><p class="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><p class="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><p class="sc-bdvvtL kiIUjN">In a prettier format, our .torrent file looks like this:</p><pre class="sc-furwcr jiGIaj language-markdown"><code class="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><p class="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><p class="sc-bdvvtL kiIUjN"><img src="/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><p class="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><p class="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 <a href="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 <a href="https://github.com/jackpal/bencode-go" target="_blank" rel="nofollow" class="sc-crHmcD gJMBjK">github.com/jackpal/bencode-go</a>:</p><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token keyword">import</span> <span class="token punctuation">(</span>
|
||
<span class="token string">"github.com/jackpal/bencode-go"</span>
|
||
<span class="token punctuation">)</span>
|
||
|
||
<span class="token keyword">type</span> bencodeInfo <span class="token keyword">struct</span> <span class="token punctuation">{</span>
|
||
Pieces <span class="token builtin">string</span> <span class="token string">`bencode:"pieces"`</span>
|
||
PieceLength <span class="token builtin">int</span> <span class="token string">`bencode:"piece length"`</span>
|
||
Length <span class="token builtin">int</span> <span class="token string">`bencode:"length"`</span>
|
||
Name <span class="token builtin">string</span> <span class="token string">`bencode:"name"`</span>
|
||
<span class="token punctuation">}</span>
|
||
|
||
<span class="token keyword">type</span> bencodeTorrent <span class="token keyword">struct</span> <span class="token punctuation">{</span>
|
||
Announce <span class="token builtin">string</span> <span class="token string">`bencode:"announce"`</span>
|
||
Info bencodeInfo <span class="token string">`bencode:"info"`</span>
|
||
<span class="token punctuation">}</span>
|
||
|
||
<span class="token comment">// Open parses a torrent file</span>
|
||
<span class="token keyword">func</span> <span class="token function">Open</span><span class="token punctuation">(</span>r io<span class="token punctuation">.</span>Reader<span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token operator">*</span>bencodeTorrent<span class="token punctuation">,</span> <span class="token builtin">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
|
||
bto <span class="token operator">:=</span> bencodeTorrent<span class="token punctuation">{</span><span class="token punctuation">}</span>
|
||
err <span class="token operator">:=</span> bencode<span class="token punctuation">.</span><span class="token function">Unmarshal</span><span class="token punctuation">(</span>r<span class="token punctuation">,</span> <span class="token operator">&</span>bto<span class="token punctuation">)</span>
|
||
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
|
||
<span class="token keyword">return</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> err
|
||
<span class="token punctuation">}</span>
|
||
<span class="token keyword">return</span> <span class="token operator">&</span>bto<span class="token punctuation">,</span> <span class="token boolean">nil</span>
|
||
<span class="token punctuation">}</span>
|
||
</code></pre><p class="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><p class="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><p class="sc-bdvvtL kiIUjN"><img src="/guides/torrent-client/info-hash.png" alt="a name tag saying 'Hello my name is 86d4c80024a469be4c50bc5a102cf71780310074'" class="sc-iqseJM bIxQHp"/></p><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token keyword">type</span> TorrentFile <span class="token keyword">struct</span> <span class="token punctuation">{</span>
|
||
Announce <span class="token builtin">string</span>
|
||
InfoHash <span class="token punctuation">[</span><span class="token number">20</span><span class="token punctuation">]</span><span class="token builtin">byte</span>
|
||
PieceHashes <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">20</span><span class="token punctuation">]</span><span class="token builtin">byte</span>
|
||
PieceLength <span class="token builtin">int</span>
|
||
Length <span class="token builtin">int</span>
|
||
Name <span class="token builtin">string</span>
|
||
<span class="token punctuation">}</span>
|
||
|
||
<span class="token keyword">func</span> <span class="token punctuation">(</span>bto <span class="token operator">*</span>bencodeTorrent<span class="token punctuation">)</span> <span class="token function">toTorrentFile</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token operator">*</span>TorrentFile<span class="token punctuation">,</span> <span class="token builtin">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
|
||
<span class="token comment">// ...</span>
|
||
<span class="token punctuation">}</span>
|
||
</code></pre><h3 class="sc-dkPtRN sc-eCImPb dKUOdJ cabxUq">Retrieving peers from the tracker</h3><p class="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><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token keyword">func</span> <span class="token punctuation">(</span>t <span class="token operator">*</span>TorrentFile<span class="token punctuation">)</span> <span class="token function">buildTrackerURL</span><span class="token punctuation">(</span>peerID <span class="token punctuation">[</span><span class="token number">20</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> port <span class="token builtin">uint16</span><span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
|
||
base<span class="token punctuation">,</span> err <span class="token operator">:=</span> url<span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>Announce<span class="token punctuation">)</span>
|
||
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
|
||
<span class="token keyword">return</span> <span class="token string">""</span><span class="token punctuation">,</span> err
|
||
<span class="token punctuation">}</span>
|
||
params <span class="token operator">:=</span> url<span class="token punctuation">.</span>Values<span class="token punctuation">{</span>
|
||
<span class="token string">"info_hash"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span><span class="token function">string</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>InfoHash<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
|
||
<span class="token string">"peer_id"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span><span class="token function">string</span><span class="token punctuation">(</span>peerID<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
|
||
<span class="token string">"port"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span>strconv<span class="token punctuation">.</span><span class="token function">Itoa</span><span class="token punctuation">(</span><span class="token function">int</span><span class="token punctuation">(</span>Port<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
|
||
<span class="token string">"uploaded"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span><span class="token string">"0"</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
|
||
<span class="token string">"downloaded"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span><span class="token string">"0"</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
|
||
<span class="token string">"compact"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span><span class="token string">"1"</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
|
||
<span class="token string">"left"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span>strconv<span class="token punctuation">.</span><span class="token function">Itoa</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>Length<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
|
||
<span class="token punctuation">}</span>
|
||
base<span class="token punctuation">.</span>RawQuery <span class="token operator">=</span> params<span class="token punctuation">.</span><span class="token function">Encode</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
|
||
<span class="token keyword">return</span> base<span class="token punctuation">.</span><span class="token function">String</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token boolean">nil</span>
|
||
<span class="token punctuation">}</span>
|
||
</code></pre><p class="sc-bdvvtL kiIUjN">The important ones:</p><ul class="sc-egiyK gfrORe"><li class="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><li class="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><p class="sc-bdvvtL kiIUjN"><img src="/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><h3 class="sc-dkPtRN sc-eCImPb dKUOdJ cabxUq">Parsing the tracker response</h3><p class="sc-bdvvtL kiIUjN">We get back a bencoded response:</p><pre class="sc-furwcr jiGIaj language-markdown"><code class="chakra-code language-markdown css-4m8w8z">d
|
||
8:interval
|
||
i900e
|
||
5:peers
|
||
252:(another long binary blob)
|
||
e
|
||
</code></pre><p class="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><p class="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><p class="sc-bdvvtL kiIUjN"><img src="/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><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token comment">// Peer encodes connection information for a peer</span>
|
||
<span class="token keyword">type</span> Peer <span class="token keyword">struct</span> <span class="token punctuation">{</span>
|
||
IP net<span class="token punctuation">.</span>IP
|
||
Port <span class="token builtin">uint16</span>
|
||
<span class="token punctuation">}</span>
|
||
|
||
<span class="token comment">// Unmarshal parses peer IP addresses and ports from a buffer</span>
|
||
<span class="token keyword">func</span> <span class="token function">Unmarshal</span><span class="token punctuation">(</span>peersBin <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span>Peer<span class="token punctuation">,</span> <span class="token builtin">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
|
||
<span class="token keyword">const</span> peerSize <span class="token operator">=</span> <span class="token number">6</span> <span class="token comment">// 4 for IP, 2 for port</span>
|
||
numPeers <span class="token operator">:=</span> <span class="token function">len</span><span class="token punctuation">(</span>peersBin<span class="token punctuation">)</span> <span class="token operator">/</span> peerSize
|
||
<span class="token keyword">if</span> <span class="token function">len</span><span class="token punctuation">(</span>peersBin<span class="token punctuation">)</span><span class="token operator">%</span>peerSize <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span>
|
||
err <span class="token operator">:=</span> fmt<span class="token punctuation">.</span><span class="token function">Errorf</span><span class="token punctuation">(</span><span class="token string">"Received malformed peers"</span><span class="token punctuation">)</span>
|
||
<span class="token keyword">return</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> err
|
||
<span class="token punctuation">}</span>
|
||
peers <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span>Peer<span class="token punctuation">,</span> numPeers<span class="token punctuation">)</span>
|
||
<span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> numPeers<span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span>
|
||
offset <span class="token operator">:=</span> i <span class="token operator">*</span> peerSize
|
||
peers<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>IP <span class="token operator">=</span> net<span class="token punctuation">.</span><span class="token function">IP</span><span class="token punctuation">(</span>peersBin<span class="token punctuation">[</span>offset <span class="token punctuation">:</span> offset<span class="token operator">+</span><span class="token number">4</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
|
||
peers<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>Port <span class="token operator">=</span> binary<span class="token punctuation">.</span>BigEndian<span class="token punctuation">.</span><span class="token function">Uint16</span><span class="token punctuation">(</span>peersBin<span class="token punctuation">[</span>offset<span class="token operator">+</span><span class="token number">4</span> <span class="token punctuation">:</span> offset<span class="token operator">+</span><span class="token number">6</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
|
||
<span class="token punctuation">}</span>
|
||
<span class="token keyword">return</span> peers<span class="token punctuation">,</span> <span class="token boolean">nil</span>
|
||
<span class="token punctuation">}</span>
|
||
</code></pre><h2 class="sc-dkPtRN sc-hKwDye dKUOdJ kqfgwy">Downloading from peers</h2><p class="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><li class="sc-bqiRlB xTMXv">Start a TCP connection with the peer. This is like starting a phone call.</li><li class="sc-bqiRlB xTMXv">Complete a two-way BitTorrent <strong>handshake</strong>. <em>"Hello?" "Hello."</em></li><li class="sc-bqiRlB xTMXv">Exchange <strong>messages</strong> to download <strong>pieces</strong>. <em>"I'd like piece #231 please."</em></li></ol><h2 class="sc-dkPtRN sc-hKwDye dKUOdJ kqfgwy">Start a TCP connection</h2><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z">conn<span class="token punctuation">,</span> err <span class="token operator">:=</span> net<span class="token punctuation">.</span><span class="token function">DialTimeout</span><span class="token punctuation">(</span><span class="token string">"tcp"</span><span class="token punctuation">,</span> peer<span class="token punctuation">.</span><span class="token function">String</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token operator">*</span>time<span class="token punctuation">.</span>Second<span class="token punctuation">)</span>
|
||
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
|
||
<span class="token keyword">return</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> err
|
||
<span class="token punctuation">}</span>
|
||
</code></pre><p class="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><h3 class="sc-dkPtRN sc-eCImPb dKUOdJ cabxUq">Complete the handshake</h3><p class="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><ul class="sc-egiyK gfrORe"><li class="sc-bqiRlB xTMXv">can communicate using the BitTorrent protocol</li><li class="sc-bqiRlB xTMXv">is able to understand and respond to our messages</li><li class="sc-bqiRlB xTMXv">has the file that we want, or at least knows what we're talking about</li></ul><p class="sc-bdvvtL kiIUjN"><img src="/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><p class="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><li class="sc-bqiRlB xTMXv">The length of the protocol identifier, which is always 19 (0x13 in hex)</li><li class="sc-bqiRlB xTMXv">The protocol identifier, called the <strong>pstr</strong> which is always <code>BitTorrent protocol</code></li><li class="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 <a href="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><li class="sc-bqiRlB xTMXv">The <strong>infohash</strong> that we calculated earlier to identify which file we want</li><li class="sc-bqiRlB xTMXv">The <strong>Peer ID</strong> that we made up to identify ourselves</li></ol><p class="sc-bdvvtL kiIUjN">Put together, a handshake string might look like this:</p><pre class="sc-furwcr jiGIaj language-markdown"><code class="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><p class="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><p class="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><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token comment">// A Handshake is a special message that a peer uses to identify itself</span>
|
||
<span class="token keyword">type</span> Handshake <span class="token keyword">struct</span> <span class="token punctuation">{</span>
|
||
Pstr <span class="token builtin">string</span>
|
||
InfoHash <span class="token punctuation">[</span><span class="token number">20</span><span class="token punctuation">]</span><span class="token builtin">byte</span>
|
||
PeerID <span class="token punctuation">[</span><span class="token number">20</span><span class="token punctuation">]</span><span class="token builtin">byte</span>
|
||
<span class="token punctuation">}</span>
|
||
|
||
<span class="token comment">// Serialize serializes the handshake to a buffer</span>
|
||
<span class="token keyword">func</span> <span class="token punctuation">(</span>h <span class="token operator">*</span>Handshake<span class="token punctuation">)</span> <span class="token function">Serialize</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span> <span class="token punctuation">{</span>
|
||
buf <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> <span class="token function">len</span><span class="token punctuation">(</span>h<span class="token punctuation">.</span>Pstr<span class="token punctuation">)</span><span class="token operator">+</span><span class="token number">49</span><span class="token punctuation">)</span>
|
||
buf<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">byte</span><span class="token punctuation">(</span><span class="token function">len</span><span class="token punctuation">(</span>h<span class="token punctuation">.</span>Pstr<span class="token punctuation">)</span><span class="token punctuation">)</span>
|
||
curr <span class="token operator">:=</span> <span class="token number">1</span>
|
||
curr <span class="token operator">+=</span> <span class="token function">copy</span><span class="token punctuation">(</span>buf<span class="token punctuation">[</span>curr<span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">,</span> h<span class="token punctuation">.</span>Pstr<span class="token punctuation">)</span>
|
||
curr <span class="token operator">+=</span> <span class="token function">copy</span><span class="token punctuation">(</span>buf<span class="token punctuation">[</span>curr<span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> <span class="token number">8</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// 8 reserved bytes</span>
|
||
curr <span class="token operator">+=</span> <span class="token function">copy</span><span class="token punctuation">(</span>buf<span class="token punctuation">[</span>curr<span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">,</span> h<span class="token punctuation">.</span>InfoHash<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
|
||
curr <span class="token operator">+=</span> <span class="token function">copy</span><span class="token punctuation">(</span>buf<span class="token punctuation">[</span>curr<span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">,</span> h<span class="token punctuation">.</span>PeerID<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
|
||
<span class="token keyword">return</span> buf
|
||
<span class="token punctuation">}</span>
|
||
|
||
<span class="token comment">// Read parses a handshake from a stream</span>
|
||
<span class="token keyword">func</span> <span class="token function">Read</span><span class="token punctuation">(</span>r io<span class="token punctuation">.</span>Reader<span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token operator">*</span>Handshake<span class="token punctuation">,</span> <span class="token builtin">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
|
||
<span class="token comment">// Do Serialize(), but backwards</span>
|
||
<span class="token comment">// ...</span>
|
||
<span class="token punctuation">}</span>
|
||
</code></pre><h3 class="sc-dkPtRN sc-eCImPb dKUOdJ cabxUq">Send and receive messages</h3><p class="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><p class="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><p class="sc-bdvvtL kiIUjN"><img src="/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><h4 class="sc-dkPtRN sc-jRQBWg dKUOdJ gRXaYU">Interpreting messages</h4><p class="sc-bdvvtL kiIUjN">A message has a length, an <strong>ID</strong> and a <strong>payload</strong>. On the wire, it looks like:</p><p class="sc-bdvvtL kiIUjN"><img src="/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><p class="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><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token keyword">type</span> messageID <span class="token builtin">uint8</span>
|
||
|
||
<span class="token keyword">const</span> <span class="token punctuation">(</span>
|
||
MsgChoke messageID <span class="token operator">=</span> <span class="token number">0</span>
|
||
MsgUnchoke messageID <span class="token operator">=</span> <span class="token number">1</span>
|
||
MsgInterested messageID <span class="token operator">=</span> <span class="token number">2</span>
|
||
MsgNotInterested messageID <span class="token operator">=</span> <span class="token number">3</span>
|
||
MsgHave messageID <span class="token operator">=</span> <span class="token number">4</span>
|
||
MsgBitfield messageID <span class="token operator">=</span> <span class="token number">5</span>
|
||
MsgRequest messageID <span class="token operator">=</span> <span class="token number">6</span>
|
||
MsgPiece messageID <span class="token operator">=</span> <span class="token number">7</span>
|
||
MsgCancel messageID <span class="token operator">=</span> <span class="token number">8</span>
|
||
<span class="token punctuation">)</span>
|
||
|
||
<span class="token comment">// Message stores ID and payload of a message</span>
|
||
<span class="token keyword">type</span> Message <span class="token keyword">struct</span> <span class="token punctuation">{</span>
|
||
ID messageID
|
||
Payload <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span>
|
||
<span class="token punctuation">}</span>
|
||
|
||
<span class="token comment">// Serialize serializes a message into a buffer of the form</span>
|
||
<span class="token comment">// <length prefix><message ID><payload></span>
|
||
<span class="token comment">// Interprets `nil` as a keep-alive message</span>
|
||
<span class="token keyword">func</span> <span class="token punctuation">(</span>m <span class="token operator">*</span>Message<span class="token punctuation">)</span> <span class="token function">Serialize</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span> <span class="token punctuation">{</span>
|
||
<span class="token keyword">if</span> m <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
|
||
<span class="token keyword">return</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">)</span>
|
||
<span class="token punctuation">}</span>
|
||
length <span class="token operator">:=</span> <span class="token function">uint32</span><span class="token punctuation">(</span><span class="token function">len</span><span class="token punctuation">(</span>m<span class="token punctuation">.</span>Payload<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token comment">// +1 for id</span>
|
||
buf <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token operator">+</span>length<span class="token punctuation">)</span>
|
||
binary<span class="token punctuation">.</span>BigEndian<span class="token punctuation">.</span><span class="token function">PutUint32</span><span class="token punctuation">(</span>buf<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">:</span><span class="token number">4</span><span class="token punctuation">]</span><span class="token punctuation">,</span> length<span class="token punctuation">)</span>
|
||
buf<span class="token punctuation">[</span><span class="token number">4</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">byte</span><span class="token punctuation">(</span>m<span class="token punctuation">.</span>ID<span class="token punctuation">)</span>
|
||
<span class="token function">copy</span><span class="token punctuation">(</span>buf<span class="token punctuation">[</span><span class="token number">5</span><span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">,</span> m<span class="token punctuation">.</span>Payload<span class="token punctuation">)</span>
|
||
<span class="token keyword">return</span> buf
|
||
<span class="token punctuation">}</span>
|
||
</code></pre><p class="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><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token comment">// Read parses a message from a stream. Returns `nil` on keep-alive message</span>
|
||
<span class="token keyword">func</span> <span class="token function">Read</span><span class="token punctuation">(</span>r io<span class="token punctuation">.</span>Reader<span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token operator">*</span>Message<span class="token punctuation">,</span> <span class="token builtin">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
|
||
lengthBuf <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">)</span>
|
||
<span class="token boolean">_</span><span class="token punctuation">,</span> err <span class="token operator">:=</span> io<span class="token punctuation">.</span><span class="token function">ReadFull</span><span class="token punctuation">(</span>r<span class="token punctuation">,</span> lengthBuf<span class="token punctuation">)</span>
|
||
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
|
||
<span class="token keyword">return</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> err
|
||
<span class="token punctuation">}</span>
|
||
length <span class="token operator">:=</span> binary<span class="token punctuation">.</span>BigEndian<span class="token punctuation">.</span><span class="token function">Uint32</span><span class="token punctuation">(</span>lengthBuf<span class="token punctuation">)</span>
|
||
|
||
<span class="token comment">// keep-alive message</span>
|
||
<span class="token keyword">if</span> length <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span>
|
||
<span class="token keyword">return</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> <span class="token boolean">nil</span>
|
||
<span class="token punctuation">}</span>
|
||
|
||
messageBuf <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> length<span class="token punctuation">)</span>
|
||
<span class="token boolean">_</span><span class="token punctuation">,</span> err <span class="token operator">=</span> io<span class="token punctuation">.</span><span class="token function">ReadFull</span><span class="token punctuation">(</span>r<span class="token punctuation">,</span> messageBuf<span class="token punctuation">)</span>
|
||
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
|
||
<span class="token keyword">return</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> err
|
||
<span class="token punctuation">}</span>
|
||
|
||
m <span class="token operator">:=</span> Message<span class="token punctuation">{</span>
|
||
ID<span class="token punctuation">:</span> <span class="token function">messageID</span><span class="token punctuation">(</span>messageBuf<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
|
||
Payload<span class="token punctuation">:</span> messageBuf<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
|
||
<span class="token punctuation">}</span>
|
||
|
||
<span class="token keyword">return</span> <span class="token operator">&</span>m<span class="token punctuation">,</span> <span class="token boolean">nil</span>
|
||
<span class="token punctuation">}</span>
|
||
</code></pre><h4 class="sc-dkPtRN sc-jRQBWg dKUOdJ gRXaYU">Bitfields</h4><p class="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><p class="sc-bdvvtL kiIUjN"><img src="/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><p class="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><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token comment">// A Bitfield represents the pieces that a peer has</span>
|
||
<span class="token keyword">type</span> Bitfield <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span>
|
||
|
||
<span class="token comment">// HasPiece tells if a bitfield has a particular index set</span>
|
||
<span class="token keyword">func</span> <span class="token punctuation">(</span>bf Bitfield<span class="token punctuation">)</span> <span class="token function">HasPiece</span><span class="token punctuation">(</span>index <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token builtin">bool</span> <span class="token punctuation">{</span>
|
||
byteIndex <span class="token operator">:=</span> index <span class="token operator">/</span> <span class="token number">8</span>
|
||
offset <span class="token operator">:=</span> index <span class="token operator">%</span> <span class="token number">8</span>
|
||
<span class="token keyword">return</span> bf<span class="token punctuation">[</span>byteIndex<span class="token punctuation">]</span><span class="token operator">>></span><span class="token punctuation">(</span><span class="token number">7</span><span class="token operator">-</span>offset<span class="token punctuation">)</span><span class="token operator">&</span><span class="token number">1</span> <span class="token operator">!=</span> <span class="token number">0</span>
|
||
<span class="token punctuation">}</span>
|
||
|
||
<span class="token comment">// SetPiece sets a bit in the bitfield</span>
|
||
<span class="token keyword">func</span> <span class="token punctuation">(</span>bf Bitfield<span class="token punctuation">)</span> <span class="token function">SetPiece</span><span class="token punctuation">(</span>index <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
|
||
byteIndex <span class="token operator">:=</span> index <span class="token operator">/</span> <span class="token number">8</span>
|
||
offset <span class="token operator">:=</span> index <span class="token operator">%</span> <span class="token number">8</span>
|
||
bf<span class="token punctuation">[</span>byteIndex<span class="token punctuation">]</span> <span class="token operator">|=</span> <span class="token number">1</span> <span class="token operator"><<</span> <span class="token punctuation">(</span><span class="token number">7</span> <span class="token operator">-</span> offset<span class="token punctuation">)</span>
|
||
<span class="token punctuation">}</span>
|
||
</code></pre><h3 class="sc-dkPtRN sc-eCImPb dKUOdJ cabxUq">Putting it all together</h3><p class="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><h4 class="sc-dkPtRN sc-jRQBWg dKUOdJ gRXaYU">Managing concurrency: channels as queues</h4><p class="sc-bdvvtL kiIUjN">In Go, we <a href="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><p class="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><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token comment">// Init queues for workers to retrieve work and send results</span>
|
||
workQueue <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token operator">*</span>pieceWork<span class="token punctuation">,</span> <span class="token function">len</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>PieceHashes<span class="token punctuation">)</span><span class="token punctuation">)</span>
|
||
results <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token operator">*</span>pieceResult<span class="token punctuation">)</span>
|
||
<span class="token keyword">for</span> index<span class="token punctuation">,</span> hash <span class="token operator">:=</span> <span class="token keyword">range</span> t<span class="token punctuation">.</span>PieceHashes <span class="token punctuation">{</span>
|
||
length <span class="token operator">:=</span> t<span class="token punctuation">.</span><span class="token function">calculatePieceSize</span><span class="token punctuation">(</span>index<span class="token punctuation">)</span>
|
||
workQueue <span class="token operator"><-</span> <span class="token operator">&</span>pieceWork<span class="token punctuation">{</span>index<span class="token punctuation">,</span> hash<span class="token punctuation">,</span> length<span class="token punctuation">}</span>
|
||
<span class="token punctuation">}</span>
|
||
|
||
<span class="token comment">// Start workers</span>
|
||
<span class="token keyword">for</span> <span class="token boolean">_</span><span class="token punctuation">,</span> peer <span class="token operator">:=</span> <span class="token keyword">range</span> t<span class="token punctuation">.</span>Peers <span class="token punctuation">{</span>
|
||
<span class="token keyword">go</span> t<span class="token punctuation">.</span><span class="token function">startDownloadWorker</span><span class="token punctuation">(</span>peer<span class="token punctuation">,</span> workQueue<span class="token punctuation">,</span> results<span class="token punctuation">)</span>
|
||
<span class="token punctuation">}</span>
|
||
|
||
<span class="token comment">// Collect results into a buffer until full</span>
|
||
buf <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> t<span class="token punctuation">.</span>Length<span class="token punctuation">)</span>
|
||
donePieces <span class="token operator">:=</span> <span class="token number">0</span>
|
||
<span class="token keyword">for</span> donePieces <span class="token operator"><</span> <span class="token function">len</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>PieceHashes<span class="token punctuation">)</span> <span class="token punctuation">{</span>
|
||
res <span class="token operator">:=</span> <span class="token operator"><-</span>results
|
||
begin<span class="token punctuation">,</span> end <span class="token operator">:=</span> t<span class="token punctuation">.</span><span class="token function">calculateBoundsForPiece</span><span class="token punctuation">(</span>res<span class="token punctuation">.</span>index<span class="token punctuation">)</span>
|
||
<span class="token function">copy</span><span class="token punctuation">(</span>buf<span class="token punctuation">[</span>begin<span class="token punctuation">:</span>end<span class="token punctuation">]</span><span class="token punctuation">,</span> res<span class="token punctuation">.</span>buf<span class="token punctuation">)</span>
|
||
donePieces<span class="token operator">++</span>
|
||
<span class="token punctuation">}</span>
|
||
<span class="token function">close</span><span class="token punctuation">(</span>workQueue<span class="token punctuation">)</span>
|
||
</code></pre><p class="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><p class="sc-bdvvtL kiIUjN"><img src="/guides/torrent-client/download.png" alt="a flow chart of the download strategy" class="sc-iqseJM bIxQHp"/></p><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token keyword">func</span> <span class="token punctuation">(</span>t <span class="token operator">*</span>Torrent<span class="token punctuation">)</span> <span class="token function">startDownloadWorker</span><span class="token punctuation">(</span>peer peers<span class="token punctuation">.</span>Peer<span class="token punctuation">,</span> workQueue <span class="token keyword">chan</span> <span class="token operator">*</span>pieceWork<span class="token punctuation">,</span> results <span class="token keyword">chan</span> <span class="token operator">*</span>pieceResult<span class="token punctuation">)</span> <span class="token punctuation">{</span>
|
||
c<span class="token punctuation">,</span> err <span class="token operator">:=</span> client<span class="token punctuation">.</span><span class="token function">New</span><span class="token punctuation">(</span>peer<span class="token punctuation">,</span> t<span class="token punctuation">.</span>PeerID<span class="token punctuation">,</span> t<span class="token punctuation">.</span>InfoHash<span class="token punctuation">)</span>
|
||
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
|
||
log<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"Could not handshake with %s. Disconnecting\n"</span><span class="token punctuation">,</span> peer<span class="token punctuation">.</span>IP<span class="token punctuation">)</span>
|
||
<span class="token keyword">return</span>
|
||
<span class="token punctuation">}</span>
|
||
<span class="token keyword">defer</span> c<span class="token punctuation">.</span>Conn<span class="token punctuation">.</span><span class="token function">Close</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
|
||
log<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"Completed handshake with %s\n"</span><span class="token punctuation">,</span> peer<span class="token punctuation">.</span>IP<span class="token punctuation">)</span>
|
||
|
||
c<span class="token punctuation">.</span><span class="token function">SendUnchoke</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
|
||
c<span class="token punctuation">.</span><span class="token function">SendInterested</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
|
||
|
||
<span class="token keyword">for</span> pw <span class="token operator">:=</span> <span class="token keyword">range</span> workQueue <span class="token punctuation">{</span>
|
||
<span class="token keyword">if</span> <span class="token operator">!</span>c<span class="token punctuation">.</span>Bitfield<span class="token punctuation">.</span><span class="token function">HasPiece</span><span class="token punctuation">(</span>pw<span class="token punctuation">.</span>index<span class="token punctuation">)</span> <span class="token punctuation">{</span>
|
||
workQueue <span class="token operator"><-</span> pw <span class="token comment">// Put piece back on the queue</span>
|
||
<span class="token keyword">continue</span>
|
||
<span class="token punctuation">}</span>
|
||
|
||
<span class="token comment">// Download the piece</span>
|
||
buf<span class="token punctuation">,</span> err <span class="token operator">:=</span> <span class="token function">attemptDownloadPiece</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> pw<span class="token punctuation">)</span>
|
||
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
|
||
log<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"Exiting"</span><span class="token punctuation">,</span> err<span class="token punctuation">)</span>
|
||
workQueue <span class="token operator"><-</span> pw <span class="token comment">// Put piece back on the queue</span>
|
||
<span class="token keyword">return</span>
|
||
<span class="token punctuation">}</span>
|
||
|
||
err <span class="token operator">=</span> <span class="token function">checkIntegrity</span><span class="token punctuation">(</span>pw<span class="token punctuation">,</span> buf<span class="token punctuation">)</span>
|
||
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
|
||
log<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"Piece #%d failed integrity check\n"</span><span class="token punctuation">,</span> pw<span class="token punctuation">.</span>index<span class="token punctuation">)</span>
|
||
workQueue <span class="token operator"><-</span> pw <span class="token comment">// Put piece back on the queue</span>
|
||
<span class="token keyword">continue</span>
|
||
<span class="token punctuation">}</span>
|
||
|
||
c<span class="token punctuation">.</span><span class="token function">SendHave</span><span class="token punctuation">(</span>pw<span class="token punctuation">.</span>index<span class="token punctuation">)</span>
|
||
results <span class="token operator"><-</span> <span class="token operator">&</span>pieceResult<span class="token punctuation">{</span>pw<span class="token punctuation">.</span>index<span class="token punctuation">,</span> buf<span class="token punctuation">}</span>
|
||
<span class="token punctuation">}</span>
|
||
<span class="token punctuation">}</span>
|
||
</code></pre><h4 class="sc-dkPtRN sc-jRQBWg dKUOdJ gRXaYU">Managing state</h4><p class="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><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token keyword">type</span> pieceProgress <span class="token keyword">struct</span> <span class="token punctuation">{</span>
|
||
index <span class="token builtin">int</span>
|
||
client <span class="token operator">*</span>client<span class="token punctuation">.</span>Client
|
||
buf <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span>
|
||
downloaded <span class="token builtin">int</span>
|
||
requested <span class="token builtin">int</span>
|
||
backlog <span class="token builtin">int</span>
|
||
<span class="token punctuation">}</span>
|
||
|
||
<span class="token keyword">func</span> <span class="token punctuation">(</span>state <span class="token operator">*</span>pieceProgress<span class="token punctuation">)</span> <span class="token function">readMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">error</span> <span class="token punctuation">{</span>
|
||
msg<span class="token punctuation">,</span> err <span class="token operator">:=</span> state<span class="token punctuation">.</span>client<span class="token punctuation">.</span><span class="token function">Read</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// this call blocks</span>
|
||
<span class="token keyword">switch</span> msg<span class="token punctuation">.</span>ID <span class="token punctuation">{</span>
|
||
<span class="token keyword">case</span> message<span class="token punctuation">.</span>MsgUnchoke<span class="token punctuation">:</span>
|
||
state<span class="token punctuation">.</span>client<span class="token punctuation">.</span>Choked <span class="token operator">=</span> <span class="token boolean">false</span>
|
||
<span class="token keyword">case</span> message<span class="token punctuation">.</span>MsgChoke<span class="token punctuation">:</span>
|
||
state<span class="token punctuation">.</span>client<span class="token punctuation">.</span>Choked <span class="token operator">=</span> <span class="token boolean">true</span>
|
||
<span class="token keyword">case</span> message<span class="token punctuation">.</span>MsgHave<span class="token punctuation">:</span>
|
||
index<span class="token punctuation">,</span> err <span class="token operator">:=</span> message<span class="token punctuation">.</span><span class="token function">ParseHave</span><span class="token punctuation">(</span>msg<span class="token punctuation">)</span>
|
||
state<span class="token punctuation">.</span>client<span class="token punctuation">.</span>Bitfield<span class="token punctuation">.</span><span class="token function">SetPiece</span><span class="token punctuation">(</span>index<span class="token punctuation">)</span>
|
||
<span class="token keyword">case</span> message<span class="token punctuation">.</span>MsgPiece<span class="token punctuation">:</span>
|
||
n<span class="token punctuation">,</span> err <span class="token operator">:=</span> message<span class="token punctuation">.</span><span class="token function">ParsePiece</span><span class="token punctuation">(</span>state<span class="token punctuation">.</span>index<span class="token punctuation">,</span> state<span class="token punctuation">.</span>buf<span class="token punctuation">,</span> msg<span class="token punctuation">)</span>
|
||
state<span class="token punctuation">.</span>downloaded <span class="token operator">+=</span> n
|
||
state<span class="token punctuation">.</span>backlog<span class="token operator">--</span>
|
||
<span class="token punctuation">}</span>
|
||
<span class="token keyword">return</span> <span class="token boolean">nil</span>
|
||
<span class="token punctuation">}</span>
|
||
</code></pre><h4 class="sc-dkPtRN sc-jRQBWg dKUOdJ gRXaYU">Time to make requests!</h4><p class="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><p class="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><h4 class="sc-dkPtRN sc-jRQBWg dKUOdJ gRXaYU">Pipelining</h4><p class="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><p class="sc-bdvvtL kiIUjN"><img src="/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><p class="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 <a href="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><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token comment">// MaxBlockSize is the largest number of bytes a request can ask for</span>
|
||
<span class="token keyword">const</span> MaxBlockSize <span class="token operator">=</span> <span class="token number">16384</span>
|
||
|
||
<span class="token comment">// MaxBacklog is the number of unfulfilled requests a client can have in its pipeline</span>
|
||
<span class="token keyword">const</span> MaxBacklog <span class="token operator">=</span> <span class="token number">5</span>
|
||
|
||
<span class="token keyword">func</span> <span class="token function">attemptDownloadPiece</span><span class="token punctuation">(</span>c <span class="token operator">*</span>client<span class="token punctuation">.</span>Client<span class="token punctuation">,</span> pw <span class="token operator">*</span>pieceWork<span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> <span class="token builtin">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
|
||
state <span class="token operator">:=</span> pieceProgress<span class="token punctuation">{</span>
|
||
index<span class="token punctuation">:</span> pw<span class="token punctuation">.</span>index<span class="token punctuation">,</span>
|
||
client<span class="token punctuation">:</span> c<span class="token punctuation">,</span>
|
||
buf<span class="token punctuation">:</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> pw<span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">,</span>
|
||
<span class="token punctuation">}</span>
|
||
|
||
<span class="token comment">// Setting a deadline helps get unresponsive peers unstuck.</span>
|
||
<span class="token comment">// 30 seconds is more than enough time to download a 262 KB piece</span>
|
||
c<span class="token punctuation">.</span>Conn<span class="token punctuation">.</span><span class="token function">SetDeadline</span><span class="token punctuation">(</span>time<span class="token punctuation">.</span><span class="token function">Now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token number">30</span> <span class="token operator">*</span> time<span class="token punctuation">.</span>Second<span class="token punctuation">)</span><span class="token punctuation">)</span>
|
||
<span class="token keyword">defer</span> c<span class="token punctuation">.</span>Conn<span class="token punctuation">.</span><span class="token function">SetDeadline</span><span class="token punctuation">(</span>time<span class="token punctuation">.</span>Time<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token comment">// Disable the deadline</span>
|
||
|
||
<span class="token keyword">for</span> state<span class="token punctuation">.</span>downloaded <span class="token operator"><</span> pw<span class="token punctuation">.</span>length <span class="token punctuation">{</span>
|
||
<span class="token comment">// If unchoked, send requests until we have enough unfulfilled requests</span>
|
||
<span class="token keyword">if</span> <span class="token operator">!</span>state<span class="token punctuation">.</span>client<span class="token punctuation">.</span>Choked <span class="token punctuation">{</span>
|
||
<span class="token keyword">for</span> state<span class="token punctuation">.</span>backlog <span class="token operator"><</span> MaxBacklog <span class="token operator">&&</span> state<span class="token punctuation">.</span>requested <span class="token operator"><</span> pw<span class="token punctuation">.</span>length <span class="token punctuation">{</span>
|
||
blockSize <span class="token operator">:=</span> MaxBlockSize
|
||
<span class="token comment">// Last block might be shorter than the typical block</span>
|
||
<span class="token keyword">if</span> pw<span class="token punctuation">.</span>length<span class="token operator">-</span>state<span class="token punctuation">.</span>requested <span class="token operator"><</span> blockSize <span class="token punctuation">{</span>
|
||
blockSize <span class="token operator">=</span> pw<span class="token punctuation">.</span>length <span class="token operator">-</span> state<span class="token punctuation">.</span>requested
|
||
<span class="token punctuation">}</span>
|
||
|
||
err <span class="token operator">:=</span> c<span class="token punctuation">.</span><span class="token function">SendRequest</span><span class="token punctuation">(</span>pw<span class="token punctuation">.</span>index<span class="token punctuation">,</span> state<span class="token punctuation">.</span>requested<span class="token punctuation">,</span> blockSize<span class="token punctuation">)</span>
|
||
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
|
||
<span class="token keyword">return</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> err
|
||
<span class="token punctuation">}</span>
|
||
state<span class="token punctuation">.</span>backlog<span class="token operator">++</span>
|
||
state<span class="token punctuation">.</span>requested <span class="token operator">+=</span> blockSize
|
||
<span class="token punctuation">}</span>
|
||
<span class="token punctuation">}</span>
|
||
|
||
err <span class="token operator">:=</span> state<span class="token punctuation">.</span><span class="token function">readMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
|
||
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
|
||
<span class="token keyword">return</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> err
|
||
<span class="token punctuation">}</span>
|
||
<span class="token punctuation">}</span>
|
||
|
||
<span class="token keyword">return</span> state<span class="token punctuation">.</span>buf<span class="token punctuation">,</span> <span class="token boolean">nil</span>
|
||
<span class="token punctuation">}</span>
|
||
</code></pre><h4 class="sc-dkPtRN sc-jRQBWg dKUOdJ gRXaYU">main.go</h4><p class="sc-bdvvtL kiIUjN">This is a short one. We're almost there.</p><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token keyword">package</span> main
|
||
|
||
<span class="token keyword">import</span> <span class="token punctuation">(</span>
|
||
<span class="token string">"log"</span>
|
||
<span class="token string">"os"</span>
|
||
|
||
<span class="token string">"github.com/veggiedefender/torrent-client/torrentfile"</span>
|
||
<span class="token punctuation">)</span>
|
||
|
||
<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
|
||
inPath <span class="token operator">:=</span> os<span class="token punctuation">.</span>Args<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span>
|
||
outPath <span class="token operator">:=</span> os<span class="token punctuation">.</span>Args<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>
|
||
|
||
tf<span class="token punctuation">,</span> err <span class="token operator">:=</span> torrentfile<span class="token punctuation">.</span><span class="token function">Open</span><span class="token punctuation">(</span>inPath<span class="token punctuation">)</span>
|
||
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
|
||
log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span>
|
||
<span class="token punctuation">}</span>
|
||
|
||
err <span class="token operator">=</span> tf<span class="token punctuation">.</span><span class="token function">DownloadToFile</span><span class="token punctuation">(</span>outPath<span class="token punctuation">)</span>
|
||
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
|
||
log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span>
|
||
<span class="token punctuation">}</span>
|
||
<span class="token punctuation">}</span>
|
||
</code></pre><script id="asciicast-xqRSB0Jec8RN91Zt89rbb9PcL" src="https://asciinema.org/a/xqRSB0Jec8RN91Zt89rbb9PcL.js" async=""></script><h2 class="sc-dkPtRN sc-hKwDye dKUOdJ kqfgwy">This isn't the full story</h2><p class="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 <a href="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><style data-emotion="css zeuzl6">.css-zeuzl6{background:var(--chakra-colors-white);border-top-width:1px;padding-top:45px;padding-bottom:60px;text-align:center;}@media screen and (min-width: 30em){.css-zeuzl6{padding-top:45px;padding-bottom:60px;}}@media screen and (min-width: 48em){.css-zeuzl6{padding-top:70px;padding-bottom:90px;}}</style><div class="css-zeuzl6"><div class="chakra-container css-nm5t63"><style data-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;}@media screen and (min-width: 30em){.css-482acf{font-size:25px;margin-bottom:10px;}}@media screen and (min-width: 48em){.css-482acf{font-size:35px;line-height:1.2;margin-bottom:20px;}}</style><h2 class="chakra-heading css-482acf">Open Source</h2><style data-emotion="css tmji1h">.css-tmji1h{line-height:26px;font-size:15px;margin-bottom:20px;}@media screen and (min-width: 30em){.css-tmji1h{font-size:15px;}}@media screen and (min-width: 48em){.css-tmji1h{font-size:16px;}}</style><p class="chakra-text css-tmji1h">The project is OpenSource, <style data-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:2px solid transparent;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><a target="_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><iframe src="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><style data-emotion="css mz2q9v">.css-mz2q9v{line-height:25px;font-size:15px;margin-bottom:15px;}@media screen and (min-width: 30em){.css-mz2q9v{line-height:25px;font-size:15px;}}@media screen and (min-width: 48em){.css-mz2q9v{line-height:26px;font-size:16px;}}</style><p class="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><div class="css-0"><iframe src="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><style data-emotion="css llwly4">.css-llwly4{border-top-width:1px;padding-top:40px;padding-bottom:40px;text-align:left;background:var(--chakra-colors-brand-footer);}@media screen and (min-width: 30em){.css-llwly4{padding-top:40px;padding-bottom:45px;}}@media screen and (min-width: 48em){.css-llwly4{padding-top:70px;padding-bottom:80px;}}</style><div class="css-llwly4"><div class="chakra-container css-nm5t63"><style data-emotion="css 1bjthgu">.css-1bjthgu{font-family:var(--chakra-fonts-heading);font-weight:var(--chakra-fontWeights-bold);font-size:25px;line-height:1.33;color:var(--chakra-colors-gray-100);margin-bottom:5px;}@media screen and (min-width: 30em){.css-1bjthgu{font-size:25px;margin-bottom:5px;}}@media screen and (min-width: 48em){.css-1bjthgu{font-size:35px;line-height:1.2;margin-bottom:15px;}}</style><h2 class="chakra-heading css-1bjthgu">Stay Informed</h2><style data-emotion="css 1ne3i1y">.css-1ne3i1y{color:var(--chakra-colors-gray-400);line-height:26px;font-size:15px;margin-bottom:20px;}@media screen and (min-width: 30em){.css-1ne3i1y{font-size:15px;}}@media screen and (min-width: 48em){.css-1ne3i1y{font-size:16px;}}</style><p class="chakra-text css-1ne3i1y">Subscribe yourself to get updates, new guides, videos and roadmaps in your inbox.</p><style data-emotion="css u91i2o">.css-u91i2o{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;}@media screen and (min-width: 30em){.css-u91i2o{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;}}@media screen and (min-width: 48em){.css-u91i2o{-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;}}</style><div class="css-u91i2o"><style data-emotion="css bjg6se">.css-bjg6se{margin-right:0px;margin-bottom:15px;}@media screen and (min-width: 30em){.css-bjg6se{margin-right:0px;margin-bottom:15px;}}@media screen and (min-width: 48em){.css-bjg6se{margin-right:20px;margin-bottom:0px;}}</style><div class="css-bjg6se"><style data-emotion="css 6cpjh7">.css-6cpjh7{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:relative;white-space:nowrap;vertical-align:middle;outline:2px solid transparent;outline-offset:2px;width:var(--chakra-sizes-full);line-height:1.2;border-radius:var(--chakra-radii-md);font-weight:var(--chakra-fontWeights-semibold);transition-property:var(--chakra-transition-property-common);transition-duration:var(--chakra-transition-duration-normal);height:var(--chakra-sizes-10);min-width:var(--chakra-sizes-10);font-size:14px;-webkit-padding-start:var(--chakra-space-4);padding-inline-start:var(--chakra-space-4);-webkit-padding-end:var(--chakra-space-4);padding-inline-end:var(--chakra-space-4);border:1px solid;border-color:currentColor;color:var(--chakra-colors-green-600);background:var(--chakra-colors-transparent);border-width:2px;}@media screen and (min-width: 30em){.css-6cpjh7{width:auto;font-size:14px;}}.css-6cpjh7:focus,.css-6cpjh7[data-focus]{box-shadow:var(--chakra-shadows-outline);}.css-6cpjh7[disabled],.css-6cpjh7[aria-disabled=true],.css-6cpjh7[data-disabled]{opacity:0.4;cursor:not-allowed;box-shadow:var(--chakra-shadows-none);}.css-6cpjh7:hover,.css-6cpjh7[data-hover]{color:var(--chakra-colors-green-200);-webkit-text-decoration:none;text-decoration:none;}@media screen and (min-width: 48em){.css-6cpjh7{font-size:16px;}}.css-6cpjh7:active,.css-6cpjh7[data-active]{background:var(--chakra-colors-green-100);}</style><style data-emotion="css 198h90r">.css-198h90r{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:2px solid transparent;outline-offset:2px;color:inherit;display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:relative;white-space:nowrap;vertical-align:middle;outline:2px solid transparent;outline-offset:2px;width:var(--chakra-sizes-full);line-height:1.2;border-radius:var(--chakra-radii-md);font-weight:var(--chakra-fontWeights-semibold);transition-property:var(--chakra-transition-property-common);transition-duration:var(--chakra-transition-duration-normal);height:var(--chakra-sizes-10);min-width:var(--chakra-sizes-10);font-size:14px;-webkit-padding-start:var(--chakra-space-4);padding-inline-start:var(--chakra-space-4);-webkit-padding-end:var(--chakra-space-4);padding-inline-end:var(--chakra-space-4);border:1px solid;border-color:currentColor;color:var(--chakra-colors-green-600);background:var(--chakra-colors-transparent);border-width:2px;}.css-198h90r:hover,.css-198h90r[data-hover]{-webkit-text-decoration:underline;text-decoration:underline;}.css-198h90r:focus,.css-198h90r[data-focus]{box-shadow:var(--chakra-shadows-outline);}@media screen and (min-width: 30em){.css-198h90r{width:auto;font-size:14px;}}.css-198h90r:focus,.css-198h90r[data-focus]{box-shadow:var(--chakra-shadows-outline);}.css-198h90r[disabled],.css-198h90r[aria-disabled=true],.css-198h90r[data-disabled]{opacity:0.4;cursor:not-allowed;box-shadow:var(--chakra-shadows-none);}.css-198h90r:hover,.css-198h90r[data-hover]{color:var(--chakra-colors-green-200);-webkit-text-decoration:none;text-decoration:none;}@media screen and (min-width: 48em){.css-198h90r{font-size:16px;}}.css-198h90r:active,.css-198h90r[data-active]{background:var(--chakra-colors-green-100);}</style><a class="chakra-link chakra-button css-198h90r" href="/signup">Subscribe to Updates</a><style data-emotion="css ilygfh">.css-ilygfh{color:var(--chakra-colors-gray-500);font-size:13px;margin-top:5px;}</style><p class="chakra-text css-ilygfh">Free subscription for updates</p></div><div class="css-0"><style data-emotion="css 1o5lodd">.css-1o5lodd{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:relative;white-space:nowrap;vertical-align:middle;outline:2px solid transparent;outline-offset:2px;width:var(--chakra-sizes-full);line-height:1.2;border-radius:var(--chakra-radii-md);font-weight:var(--chakra-fontWeights-semibold);transition-property:var(--chakra-transition-property-common);transition-duration:var(--chakra-transition-duration-normal);height:var(--chakra-sizes-10);min-width:var(--chakra-sizes-10);font-size:14px;-webkit-padding-start:var(--chakra-space-4);padding-inline-start:var(--chakra-space-4);-webkit-padding-end:var(--chakra-space-4);padding-inline-end:var(--chakra-space-4);background:var(--chakra-colors-yellow-400);color:var(--chakra-colors-black);}@media screen and (min-width: 30em){.css-1o5lodd{width:auto;font-size:14px;}}.css-1o5lodd:focus,.css-1o5lodd[data-focus]{box-shadow:var(--chakra-shadows-outline);}.css-1o5lodd[disabled],.css-1o5lodd[aria-disabled=true],.css-1o5lodd[data-disabled]{opacity:0.4;cursor:not-allowed;box-shadow:var(--chakra-shadows-none);}.css-1o5lodd:hover,.css-1o5lodd[data-hover]{-webkit-text-decoration:none;text-decoration:none;background:var(--chakra-colors-yellow-500);}@media screen and (min-width: 48em){.css-1o5lodd{font-size:16px;}}.css-1o5lodd:active,.css-1o5lodd[data-active]{background:var(--chakra-colors-yellow-600);}</style><style data-emotion="css 1matfw4">.css-1matfw4{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:2px solid transparent;outline-offset:2px;color:inherit;display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:relative;white-space:nowrap;vertical-align:middle;outline:2px solid transparent;outline-offset:2px;width:var(--chakra-sizes-full);line-height:1.2;border-radius:var(--chakra-radii-md);font-weight:var(--chakra-fontWeights-semibold);transition-property:var(--chakra-transition-property-common);transition-duration:var(--chakra-transition-duration-normal);height:var(--chakra-sizes-10);min-width:var(--chakra-sizes-10);font-size:14px;-webkit-padding-start:var(--chakra-space-4);padding-inline-start:var(--chakra-space-4);-webkit-padding-end:var(--chakra-space-4);padding-inline-end:var(--chakra-space-4);background:var(--chakra-colors-yellow-400);color:var(--chakra-colors-black);}.css-1matfw4:hover,.css-1matfw4[data-hover]{-webkit-text-decoration:underline;text-decoration:underline;}.css-1matfw4:focus,.css-1matfw4[data-focus]{box-shadow:var(--chakra-shadows-outline);}@media screen and (min-width: 30em){.css-1matfw4{width:auto;font-size:14px;}}.css-1matfw4:focus,.css-1matfw4[data-focus]{box-shadow:var(--chakra-shadows-outline);}.css-1matfw4[disabled],.css-1matfw4[aria-disabled=true],.css-1matfw4[data-disabled]{opacity:0.4;cursor:not-allowed;box-shadow:var(--chakra-shadows-none);}.css-1matfw4:hover,.css-1matfw4[data-hover]{-webkit-text-decoration:none;text-decoration:none;background:var(--chakra-colors-yellow-500);}@media screen and (min-width: 48em){.css-1matfw4{font-size:16px;}}.css-1matfw4:active,.css-1matfw4[data-active]{background:var(--chakra-colors-yellow-600);}</style><a target="_blank" class="chakra-link chakra-button css-1matfw4" href="https://github.com/sponsors/kamranahmedse">Updates & Paid Content</a><p class="chakra-text css-ilygfh">Support the project by paying as little as<!-- --> <style data-emotion="css 35ezg3">.css-35ezg3{font-weight:600;}</style><span class="chakra-text css-35ezg3">5$ per month</span></p></div></div></div></div><style data-emotion="css t1u31f">.css-t1u31f{background:var(--chakra-colors-brand-hero);padding:25px 0;}@media screen and (min-width: 30em){.css-t1u31f{padding:25px 0;}}@media screen and (min-width: 48em){.css-t1u31f{padding:40px 0;}}</style><div class="css-t1u31f"><div class="chakra-container css-nm5t63"><style data-emotion="css 1cvpnh4">.css-1cvpnh4{display:none;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;color:var(--chakra-colors-gray-400);font-weight:600;}.css-1cvpnh4>*:not(style)~*:not(style){margin-top:0px;-webkit-margin-end:0px;margin-inline-end:0px;margin-bottom:0px;-webkit-margin-start:30px;margin-inline-start:30px;}@media screen and (min-width: 30em){.css-1cvpnh4{display:none;}}@media screen and (min-width: 48em){.css-1cvpnh4{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}}</style><div class="chakra-stack css-1cvpnh4"><style data-emotion="css 1807wg8">.css-1807wg8{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:2px solid transparent;outline-offset:2px;color:inherit;}.css-1807wg8:hover,.css-1807wg8[data-hover]{color:var(--chakra-colors-white);}.css-1807wg8:focus,.css-1807wg8[data-focus]{box-shadow:var(--chakra-shadows-outline);}</style><a class="chakra-link css-1807wg8" href="/roadmaps">Roadmaps</a><a class="chakra-link css-1807wg8" href="/guides">Guides</a><a class="chakra-link css-1807wg8" href="/watch">Videos</a><a class="chakra-link css-1807wg8" href="/about">About</a><a target="_blank" class="chakra-link css-1807wg8" href="https://youtube.com/theroadmap?sub_confirmation=1">YouTube</a></div><style data-emotion="css 1ehdw1x">.css-1ehdw1x{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;color:var(--chakra-colors-gray-400);font-weight:600;}.css-1ehdw1x>*:not(style)~*:not(style){margin-top:0px;-webkit-margin-end:0px;margin-inline-end:0px;margin-bottom:0px;-webkit-margin-start:0px;margin-inline-start:0px;}@media screen and (min-width: 30em){.css-1ehdw1x{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}}@media screen and (min-width: 48em){.css-1ehdw1x{display:none;}}</style><div class="chakra-stack css-1ehdw1x"><style data-emotion="css vsan9l">.css-vsan9l{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:2px solid transparent;outline-offset:2px;color:inherit;padding-top:7px;padding-bottom:7px;border-bottom-width:1px;border-bottom-color:var(--chakra-colors-gray-800);}.css-vsan9l:hover,.css-vsan9l[data-hover]{color:var(--chakra-colors-white);}.css-vsan9l:focus,.css-vsan9l[data-focus]{box-shadow:var(--chakra-shadows-outline);}</style><a class="chakra-link css-vsan9l" href="/roadmaps">Roadmaps</a><a class="chakra-link css-vsan9l" href="/guides">Guides</a><a class="chakra-link css-vsan9l" href="/watch">Videos</a><a class="chakra-link css-vsan9l" href="/thanks">Thanks</a><a class="chakra-link css-vsan9l" href="/about">About</a><style data-emotion="css ex4by1">.css-ex4by1{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:2px solid transparent;outline-offset:2px;color:inherit;padding-top:7px;padding-bottom:7px;}.css-ex4by1:hover,.css-ex4by1[data-hover]{color:var(--chakra-colors-white);}.css-ex4by1:focus,.css-ex4by1[data-focus]{box-shadow:var(--chakra-shadows-outline);}</style><a target="_blank" class="chakra-link css-ex4by1" href="https://youtube.com/theroadmap?sub_confirmation=1">YouTube</a></div><style data-emotion="css opw3zq">.css-opw3zq{margin-top:40px;margin-bottom:40px;max-width:500px;}@media screen and (min-width: 30em){.css-opw3zq{margin-top:40px;}}@media screen and (min-width: 48em){.css-opw3zq{margin-top:50px;}}</style><div class="css-opw3zq"><style data-emotion="css 1r78w6">.css-1r78w6{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:var(--chakra-colors-gray-400);}</style><div spacing="0" class="css-1r78w6"><style data-emotion="css 1cdtrts">.css-1cdtrts{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:2px solid transparent;outline-offset:2px;color:inherit;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-weight:600;}.css-1cdtrts:hover,.css-1cdtrts[data-hover]{-webkit-text-decoration:none;text-decoration:none;color:var(--chakra-colors-white);}.css-1cdtrts:focus,.css-1cdtrts[data-focus]{box-shadow:var(--chakra-shadows-outline);}</style><a class="chakra-link css-1cdtrts" href="/"><style data-emotion="css 1l24g6b">.css-1l24g6b{height:25px;width:25px;margin-right:6px;}</style><img alt="" src="/logo.svg" class="chakra-image css-1l24g6b"/>roadmap.sh</a><style data-emotion="css qh3ecy">.css-qh3ecy{-webkit-margin-start:7px;margin-inline-start:7px;-webkit-margin-end:7px;margin-inline-end:7px;}</style><span class="chakra-text css-qh3ecy">by</span><style data-emotion="css 1svda2y">.css-1svda2y{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:2px solid transparent;outline-offset:2px;color:var(--chakra-colors-white);background:var(--chakra-colors-blue-500);-webkit-padding-start:6px;padding-inline-start:6px;-webkit-padding-end:6px;padding-inline-end:6px;padding-top:2px;padding-bottom:2px;border-radius:4px;font-weight:600;font-size:13px;}.css-1svda2y:hover,.css-1svda2y[data-hover]{-webkit-text-decoration:none;text-decoration:none;background:var(--chakra-colors-blue-600);}.css-1svda2y:focus,.css-1svda2y[data-focus]{box-shadow:var(--chakra-shadows-outline);}</style><a target="_blank" class="chakra-link css-1svda2y" href="https://twitter.com/kamranahmedse">@kamranahmedse</a></div><style data-emotion="css gp4krv">.css-gp4krv{margin-top:15px;margin-bottom:15px;font-size:14px;color:var(--chakra-colors-gray-500);}</style><p class="chakra-text css-gp4krv">Community created roadmaps, articles, resources and journeys to help you choose your path and grow in your career.</p><style data-emotion="css 1vf9ext">.css-1vf9ext{font-size:14px;color:var(--chakra-colors-gray-500);}</style><p class="chakra-text css-1vf9ext"><style data-emotion="css lvyu5j">.css-lvyu5j{margin-right:10px;}</style><span class="chakra-text css-lvyu5j">© roadmap.sh</span>·<style data-emotion="css q9uhdd">.css-q9uhdd{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:2px solid transparent;outline-offset:2px;color:var(--chakra-colors-gray-400);-webkit-margin-start:10px;margin-inline-start:10px;-webkit-margin-end:10px;margin-inline-end:10px;}.css-q9uhdd:hover,.css-q9uhdd[data-hover]{-webkit-text-decoration:none;text-decoration:none;color:var(--chakra-colors-white);}.css-q9uhdd:focus,.css-q9uhdd[data-focus]{box-shadow:var(--chakra-shadows-outline);}</style><a class="chakra-link css-q9uhdd" href="/about">FAQs</a>·<a class="chakra-link css-q9uhdd" href="/terms">Terms</a>·<a class="chakra-link css-q9uhdd" href="/privacy">Privacy</a></p></div></div></div></div><span></span></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"guide":{"id":"torrent-client","title":"Building a BitTorrent Client","description":"Learn everything you need to know about BitTorrent by writing a client in Go","isPro":false,"authorUsername":"jesse","updatedAt":"2021-01-17T15:48:21.191Z","createdAt":"2021-01-17T15:48:21.191Z","canonical":"https://blog.jse.li/posts/torrent/","formattedCreatedAt":"January 17, 2021","formattedUpdatedAt":"January 17, 2021","author":{"username":"jesse","name":"Jesse Li","twitter":"__jesse_li","picture":"/authors/jesse.png","bio":"Software engineer."}}},"__N_SSG":true},"page":"/guides/[guide]","query":{"guide":"torrent-client"},"buildId":"ooSnr_WJpF8nzY9n_DSAx","isFallback":false,"gsp":true,"scriptLoader":[]}</script></body></html> |