学习自极客时间《趣谈网络协议》 作者:刘超
5. 流媒体协议
5.1 什么是流媒体?
流媒体(Streaming media)是指将一连串的媒体数据压缩后,经过网络分段发送数据,在网络上即时传输影音以供观赏的一种技术与过程,此技术使得数据包得以像流水一样发送;如果不使用此技术,就必须在使用前下载整个媒体文件。
流媒体文件一般定义在bit层次结构,因此流数据包并不一定必须按照字节对齐,虽然通常的媒体文件都是按照这种字节对齐的方式打包的。流媒体的三大操作平台是微软公司、RealNetworks、苹果公司提供的。
5.2 有哪些多媒体常用协议
- TTP/TCP/UDP
- 涵盖传统的私有协议,以及基于HTTP的流式传输协议。
- RTP/RTSP/RTCP/SRTP & SRTCP/RTMP
- HLS
- MMS (Microsoft Media Server Protocol)
- DASH
- RTMP 是一个古老的协议。RMTP 最初由 Macromedia 开发,后被 Adobe 收购,至今仍被使用。
由于 RTMP 播放视频需要依赖 Flash 插件。而 Flash 插件多年来一直受安全问题困扰,正在被迅速淘汰。因此,目前 RTMP 主要用于提取 stream。也就是,当设置解编码器将视频发送到托管平台时,视频将使用 RTMP 协议发送到 CDN,随后使用另一种协议(通常是HLS)传递给播放器。何时使用 RTMP
RTMP 协议延迟非常低,但由于需要 Flash 插件,不建议使用该协议,但流提取是例外。在流提取方便,RTMP 非常强大,且几乎得到了普遍支持。
- 相对于常见的流媒体直播协议,例如RTMP协议、RTSP协议、MMS协议等,HLS直播最大的不同在于,直播客户端获取到的,并不是一个完整的数据流。HLS协议在服务器端将直播数据流存储为连续的、很短时长的媒体文件(MPEG-TS格式),而客户端则不断的下载并播放这些小文件,因为服务器端总是会将最新的直播数据生成新的小文件,这样客户端只要不停的按顺序播放从服务器获取到的文件,就实现了直播。由此可见,基本上可以认为,HLS是以点播的技术方式来实现直播。由于数据通过HTTP协议传输,所以完全不用考虑防火墙或者代理的问题,而且分段文件的时长很短,客户端可以很快的选择和切换码率,以适应不同带宽条件下的播放。不过HLS的这种技术特点,决定了它的延迟一般总是会高于普通的流媒体直播协议。但 Apple 在 WWDC 2019 发布了新的解决方案,可以将延迟从8秒降低到1至2秒。具体可以查看Introducing Low-Latency HLS。
5.3 什么是视频 ?
视频其实就是快速播放一连串连续的图片。每一张图片,我们称为一帧。只要每秒钟帧的数据足够多,也即播放得足够快。比如每秒 30 帧,以人的眼睛的敏感程度,是看不出这是一张张独立的图片的,这就是我们常说的帧率(FPS)。
由于图片是由像素组成的,所以视频的数据量非常大,需要压缩。编码就是一种压缩的过程。
5.4 视频和图片的压缩过程的特点
- 空间冗余:图像的相邻像素之间有较强的相关性,一张图片相邻像素往往是渐变的,不是突变的,没必要每个像素都完整地保存,可以隔几个保存一个,中间的用算法计算出来。 
- 时间冗余:视频序列的相邻图像之间内容相似。一个视频中连续出现的图片也不是突变的,可以根据已有的图片进行预测和推断。 
- 时间冗余:视频序列的相邻图像之间内容相似。一个视频中连续出现的图片也不是突变的,可以根据已有的图片进行预测和推断。 
- 编码冗余:不同像素值出现的概率不同,概率高的用的字节少,概率低的用的字节多,类似霍夫曼编码(Huffman Coding)的思路。 
编码的过程如下:

5.5 视频编码的两大流派
- 流派一:ITU(International Telecommunications Union)的 VCEG(Video Coding Experts Group),这个称为国际电联下的 VCEG。既然是电信,可想而知,他们最初做视频编码,主要侧重传输。名词系列二,就是这个组织制定的标准。 
- 流派二:ISO(International Standards Organization)的 MPEG(Moving Picture Experts Group),这个是 ISO 旗下的 MPEG,本来是做视频存储的。例如,编码后保存在 VCD 和 DVD 中。当然后来也慢慢侧重视频传输了。名词系列三,就是这个组织制定的标准。 
后来,ITU-T(国际电信联盟电信标准化部门,ITU Telecommunication Standardization Sector)与 MPEG 联合制定了 H.264/MPEG-4 AVC。
5.6 看直播时到底发生了什么
- 网络协议将编码好的视频流,从主播端推送到服务器,在服务器上有个运行了同样协议的服务端来接收这些网络包,从而得到里面的视频流,这个过程称为接流。 
- 服务端接到视频流之后,可以对视频流进行一定的处理,例如转码,也即从一个编码格式,转成另一种格式。因为观众使用的客户端千差万别,要保证他们都能看到直播。 
- 流处理完毕之后,就可以等待观众的客户端来请求这些视频流。观众的客户端请求的过程称为拉流。 
 如果有非常多的观众,同时看一个视频直播,那都从一个服务器上拉流,压力太大了,因而需要一个视频的分发网络,将视频预先加载到就近的边缘节点,这样大部分观众看的视频,是从边缘节点拉取的,就能降低服务器的压力。
- 当观众的客户端将视频流拉下来之后,就需要进行解码,也即通过上述过程的逆过程,将一串串看不懂的二进制,再转变成一帧帧生动的图片,在客户端播放出来。 

5.6.1 编码的实现
(1) 帧的分类
虽然我们说视频是一张张图片的序列,但是如果每张图片都完整,就太大了,因而会将视频序列分成三种帧。
- I 帧,也称关键帧。里面是完整的图片,只需要本帧数据,就可以完成解码。
- P 帧,前向预测编码帧。P 帧表示的是这一帧跟之前的一个关键帧(或 P 帧)的差别,解码时需要用之前缓存的画面,叠加上和本帧定义的差别,生成最终画面。
- B 帧,双向预测内插编码帧。B 帧记录的是本帧与前后帧的差别。要解码 B 帧,不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面的数据与本帧数据的叠加,取得最终的画面。
I 帧最完整,B 帧压缩率最高,而压缩后帧的序列,应该是在 IBBP 的间隔出现的。这就是通过时序进行编码。
在一帧中,分成多个片,每个片中分成多个宏块,每个宏块分成多个子块,这样将一张大的图分解成一个个小块,可以方便进行空间上的编码。

(2) 网络提取层单元(NALU)
尽管时空非常立体的组成了一个序列,但是总归还是要压缩成一个二进制流。这个流是有结构的,是一个个的网络提取层单元(NALU,Network Abstraction Layer Unit)。变成这种格式就是为了传输,因为网络上的传输,默认的是一个个的包,因而这里也就分成了一个个的单元。

- 每一个 NALU 首先是一个起始标识符,用于标识 NALU 之间的间隔;然后是 NALU 的头,里面主要配置了 NALU 的类型;最终 Payload 里面是 NALU 承载的数据。
- 在 NALU 头里面,主要的内容是类型 NAL Type。- 0x07 表示 SPS,是序列参数集, 包括一个图像序列的所有信息,如图像尺寸、视频格式等。
- 0x08 表示 PPS,是图像参数集,包括一个图像的所有分片的所有相关信息,包括图像类型、序列号等。
 
在传输视频流之前,必须要传输这两类参数,不然无法解码。为了保证容错性,每一个 I 帧前面,都会传一遍这两个参数集合
如果 NALU Header 里面的表示类型是 SPS 或者 PPS,则 Payload 中就是真正的参数集的内容。如果类型是帧,则 Payload 中才是正的视频数据,当然也是一帧一帧存放的,前面说了,一帧的内容还是挺多的,因而每一个 NALU 里面保存的是一片。
一个视频,可以拆分成一系列的帧,每一帧拆分成一系列的片,每一片都放在一个 NALU 里面,NALU 之间都是通过特殊的起始标识符分隔,在每一个 I 帧的第一片前面,要插入单独保存 SPS 和 PPS 的 NALU,最终形成一个长长的 NALU 序列。
5.6.2 推流
显然上面所说的二进制流是不能直接在网上传输的,还需要将其打包成网络包进行发送,用到了 RTMP 协议。
RTMP 是基于 TCP 的,因而肯定需要双方建立一个 TCP 的连接。在有 TCP 的连接的基础上,还需要建立一个 RTMP 的连接,也即在程序里面,你需要调用 RTMP 类库的 Connect 函数,显示创建一个连接。
为什么RTMP要建立单独连接?
主要就是协商两个事情:
- 一个是版本号,如果客户端、服务器的版本号不一致,则不能工作。
- 另一个就是时间戳,视频播放中,时间是很重要的,后面的数据流互通的时候,经常要带上时间戳的差值,因而一开始双方就要知道对方的时间戳。
RTMP建立连接的过程如图

- 首先,客户端发送 C0 表示自己的版本号,不必等对方的回复,然后发送 C1 表示自己的时间戳。
- 服务器只有在收到 C0 的时候,才能返回 S0,表明自己的版本号,如果版本不匹配,可以断开连接。
- 服务器发送完 S0 后,也不用等什么,就直接发送自己的时间戳 S1。客户端收到 S1 的时候,发一个知道了对方时间戳的 ACK C2。同理服务器收到 C1 的时候,发一个知道了对方时间戳的 ACK S2。于是,握手完成。
握手之后,双方需要互相传递一些控制信息,例如 Chunk 块的大小、窗口大小等。
传输数据
真正传输数据的时候,还是需要创建一个流 Stream,然后通过这个 Stream 来推流 publish。推流的过程,就是将 NALU 放在 Message 里面发送,这个也称为 RTMP Packet 包。
Message 的格式就像这样。

- 发送的时候,去掉 NALU 的起始标识符。因为这部分对于 RTMP 协议来讲没有用。接下来,将 SPS 和 PPS 参数集封装成一个 RTMP 包发送,然后发送一个个片的 NALU。 
- RTMP 在收发数据的时候并不是以 Message 为单位的,而是把 Message 拆分成 Chunk 发送,而且必须在一个 Chunk 发送完成之后,才能开始发送下一个 Chunk。每个 Chunk 中都带有 Message ID,表示属于哪个 Message,接收端也会按照这个 ID 将 Chunk 组装成 Message。可以设置 Chunk 块大小,将大的消息变为小的块再发送,可以在低带宽的情况下,减少网络拥塞。 - 整个推流过程如下: 

这个时候,大量观看直播的观众就可以通过 RTMP 协议从流媒体服务器上拉取,如果用户量巨大并且都在同一个服务器拉取,服务器的压力就很大,而且用户分布在全国甚至全球,如果都去统一的一个地方下载,也会时延比较长,需要有分发网络。
分发网络分为中心和边缘两层。边缘层服务器部署在全国各地及横跨各大运营商里,和用户距离很近。中心层是流媒体服务集群,负责内容的转发。智能负载均衡系统,根据用户的地理位置信息,就近选择边缘服务器,为用户提供推 / 拉流服务。中心层也负责转码服务,例如,把 RTMP 协议的码流转换为 HLS 码流。

5.6.3 拉流
用户先读到的是 H.264 的解码参数,例如 SPS 和 PPS,然后对收到的 NALU 组成的一个个帧,进行解码,交给播发器播放,一个绚丽多彩的视频画面就出来了。

5.7 总结
- 视频名词比较多,编码两大流派达成了一致,都是通过时间、空间的各种算法来压缩数据;
- 压缩好的数据,为了传输组成一系列 NALU,按照帧和片依次排列;
- 排列好的 NALU,在网络传输的时候,要按照 RTMP 包的格式进行包装,RTMP 的包会拆分成 Chunk 进行传输;
- 推送到流媒体集群的视频流经过转码和分发,可以被客户端通过 RTMP 协议拉取,然后组合为 NALU,解码成视频格式进行播放。
参考文章
五种常见流媒体协议 : https://www.jianshu.com/p/d71ceef679de
流媒体协议介绍 : https://blog.csdn.net/u013008311/article/details/80405241
6. P2P协议
6.1 以前下载数据的方式
方式一, 最简单的方式就是通过 HTTP 进行下载。但是通过浏览器下载的时候,只要文件稍微大点,下载的速度就奇慢无比。
方式二, 还有种下载文件的方式,就是通过 FTP,也即文件传输协议。FTP 采用两个 TCP 连接来传输一个文件。
- 控制连接:服务器以被动的方式,打开众所周知用于 FTP 的端口 21,客户端则主动发起连接。该连接将命令从客户端传给服务器,并传回服务器的应答。常用的命令有:list——获取文件目录;reter——取一个文件;store——存一个文件。
- 数据连接:每当一个文件在客户端与服务器之间传输时,就创建一个数据连接。
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,然后在两个端口之间进行数据传输。
上面说了 HTTP 下载和 FTP 下载,这两种方式都有一个大缺点-难以解决单一服务器的带宽压力。因为它们使用的都是传统 C/S 结构,这种结构会随着客户端的增多,下载速度越来越慢。这在当今互联网世界显然是不合理的,我们期望能实现“下载人数越多,下载速度不变甚至更快”的愿望。
6.2 P2P
P2P 就是 peer-to-peer。资源开始并不集中地存储在某些设备上,而是分散地存储在多台设备上,这些设备我们称为 peer。
在下载一个文件时,只要得到那些已经存在了文件的 peer 地址,并和这些 peer 建立点对点的连接,就可以就近下载文件,而不需要到中心服务器上。一旦下载了文件,你的设备也就称为这个网络的一个 peer,你旁边的那些机器也可能会选择从你这里下载文件。即你自己也加入了这个 P2P 的网络,自己从别人那里下载,同时也提供给其他人下载
通过这种方式解决上面 C/S 结构单一服务器带宽压力问题。如果使用过 P2P2 软件,例如 BitTorrent,你就会看到自己网络不仅有下载流量,还有上传流量,也就是说你加入了这个 P2P 网络,自己可以从这个网络里下载,同时别人也可以从你这里下载。这样就实现了,下载人数越多,下载速度越快的愿望。
6.3 种子(.torrent)文件
当你想下载一个文件的时候,怎么知道哪些 peer 有这个文件呢?这就用到种子,也即.torrent 文件。.torrent 文件由两部分组成,分别是:announce(tracker URL)和文件信息。
文件信息里面有这些内容:
- info 区:这里指定的是该种子有几个文件、文件有多长、目录结构,以及目录和文件的名字。
- Name 字段:指定顶层目录名字。
- 每个段的大小:BitTorrent(简称 BT)协议把一个文件分成很多个小段,然后分段下载。
- 段哈希值:将整个种子中,每个段的 SHA-1 哈希值拼在一起。
下载过程如下:
- 下载时,BT 客户端首先解析.torrent 文件,得到 tracker 地址,然后连接 tracker 服务器。tracker 服务器回应下载者的请求,将其他下载者(包括发布者)的 IP 提供给下载者。
- 下载者再连接其他下载者,根据.torrent 文件,两者分别对方告知自己已经有的块,然后交换对方没有的数据。此时不需要其他服务器参与,并分散了单个线路上的数据流量,因此减轻了服务器的负担。
- 下载者每得到一个块,需要算出下载块的 Hash 验证码,并与.torrent 文件中的对比。如果一样,则说明块正确,不一样则需要重新下载这个块。这种规定是为了解决下载内容的准确性问题。
这种方式特别依赖 tracker。tracker 需要收集下载者信息的服务器,并将此信息提供给其他下载者,使下载者们相互连接起来,传输数据。虽然下载的过程是非中心化的,但是加入这个 P2P 网络的时候,都需要借助 tracker 中心服务器,这个服务器是用来登记有哪些用户在请求哪些资源。
所以,这种工作方式有一个弊端,一旦 tracker 服务器出现故障或者线路遭到屏蔽,BT 工具就无法正常工作了。
6.4 去中心化网络(DHT)
那能不能彻底非中心化呢?
是,后来就有了一种叫作DHT(Distributed Hash Table),这个网络中,每个加入 DHT 网络的人,都要负责存储这个网络里的资源信息和其他成员的联系信息,相当于所有人一起构成了一个庞大的分布式存储数据库。
而 *Kedemlia 协议 *就是一种著名的 DHT 协议。
任何一个 BitTorrent 启动之后,它都有两个角色。
- 一个是 peer,监听一个 TCP 端口,用来上传和下载文件,这个角色表明,我这里有某个文件。
- 另一个角色 DHT node,监听一个 UDP 的端口,通过这个角色,这个节点加入了一个 DHT 的网络。
在 DHT 网络里面,每一个 DHT Node 都有一个 ID。这个 ID 是一个长字符串。每个 DHT Node 都有责任掌握一些“知识”,也就是文件索引。也就是说,每个节点要知道哪些文件是保存哪些节点上的。注意,这里它只需要有这些“知识”就可以了,而它本身不一定就是保存这个文件的节点。
Node ID 和文件哈希值
每个 DHT Node 不会有全局的“知识”,也就是说它不知道所有的文件保存位置,只需要知道一部分。这里的一部分,就是通过哈希算法计算出来的。每个文件可以计算出一个哈希值,而 DHT node 的 ID 是和哈希值相同长度的串。
DHT 算法是这样规定的:如果一个文件计算出一个哈希值,则和这个哈希值一样的那个 DHT node,就有责任知道从哪里下载这个文件,即便它自己没保存这个文件。
当然不一定这么巧,总能找到和哈希值一模一样的,有可能一模一样的 DHT node 也下线了,所以 DHT 算法还规定:除了一模一样的那个 DHT node 应该知道,ID 和这个哈希值非常接近的 N 个 DHT node 也应该知道。
DHT网络如下:

- 文件 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 new 不知道怎么联系上 node C,因为种子里面的 node 列表里面很可能没有 node C,但是它可以问,DHT 网络特别像一个社交网络,Node new 会去它能联系上的 Node 问,你们知道 Node C 的联系方式吗?
- 在 DHT 网络中,每个 node 都保存了一定的联系方式,但是肯定没有 node 的所有联系方式。DHT 网络中,节点之间通过互相通信,也会交流联系方式,也会删除联系方式。和人们的方式一样,你有你的朋友圈,你的朋友有它的朋友圈,你们互相加微信,就互相认识了,过一段时间不联系,就删除朋友关系。
在社交网络中,还有个著名的六度理论,就是说社交网络中的任何两个人的直接距离不超过六度,也就是即使你想联系特朗普,最多通过六个人就能够联系上。
- 所以,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 的拥有者列表了。 
DHT Node ID 以及文件哈希值是什么?
其实,我们可以将节点 ID 理解为一个 160bits(20字节)的字符串,文件的哈希也使用这样的字符串。
所谓 ID 相似,具体到什么程度算相似?
在 Kademlia 网络中,两个节点的距离是通过异或(XOR)计算的。
每个节点都有一个哈希 ID,这个 ID 由 20 个字符,160 bits 位组成。这里,我们就用一个 5 bits ID 来举例。我们假设,节点 A 的 ID 是 01010,节点 B 的 ID 是 01001,则:
    距离 d = A XOR B = 01010 XOR 00011 = 01001 = 9
哈希值接近,可以理解为距离接近,也即,和这个节点距离近的 N 个节点要知道文件的保存位置。
要注意的是,这个距离不是地理位置,因为在 Kademlia 网络中,位置近不算近,ID 近才算近。我们可以将这个距离理解为社交距离,也就是在朋友圈中的距离,或者社交网络中的距离。这个和你的空间位置没有多少关系,和人的经历关系比较大。
6.5 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。
6.6 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 节点,最坏查找情况:

图中过程如下:
- A 和 B 的每一位都不一样,所以相差 31,A 找到的朋友 C,不巧正好在中间,和 A 的距离是 16,和 B 的距离是 15;
- C 去自己朋友圈找,碰巧找到了 D,距离 C 为 8,距离 B 为 7;
- D 去自己朋友圈找,碰巧找到了 E,距离 D 为 4,距离 B 为 3;
- E 在自己朋友圈找,找到了 F,距离 E 为 2,距离 B 为 1;
- 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 不同,就删除旧节点,并将新节点加入列表(联系不上的老朋友还是删掉吧)。
通过上面这个机制,保证了任意节点的加入和离开都不影响整体网络。
6.7 总结
- 下载一个文件可以通过 HTTP 或 FTP。这两种都是集中下载的方式,而 P2P 则换了一种思路,采用非中心化下载的方式;
- **P2P 有两种。一种是依赖于 Tracker 的,也就是元数据集中,文件数据分散。另一种是基于分布式的哈希算法,元数据和文件数据全部分散。
**