网络协议 15 - P2P 协议:小种子大学问

【前五篇】系列文章传送门:html

  1. 网络协议 10 - Socket 编程(上):实践是检验真理的惟一标准
  2. 网络协议 11 - Socket 编程(下):眼见为实耳听为虚
  3. 网络协议 12 - HTTP 协议:经常使用而不简单
  4. 网络协议 13 - HTTPS 协议:加密路上无尽头
  5. 网络协议 14 - 流媒体协议:要说爱你不容易

    “兄弟,有种子吗?”
    “什么种子?小麦种吗?”
    “......,来,哥今天带你认识下什么是种子”。算法

    你们提及种子,应该都知道是用来下载资源的。那么资源下载都有哪些方式?种子下载又有什么优点呢?数据库

下载电影的两种方式

    第一种是经过 HTTP 进行下载。这种方式,有过经历的人应该体会到,当下载文件稍大点,下载速度简直能把人急死。编程

    第二种方式就是是经过 FTP(文件传输协议)。FTP 采用两个 TCP 链接来传输一个文件。服务器

  1. 控制链接。服务器以被动的方式,打开众所周知用于 FTP 的端口 21,客户端则主动发起链接。该链接将命令从客户端传给服务器,并传回服务器的应答。经常使用的命令有:lsit - 获取文件目录,reter - 取一个文件,store - 存一个文件;
  2. 数据链接。每当一个文件在客户端与服务器之间传输时,就建立一个数据链接。

FTP 的工做模式

    在 FTP 的两个 TCP 链接中,每传输一个文件,都要新创建一个数据链接。基于这个数据链接,FTP 又有两种工做模式:主动模式(PORT)被动模式(PASV),要注意的是,这里的主动和被动都是站在服务器角度来讲的。工做模式过程以下:微信

主动模式工做流程网络

  1. 客户端随机打开一个大于 1024 的端口 N,向服务器的命令端口 21 发起链接,同时开放 N+1 端口监听,并向服务器发出“port N+1” 命令;
  2. 由服务器从本身的数据端口 20,主动链接到客户端指定的数据端口 N+1

被动模式工做流程分布式

  1. 客户端在开启一个 FTP 链接时,打开两个任意的本地端口 N(大于1024)和 N+1。而后用 N 端口链接服务器的 21 端口,提交 PASV 命令;
  2. 服务器收到命令,开启一个任意的端口 P(大于 1024),返回“227 entering passive mode”消息,消息里有服务器开放的用来进行数据传输的端口号 P。
  3. 客户端收到消息,取得端口号 P,经过 N+1 端口链接服务器的 P 端口,进行数据传输。

    上面说了 HTTP 下载和 FTP 下载,这两种方式都有一个大缺点-难以解决单一服务器的带宽压力。由于它们使用的都是传统 C/S 结构,这种结构会随着客户端的增多,下载速度愈来愈慢。这在当今互联网世界显然是不合理的,咱们指望能实现“下载人数越多,下载速度不变甚至更快”的愿望。工具

    后来,一种创新的,称为 P2P 的方式实现了咱们的愿望。测试

P2P

    P2P 就是 peer-to-peer。这种方式的特色是,资源一开始并不集中存储在某些设备上,而是分散地存储在多台设备上,这些设备咱们称为 peer。

    在下载一个文件时,只要获得那些已经存在了文件的 peer 地址,并和这些 peer 创建点对点的链接,就能够就近下载文件,而不须要到中心服务器上。一旦下载了文件,你的设备也就称为这个网络的一个 peer,你旁边的那些机器也可能会选择从你这里下载文件。

    经过这种方式解决上面 C/S 结构单一服务器带宽压力问题。若是使用过 P2P2 软件,例如 BitTorrent,你就会看到本身网络不只有下载流量,还有上传流量,也就是说你加入了这个 P2P 网络,本身能够从这个网络里下载,同时别人也能够从你这里下载。这样就实现了,下载人数越多,下载速度越快的愿望

种子文件(.torent)

    上面整个过程是否是很完美?是的,结果很美好,但为了实现这个美好,咱们仍是有不少准备工做要作的。好比,咱们怎么知道哪些 peer 有某个文件呢?

    这就用到咱们常说的种子(.torrent)。 .torrent 文件由Announce(Tracker URL)文件信息两部分组成。

    其中,文件信息里有如下内容:

  • Info 区:指定该种子包含的文件数量、文件大小及目录结构,包括目录名和文件名;
  • Name 字段:指定顶层目录名字;
  • 每一个段的大小:BitTorrent(BT)协议把一个文件分红不少个小段,而后分段下载;
  • 段哈希值:将整个种子种,每一个段的 SHA-1 哈希值拼在一块儿。

    下载时,BT 客户端首先解析 .torrent 文件,获得 Tracker 地址,而后链接 Tracker 服务器。Tracker 服务器回应下载者的请求,将其余下载者(包括发布者)的 IP 提供给下载者。

    下载者再链接其余下载者,根据 .torrent 文件,二者分别对方本身已经有的块,而后交换对方没有的数据。

    能够看到,下载的过程不须要其余服务器参与,并分散了单个线路上的数据流量,减轻了服务器的压力。

    下载者每获得一个块,须要算出下载块的 Hash 验证码,并与 .torrent 文件中的进行对比。若是同样,说明块正确,不同就须要从新下载这个块。这种规定是为了解决下载内容的准确性问题。

    从这个过程也能够看出,这种方式特别依赖 Tracker。Tracker 须要收集全部 peer 的信息,并将从信息提供给下载者,使下载者相互链接,传输数据。虽然下载的过程是非中心化的,可是加入这个 P2P 网络时,须要借助 Tracker 中心服务器,这个服务器用来登记有哪些用户在请求哪些资源。

    因此,这种工做方式有一个弊端,一旦 Tracker 服务器出现故障或者线路被屏蔽,BT 工具就没法正常工做了。那能不能完全去中心化呢?答案是能够的。

去中心化网络(DHT)

    DHT(Distributed Hash Table),这个网络中,每一个加入 DHT 网络的人,都要负责存储这个网络里的资源信息和其余成员的联系信息,至关于全部人一块儿构成了一个庞大的分布式存储数据库。

    而 Kedemlia 协议 就是一种著名的 DHT 协议。咱们来基于这个协议来认识下这个神奇的 DHT 网络。

    当一个客户端启动 BitTorrent 准备下载资源时,这个客户端就充当了两个角色:

  1. peer 角色:监听一个 TCP 端口,用来上传和下载文件。对外代表我这里有某个文件;
  2. DHT Node 角色:监听一个 UDP 端口,经过这个角色,代表这个节点加入了一个 DHT 网络。

    在 DHT 网络里面,每个 DHT Node 都有一个 ID。这个 ID 是一个长字符串。每一个 DHT Node 都有责任掌握一些“知识”,也就是文件索引。也就是说,每一个节点要知道哪些文件是保存哪些节点上的。注意,这里它只须要有这些“知识”就能够了,而它自己不必定就是保存这个文件的节点。

    固然,每一个 DHT Node 不会有全局的“知识”,也就是说它不知道全部的文件保存位置,只须要知道一部分。这里的一部分,就是经过哈希算法计算出来的。

Node ID 和文件哈希值

    每一个文件能够计算出一个哈希值,而 DHT Node 的 ID 是和哈希值相同长度的串

    对于文件下载,DHT 算法是这样规定的:

若是一个文件计算出一个哈希值,则和这个哈希值同样的那个 DHT Node,就有责任知道从哪里下载这个文件,即使它本身没保存这个文件。

    固然不必定总这么巧,都能找到和哈希值如出一辙的,有可能文件对应的 DHT Node 下线了,因此 DHT 算法还规定:

除了如出一辙的那个 DHT Node 应该知道文件的保存位置,ID 和这个哈希值很是接近的 N 个 DHT Node 也应该知道。

    以上图为例。文件 1 经过哈希运算,获得匹配 ID 的 DHT Node 为 Node C(固然还会有其余的,为了便于理解,我们就先关注 Node C),因此,Node C 就有责任知道文件 1 的存放地址,虽然 Node C 自己没有存放文件 1。

    同理,文件 2 经过哈希计算,获得匹配 ID 的 DHT Node 为 Node E,可是 Node D 和 E 的值很近,因此 Node D 也知道。固然,文件 2 自己不必定在 Node D 和 E 这里,可是咱们假设 E 就有一份。

    接下来,一个新节点 Node new 上线了,若是要下载文件 1,它首先要加入 DHT 网络。如何加入呢?

    在这种模式下,种子 .torrent 文件里面就再也不是 Tracker 的地址了,而是一个 list 的 Node 地址,全部这些 Node 都是已经在 DHT 网络里面的。固然,随着时间的推移,颇有可能有退出的,有下线的,这里咱们假设,不会全部的都联系不上,总有一个能联系上。

    那么,Node new 只要在种子里面找到一个 DHT Node,就加入了网络。

    Node new 不知道怎么联系上 Node C,由于种子里面的 Node 列表里面极可能没有 Node C,可是不要紧,它能够问。DHT 网络特别像一个社交网络,Node new 会去它能联系上的 Node 问,大家知道 Node C 的联系方式吗?

    在 DHT 网络中,每一个 Node 都保存了必定的联系方式,可是确定没有全部 Node 的联系方式。节点之间经过相互通讯,会交流联系方式,也会删除联系方式。这和人们的沟通方式同样,你有你的朋友圈,他有他的朋友圈,大家互相加微信,就互相认识了,可是过一段时间不联系,就可能会删除朋友关系同样。

    在社交网络中,还有个著名的六度理论,就是说社交网络中的任何两我的的直接距离不超过六度,也就是即便你想联系比尔盖茨,最多经过六我的就可以联系上。

    因此,Node New 想联系 Node C,就去万能的朋友圈去问,而且求转发,朋友再问朋友,直到找到 C。若是最后找不到 C,可是能找到离 C 很近的节点,也能够经过 C 的相邻节点下载文件 1。

    在 Node C上,告诉 Node new,要下载文件 1,能够去 B、D、F,这里咱们假设 Node new 选择了 Node B,那么新节点就和 B 进行 peer 链接,开始下载。它一旦开始下载,本身本地也有文件 1 了,因而,Node new 就告诉 C 以及 C 的相邻节点,我也有文件 1 了,能够将我加入文件 1 的拥有者列表了。

    你可能会发现,上面的过程当中漏掉了 Node new 的文件索引,可是根据哈希算法,必定会有某些文件的哈希值是和 Node new 的 ID 匹配的。在 DHT 网络中,会有节点告诉它,你既然加入了我们这个网络,也就有责任知道某些文件的下载地址了。

    好了,完成分布式下载了。可是咱们上面的过程当中遗留了两个细节性的问题。

1)DHT Node ID 以及文件哈希值是什么?
    其实,咱们能够将节点 ID 理解为一个 160bits(20字节)的字符串,文件的哈希也使用这样的字符串。

2)所谓 ID 类似,具体到什么程度算类似?
    这里就要说到两个节点距离的定义和计算了。

    在 Kademlia 网络中,两个节点的距离采用的是逻辑上的距离,假设节点 A 和 节点 B 的距离为 d,则:

d = A XOR B

    上面说过,每一个节点都有一个哈希 ID,这个 ID 由 20 个字符,160 bits 位组成。这里,咱们就用一个 5 bits ID 来举例。
    咱们假设,节点 A 的 ID 是 01010,节点 B 的 ID 是 01001,则:

距离 d = A XOR B = 01010 XOR 00011 = 01001 = 9

    因此,咱们说节点 A 和节点 B 的逻辑距离为 9。

    回到咱们上面的问题,哈希值接近,能够理解为距离接近,也即,和这个节点距离近的 N 个节点要知道文件的保存位置

    要注意的是,这个距离不是地理位置,由于在 Kademlia 网络中,位置近不算近,ID 近才算近。咱们能够将这个距离理解为社交距离,也就是在朋友圈中的距离,或者社交网络中的距离。这个和你的空间位置没有多少关系,和人的经历关系比较大。

DHT 网络节点关系的维护

    就像人同样,虽然咱们常联系的只有少数,可是朋友圈确定是远近都有。DHT 网络的朋友圈也同样,远近都有,而且按距离分层

    假设某个节点的 ID 为 01010,若是一个节点的 ID,前面全部位数都与它相同,只有最后 1 位不停,这样的节点只有 1 个,为 01011。与基础节点的异或值为 00001,也就是距离为 1。那么对于 01010 而言,这样的节点归为第一层节点,也就是k-buket 1

    相似的,若是一个节点的 ID,前面全部位数和基础节点都相同,从倒数第 2 位开始不一样,这样的节点只有 2 个,即 01000 和 01001,与基础节点的亦或值为 00010 和 00011,也就是距离为 2 和 3。这样的节点归为第二层节点,也就是k-bucket 2

    因此,咱们能够总结出如下规律:

若是一个节点的 ID,前面全部位数相同,从倒数第 i 位开始不一样,这样的节点只有 2^(i-1) 个,与基础节点的距离范围为 [2^(i-1), 2^i],对于原始节点而言,这样的节点归为 k-bucket i

    你会发现,差距越大,陌生人就越多。可是朋友圈不能把全部的都放下,因此每一层都只放 K 个,这个 K 是能够经过参数配置的。

DHT 网络中查找好友

    假设,Node A 的 ID 为 00110,要找 B(10000),异或距离为 10110,距离范围在 [2^4, 2^5),这就说明 B 的 ID 和 A 的从第 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,在 C 的通信里里,按一样的查找方式找 B,若是 C 找到了 B,就告诉 A。若是 C 也没有找到 B,就按一样的搜索方法,在本身的通信里里找到一个离 B 更近一步的 D(D、B 之间距离小于 2^3),把 D 推荐给 A,A 请求 D 进行下一步查找。

    你可能已经发现了,Kademlia 这种查询机制,是经过折半查找的方式来收缩范围,对于总的节点数目为 N 的网络,最多只须要 log2(N) 次查询,就可以找到目标。

    以下图,A 节点找 B 节点,最坏查找状况:

    图中过程以下:

  1. A 和 B 的每一位都不同,因此相差 31,A 找到的朋友 C,不巧正好在中间,和 A 的距离是 16,和 B 的距离是 15;
  2. C 去本身朋友圈找,碰巧找到了 D,距离 C 为 8,距离 B 为 7;
  3. D 去本身朋友圈找,碰巧找到了 E,距离 D 为 4,距离 B 为 3;
  4. E 在本身朋友圈找,找到了 F,距离 E 为 2,距离 B 为 1;
  5. F 在距离为 1 的地方找到了 B。
节点的沟通

    在 Kademlia 算法中,每一个节点下面 4 个指令:

  • PING:测试一个节点是否在线。至关于打个电话,看还能打通不;
  • STORE:要钱一个节点存储一份数据;
  • FIND_NODE:根据节点 ID 查找一个节点;
  • FIND_VALUE:根据 KEY 查找一个数据,实则上和 FIND_NODE 很是相似。KEY 就是文件对应的哈希值,找到保存文件的节点。
节点的更新

    整个 DHT 网络,会经过相互通讯,维护本身朋友圈好友的状态。

  • 每一个 bucket 里的节点,都按最后一次接触时间倒序排列。至关于,朋友圈里最近联系的人每每是最熟的;
  • 每次执行四个指令中的任意一个都会触发更新;
  • 当一个节点与本身接触时,检查它是否已经在 k-bucket 中。就是说是否已经在朋友圈。若是在,那么就将它移到 k-bucket 列表的最底,也就是最新的位置(刚联系过,就置顶下,方便之后多联系)。若是不在,就要考虑新的联系人要不要加到通信录里面。假设通信录已满,就 PING 一下列表最上面的节点(最旧的),若是 PING 通了,将旧节点移动到列表最底,并丢弃新节点(老朋友仍是要留点情面的)。如 PING 不一样,就删除旧节点,并将新节点加入列表(联系不上的老朋友仍是删掉吧)。

    经过上面这个机制,保证了任意节点的加入和离开都不影响总体网络。

小结

  • 下载一个文件能够经过 HTTP 或 FTP。这两种都是集中下载的方式,而 P2P 则换了一种思路,采用非中心化下载的方式;
  • P2P 有两种。一种是依赖于 Tracker 的,也就是元数据集中,文件数据分散。另外一种是基于分布式的哈希算法,元数据和文件数据所有分散。

参考:

  1. 维基百科-DHT 网络词条;
  2. 维基百科-Kademlia 词条;
  3. 刘超 - 趣谈网络协议系列课;
相关文章
相关标签/搜索