CategoryResourceRepost/极客时间专栏/趣谈网络协议/第二模块 底层网络知识详解:最常用的应用层/第17讲 | P2P协议:我下小电影,99%急死你.md
louzefeng d3828a7aee mod
2024-07-11 05:50:32 +00:00

216 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<audio id="audio" title="第17讲 | P2P协议我下小电影99%急死你" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/70/af/70a9034e8e3d8cd4c67174c1e09bfcaf.mp3"></audio>
如果你想下载一个电影,一般会通过什么方式呢?
当然,最简单的方式就是通过**HTTP**进行下载。但是相信你有过这样的体验,通过浏览器下载的时候,只要文件稍微大点,下载的速度就奇慢无比。
还有种下载文件的方式,就是通过**FTP**,也即**文件传输协议**。FTP采用两个TCP连接来传输一个文件。
<li>
**控制连接**服务器以被动的方式打开众所周知用于FTP的端口21客户端则主动发起连接。该连接将命令从客户端传给服务器并传回服务器的应答。常用的命令有list——获取文件目录reter——取一个文件store——存一个文件。
</li>
<li>
**数据连接**:每当一个文件在客户端与服务器之间传输时,就创建一个数据连接。
</li>
## FTP的两种工作模式
每传输一个文件都要建立一个全新的数据连接。FTP有两种工作模式分别是**主动模式****PORT**)和**被动模式****PASV**这些都是站在FTP服务器的角度来说的。
主动模式下客户端随机打开一个大于1024的端口N向服务器的命令端口21发起连接同时开放N+1端口监听并向服务器发出 “port N+1” 命令由服务器从自己的数据端口20主动连接到客户端指定的数据端口N+1。
被动模式下当开启一个FTP连接时客户端打开两个任意的本地端口N大于1024和N+1。第一个端口连接服务器的21端口提交PASV命令。然后服务器会开启一个任意的端口P大于1024返回“227 entering passive mode”消息里面有FTP服务器开放的用来进行数据传输的端口。客户端收到消息取得端口号之后会通过N+1号端口连接服务器的端口P然后在两个端口之间进行数据传输。
## P2P是什么
但是无论是HTTP的方式还是FTP的方式都有一个比较大的缺点就是**难以解决单一服务器的带宽压力** 因为它们使用的都是传统的客户端服务器的方式。
后来一种创新的、称为P2P的方式流行起来。**P2P**就是**peer-to-peer**。资源开始并不集中地存储在某些设备上而是分散地存储在多台设备上。这些设备我们姑且称为peer。
想要下载一个文件的时候你只要得到那些已经存在了文件的peer并和这些peer之间建立点对点的连接而不需要到中心服务器上就可以就近下载文件。一旦下载了文件你也就成为peer中的一员你旁边的那些机器也可能会选择从你这里下载文件所以当你使用P2P软件的时候例如BitTorrent往往能够看到既有下载流量也有上传的流量也即你自己也加入了这个P2P的网络自己从别人那里下载同时也提供给其他人下载。可以想象这种方式参与的人越多下载速度越快一切完美。
## 种子(.torrent文件
但是有一个问题当你想下载一个文件的时候怎么知道哪些peer有这个文件呢
这就用到**种子**啦,也即咱们比较熟悉的**.torrent文件**。.torrent文件由两部分组成分别是**announce****tracker URL**)和**文件信息**。
文件信息里面有这些内容。
<li>
**info区**:这里指定的是该种子有几个文件、文件有多长、目录结构,以及目录和文件的名字。
</li>
<li>
**Name字段**:指定顶层目录名字。
</li>
<li>
**每个段的大小**BitTorrent简称BT协议把一个文件分成很多个小段然后分段下载。
</li>
<li>
**段哈希值**将整个种子中每个段的SHA-1哈希值拼在一起。
</li>
下载时BT客户端首先解析.torrent文件得到tracker地址然后连接tracker服务器。tracker服务器回应下载者的请求将其他下载者包括发布者的IP提供给下载者。下载者再连接其他下载者根据.torrent文件两者分别对方告知自己已经有的块然后交换对方没有的数据。此时不需要其他服务器参与并分散了单个线路上的数据流量因此减轻了服务器的负担。
下载者每得到一个块需要算出下载块的Hash验证码并与.torrent文件中的对比。如果一样则说明块正确不一样则需要重新下载这个块。这种规定是为了解决下载内容的准确性问题。
从这个过程也可以看出这种方式特别依赖tracker。tracker需要收集下载者信息的服务器并将此信息提供给其他下载者使下载者们相互连接起来传输数据。虽然下载的过程是非中心化的但是加入这个P2P网络的时候都需要借助tracker中心服务器这个服务器是用来登记有哪些用户在请求哪些资源。
所以这种工作方式有一个弊端一旦tracker服务器出现故障或者线路遭到屏蔽BT工具就无法正常工作了。
## 去中心化网络DHT
那能不能彻底非中心化呢?
于是,后来就有了一种叫作**DHT****Distributed Hash Table**的去中心化网络。每个加入这个DHT网络的人都要负责存储这个网络里的资源信息和其他成员的联系信息相当于所有人一起构成了一个庞大的分布式存储数据库。
有一种著名的DHT协议叫**Kademlia协议**。这个和区块链的概念一样,很抽象,我来详细讲一下这个协议。
任何一个BitTorrent启动之后它都有两个角色。一个是**peer**监听一个TCP端口用来上传和下载文件这个角色表明我这里有某个文件。另一个角色**DHT node**监听一个UDP的端口通过这个角色这个节点加入了一个DHT的网络。
<img src="https://static001.geekbang.org/resource/image/80/27/80ecacb45587d201cbb9a08c31476d27.jpg" alt=""><br>
在DHT网络里面每一个DHT node都有一个ID。这个ID是一个很长的串。每个DHT node都有责任掌握一些知识也就是**文件索引**,也即它应该知道某些文件是保存在哪些节点上。它只需要有这些知识就可以了,而它自己本身不一定就是保存这个文件的节点。
## 哈希值
当然每个DHT node不会有全局的知识也即不知道所有的文件保存在哪里它只需要知道一部分。那应该知道哪一部分呢这就需要用哈希算法计算出来。
每个文件可以计算出一个哈希值,而**DHT node的ID是和哈希值相同长度的串**。
DHT算法是这样规定的如果一个文件计算出一个哈希值则和这个哈希值一样的那个DHT node就有责任知道从哪里下载这个文件即便它自己没保存这个文件。
当然不一定这么巧总能找到和哈希值一模一样的有可能一模一样的DHT node也下线了所以DHT算法还规定除了一模一样的那个DHT node应该知道ID和这个哈希值非常接近的N个DHT node也应该知道。
什么叫和哈希值接近呢例如只修改了最后一位就很接近修改了倒数2位也不远修改了倒数3位也可以接受。总之凑齐了规定的N这个数就行。
刚才那个图里文件1通过哈希运算得到匹配ID的DHT node为node C当然还会有其他的我这里没有画出来。所以node C有责任知道文件1的存放地址虽然node C本身没有存放文件1。
同理文件2通过哈希运算得到匹配ID的DHT node为node E但是node D和E的ID值很近所以node D也知道。当然文件2本身没有必要一定在node D和E里但是碰巧这里就在E那有一份。
接下来一个新的节点node new上线了。如果想下载文件1它首先要加入DHT网络如何加入呢
在这种模式下,种子.torrent文件里面就不再是tracker的地址了而是一个list的node的地址而所有这些node都是已经在DHT网络里面的。当然随着时间的推移很可能有退出的有下线的但是我们假设不会所有的都联系不上总有一个能联系上。
node new只要在种子里面找到一个DHT node就加入了网络。
node new会计算文件1的哈希值并根据这个哈希值了解到和这个哈希值匹配或者很接近的node上知道如何下载这个文件例如计算出来的哈希值就是node C。
但是node new不知道怎么联系上node C因为种子里面的node列表里面很可能没有node C但是它可以问DHT网络特别像一个社交网络node new只有去它能联系上的node问你们知道不知道node C的联系方式呀
在DHT网络中每个node都保存了一定的联系方式但是肯定没有node的所有联系方式。DHT网络中节点之间通过互相通信也会交流联系方式也会删除联系方式。和人们的方式一样你有你的朋友圈你的朋友有它的朋友圈你们互相加微信就互相认识了过一段时间不联系就删除朋友关系。
有个理论是,社交网络中,任何两个人直接的距离不超过六度,也即你想联系比尔盖茨,也就六个人就能够联系到了。
所以node new想联系node C就去万能的朋友圈去问并且求转发朋友再问朋友很快就能找到。如果找不到C也能找到和C的ID很像的节点它们也知道如何下载文件1。
在node C上告诉node new下载文件1要去B、D、 F于是node new选择和node B进行peer连接开始下载它一旦开始下载自己本地也有文件1了于是node new告诉node C以及和node C的ID很像的那些节点我也有文件1了可以加入那个文件拥有者列表了。
但是你会发现node new上没有文件索引但是根据哈希算法一定会有某些文件的哈希值是和node new的ID匹配上的。在DHT网络中会有节点告诉它你既然加入了咱们这个网络你也有责任知道某些文件的下载地址。
好了,一切都分布式了。
这里面遗留几个细节的问题。
- DHT node ID以及文件哈希是个什么东西
节点ID是一个随机选择的160bits20字节空间文件的哈希也使用这样的160bits空间。
- 所谓ID相似具体到什么程度算相似
在Kademlia网络中距离是通过异或XOR计算的。我们就不以160bits举例了。我们以5位来举例。
01010与01000的距离就是两个ID之间的异或值为00010也即为2。 01010与00010的距离为01000也即为8,。01010与00011的距离为01001也即8+1=9 。以此类推,高位不同的,表示距离更远一些;低位不同的,表示距离更近一些,总的距离为所有的不同的位的距离之和。
这个距离不能比喻为地理位置因为在Kademlia网络中位置近不算近ID近才算近所以我把这个距离比喻为社交距离也即在朋友圈中的距离或者社交网络中的距离。这个和你住的位置没有关系和人的经历关系比较大。
还是以5位ID来举例就像在领英中排第一位的表示最近一份工作在哪里第二位的表示上一份工作在哪里然后第三位的是上上份工作第四位的是研究生在哪里读第五位的表示大学在哪里读。
如果你是一个猎头,在上面找候选人,当然最近的那份工作是最重要的。而对于工作经历越丰富的候选人,大学在哪里读的反而越不重要。
## DHT网络中的朋友圈是怎么维护的
就像人一样虽然我们常联系人的只有少数但是朋友圈里肯定是远近都有。DHT网络的朋友圈也是一样远近都有并且**按距离分层**。
假设某个节点的ID为01010如果一个节点的ID前面所有位数都与它相同只有最后1位不同。这样的节点只有1个为01011。与基础节点的异或值为00001即距离为1对于01010而言这样的节点归为“k-bucket 1”。
如果一个节点的ID前面所有位数都相同从倒数第2位开始不同这样的节点只有2个即01000和01001与基础节点的异或值为00010和00011即距离范围为2和3对于01010而言这样的节点归为“k-bucket 2”。
如果一个节点的ID前面所有位数相同从倒数第i位开始不同这样的节点只有2^(i-1)个,与基础节点的距离范围为[2^(i-1), 2^i)对于01010而言这样的节点归为“k-bucket i”。
最终到从倒数160位就开始都不同。
你会发现差距越大陌生人越多但是朋友圈不能都放下所以每一层都只放K个这是参数可以配置。
## DHT网络是如何查找朋友的
假设node A 的ID为00110要找node B ID为10000异或距离为10110距离范围在[2^4, 2^5)所以这个目标节点可能在“k-bucket 5”中这就说明B的ID与A的ID从第5位开始不同所以B可能在“k-bucket 5”中。
然后A看看自己的k-bucket 5有没有B。如果有太好了找到你了如果没有在k-bucket 5里随便找一个C。因为是二进制C、B都和A的第5位不同那么C的ID第5位肯定与B相同即它与B的距离会小于2^4相当于比A、B之间的距离缩短了一半以上。
再请求C在它自己的通讯录里按同样的查找方式找一下B。如果C知道B就告诉A如果C也不知道B那C按同样的搜索方法可以在自己的通讯录里找到一个离B更近的D朋友D、B之间距离小于2^3把D推荐给AA请求D进行下一步查找。
Kademlia的这种查询机制是通过折半查找的方式来收缩范围对于总的节点数目为N最多只需要查询log2(N)次,就能够找到。
例如,图中这个最差的情况。
<img src="https://static001.geekbang.org/resource/image/dc/b4/dc6d713751d09ea5dd0d79c65433aeb4.jpg" alt="">
A和B每一位都不一样所以相差31A找到的朋友C不巧正好在中间。和A的距离是16和B距离为15于是C去自己朋友圈找的时候不巧找到D正好又在中间距离C为8距离B为7。于是D去自己朋友圈找的时候不巧找到E正好又在中间距离D为4距离B为3E在朋友圈找到F距离E为2距离B为1最终在F的朋友圈距离1的地方找到B。当然这是最最不巧的情况每次找到的朋友都不远不近正好在中间。
如果碰巧了在A的朋友圈里面有G距离B只有3然后在G的朋友圈里面一下子就找到了B两次就找到了。
在DHT网络中朋友之间怎么沟通呢
Kademlia算法中每个节点只有4个指令。
<li>
PING测试一个节点是否在线还活着没相当于打个电话看还能打通不。
</li>
<li>
STORE要求一个节点存储一份数据既然加入了组织有义务保存一份数据。
</li>
<li>
FIND_NODE根据节点ID查找一个节点就是给一个160位的ID通过上面朋友圈的方式找到那个节点。
</li>
<li>
FIND_VALUE根据KEY查找一个数据实则上跟FIND_NODE非常类似。KEY就是文件对应的160位的ID就是要找到保存了文件的节点。
</li>
DHT网络中朋友圈如何更新呢
<li>
每个bucket里的节点都按最后一次接触的时间倒序排列这就相当于朋友圈里面最近联系过的人往往是最熟的。
</li>
<li>
每次执行四个指令中的任意一个都会触发更新。
</li>
<li>
当一个节点与自己接触时检查它是否已经在k-bucket中也就是说是否已经在朋友圈。如果在那么将它挪到k-bucket列表的最底也就是最新的位置刚联系过就置顶一下方便以后多联系如果不在新的联系人要不要加到通讯录里面呢假设通讯录已满的情况PING一下列表最上面也即最旧的一个节点。如果PING通了将旧节点挪到列表最底并丢弃新节点老朋友还是留一下如果PING不通删除旧节点并将新节点加入列表这人联系不上了删了吧。
</li>
这个机制保证了任意节点加入和离开都不影响整体网络。
## 小结
好了,今天的讲解就到这里了,我们总结一下:
<li>
下载一个文件可以使用HTTP或FTP这两种都是集中下载的方式而P2P则换了一种思路采取非中心化下载的方式
</li>
<li>
P2P也是有两种一种是依赖于tracker的也即元数据集中文件数据分散另一种是基于分布式的哈希算法元数据和文件数据全部分散。
</li>
接下来,给你留两个思考题:
<li>
除了这种去中心化分布式哈希的算法,你还能想到其他的应用场景吗?
</li>
<li>
在前面所有的章节中要下载一个文件都需要使用域名。但是网络通信是使用IP的那你知道怎么实现两者的映射机制吗
</li>
我们的专栏马上更新过半了,不知你掌握得如何?每节课后我留的思考题,你都有没有认真思考,并在留言区写下答案呢?我会从已发布的文章中选出一批认真留言的同学,赠送学习奖励礼券和我整理的独家网络协议知识图谱。
欢迎你留言和我讨论。趣谈网络协议,我们下期见!