“An investment in knowledge pays the best interest.” - Benjamin
文章目录
- 分布式散列表
- (键-值)对
- 散列函数
- 逻辑上的实现
- 环形DHT
- 对等方扰动
- 对等方离开
- 对等方加入
- UDP套接字编程
- 客户端代码
- 服务器端代码
分布式散列表
分布式散列表是一种数据库。集中式数据库采用客户-服务器模式,分布式数据库则有很多个对等方,每个对等方存储一部分(键-值)对。
(键-值)对
数据在数据库中以(键-值)对的方式进行存储。
- 键:相当于索引,某个对等方在查询数据时需要使用键来查询相对应的数据。
- 值:实际存储的数据。
在P2P结构中,每个对等方存储一部分(键-值)对,通过互相查询来获取数据。
散列函数
为了确定哪个对等方存储哪些数据以便查询,为每个对等方都分配了一个标识符,每个标识符都是[0,2n-1]中的一个数,这样可以很方便的用n位二进制数来表示。
接下来,我们用散列算法的方式计算出一个键所对应的标识符,并把这个键值对加入这个标识符所代表对等方的数据中。
我们使用的散列算法是:
将键对2n取模,得到一个在[0,2n-1]的值,寻找大于这个值的最小标识符,这就是这个键所对应的标识符。
举个例子:
若标识符范围在[0,15]范围内,有标识符为1,5,12,14四个对等方。要加入的键值对的键值为36,则:
- 36对16取模,结果为4.
- 大于4的最小标识符为5,则5是负责这个键值对的对等方.
- 将这个键值对传入
逻辑上的实现
在实际操作中,我们要做到让一个对等方想新增数据时确定谁是负责这个键值对的对等方,但要让每一个对等方都能与所有对等方直接联系从成本上就是不现实的,因此我们需要取巧。
环形DHT
我们在逻辑上将所有对等方连接成一个环:
在这个环中,对等方之间的链路称为覆盖链路,这是一种逻辑上的概念,在物理上它由许多不同链路和路由器组成。
一个环中的对等方只与它的后继结点和前任结点联系。我们用上图的例子来说明这个DHT是怎么工作的:
- 3想知道谁负责11,它向4发送询问报文。
- 4知道5更接近键值,于是向5发送询问报文
- 一直重复这个过程,直到12收到查询报文,发现它就是大于键值的最小对等方,于是它向3发送一个报文,说明自己负责11
这个结构的弊端就是发送报文的数量太多,可能给网络带来压力,为了协调连接成本和发送报文的成本,我们加一些“捷径”。如下图,如果1想询问11是谁负责,它发现它可以直接联系到5,从5再向后依次询问,这样就可以跳过3和4.
对等方扰动
对等方加入和离开时会产生对等方扰动,我们思考DHT怎么处理这种扰动。处理扰动的方式有点类似于链表的思想。
对等方离开
每个对等方都知道它的第一个后继和第二个后继。
- 当它的第一个后继离开时,它将它的第二个后继设为第一个后继,并询问这个后继的后继,以得到新的第二个后继。
- 当它的第二个后继离开时,它询问它第二个后继的后继,以得到新的第二个后继。
对等方加入
- 若对等方13想加入该DHT,它能联系到1,于是它询问1以得到13的前任和后继
- 假如12和14在DHT中,那么1将报文发送到12,12知道它是13的前任,并告知14它是13的后继。
- 12给13发送报文,告知它13的后继和前任,13加入DHT,将14设为它的后继,同时12将13设为他的后继。
UDP套接字编程
编写一个网络程序时,在应用层上有两种选择:
- 我们可以使用FTP、HTTP等实现在协议标准中的协议,这时我们需要使用协议标准中规定的端口号;
- 我们也可以使用自己编写的应用层协议编写网络程序,这个时候就要避免使用协议标准中规定的端口号。
接下来,我们要做的第一件事就是确定所使用的运输层协议,即TCP/UDP。我们先看使用UDP的情况,即UDP套接字编程。
一个网络程序应用包含两部分,客户程序和服务器程序。下图表示了客户和服务器之间的交互过程:
从客户视角来看,它只是从套接字处送出去和拿到了数据;从服务器视角来看也是如此,因此我们编写程序时也要保证视角中只有应用层和套接字。运输层的操作由协议自动进行。接下来用一个例子介绍实际代码,这个代码中客户将一串字符送到服务器,服务器将这串字符的大写版本送回客户。
客户端代码
下图是应用程序客户端的代码:
- socket模块行让我们能在程序中创建套接字
- serverName行提供了目的主机名(服务器名)或服务器IP地址,如果使服务器名,则自动使用DNS服务获取IP地址
- serverPort行提供了服务器端接收该报文的套接字。这一行和上一行共同指向了一个服务器端一个具体的进程。
- clientSocket行创建了一个客户端套接字,其中包含两个参数。
- AF_INET指示网络层使用了IPv4网络
- SOCK_DGRAM指示这是一个UDP套接字
- 这一行是python语法,将这个字符串放到变量message中
- 这一行指示套接字将要发送的数据发送给特定进程。
- message是发送的数据
- 第二个参数包含了服务器的IP地址和服务器的套接字,确定了一个具体进程作为目的进程
- 这一行的recvfrom()函数开辟长度为2048的缓存接收报文,并将报文的数据放入modifiedmessage,将源地址放入serverAddress。
- 这一行将modifiedmassage,也就是报文的数据打印到屏幕上。
- 关闭套接字,关闭进程。
服务器端代码
这里只介绍与客户端代码不一样的部分:
- 第四行将serverPort,即12000这个端口号分配给一个套接字,以便能用这个套接字接收特定进程的报文
- while循环让服务器每次处理完一个请求后回到recvfrom()函数行再次接收报文。
- 倒数第二行执行将字符串的所有字母改写成大写字母的指令。