阿对等网络(P2P)计算机网络是指其体系结构的节点经常同时用作服务器和作为客户端。P2P 系统的主要目标是消除对单独服务器来管理系统的需要。P2P 网络的配置将随着节点以不可预测的方式加入和离开网络而动态变化。节点可能在处理速度、带宽支持和存储能力等因素方面有所不同。术语对等意味着节点之间的平等程度。
P2P 网络有多种定义和解释。它们的特点是去中心化、不断变化和自我调节的架构。服务器倾向于提供服务,而客户端则请求它们。P2P 节点通常两者兼而有之。纯 P2P 网络不会将节点指定为客户端或服务器。实际上,这些网络很少见。大多数 P2P 网络依靠中央设施(例如 DNS 服务器)来提供支持。
某些网络可能是客户端/服务器架构和更纯粹的 P2P 架构之间的混合体,其中从来没有特定节点充当“主”服务器。例如,文件共享 P2P 可以使用网络节点来下载文件,而服务器可以提供额外的支持信息。
P2P 可以通过多种方式进行分类。我们将使用一些有助于理解 P2P 网络性质的常见分类类别。一个分类是基于如何索引,找到节点的处理中,执行:
- 集中式:这是当中央服务器跟踪数据在对等点之间的位置时
- 本地:这是每个对等点跟踪自己的数据的时间
- 分布式:这是当数据引用由多个对等点维护时
混合 P2P 网络使用集中式索引方案。纯 P2P 网络使用本地或分布式索引。
算法用于确定信息在系统中的位置。该系统是分散的,没有执行算法的覆盖服务器。该算法支持自组织系统,该系统在添加和删除节点时动态地重新配置自身。此外,随着网络成员的变化,这些系统将在理想情况下平衡负载和资源。
在本章中,我们将介绍:
- P2P 概念和术语
- 对 P2P 网络的 Java 支持
- 分布式哈希表的性质
- FreePastry 如何支持 P2P 应用程序
笔记
P2P 应用程序为传统的客户端/服务器架构提供了一种灵活的替代方案。
P2P功能/特点
了解 P2P 网络的一种方法是检查其特性。这些包括以下内容:
- 为系统贡献资源的节点,包括:
- 数据存储
- 计算资源
- 他们为一组服务提供支持
- 它们具有很强的可扩展性和容错性
- 它们支持资源的负载平衡
- 他们可能支持有限的匿名
P2P 系统的本质是用户可能无法访问特定节点以使用服务或资源。由于节点随机加入和离开系统,特定节点可能不可用。该算法将确定系统如何响应请求。
P2P系统的基本功能包括:
- 在网络中注册对等点
- 对等点发现——确定哪个对等点具有感兴趣的信息的过程
- 在对等体之间发送消息
并非所有对等点都执行所有这些功能。
P2P 系统的资源使用全局唯一标识符( GUID )进行标识,该标识符通常使用安全散列函数生成,我们将在 DHT 组件中对其进行检查。GUID 不适合人类阅读。这是一个随机生成的值,几乎没有发生冲突的机会。
P2P 的节点是使用路由 覆盖来组织的。它是一种将请求路由到适当节点的中间件。覆盖是指位于物理网络之上的网络,由使用 IP 地址的资源标识。我们可以设想一个由一系列基于 IP 的节点组成的网络。然而,overlay 是这些节点的子集,通常专注于单个任务。
路由覆盖将考虑一些因素,例如用户和资源之间的节点数量,以及连接的带宽,以确定哪个节点应该满足请求。通常,资源可能会在多个节点之间复制甚至拆分。路由覆盖将尝试提供到资源的最佳路径。
当节点加入和离开系统时,路由覆盖需要考虑这些变化。当一个节点加入一个系统时,它可能会被要求承担一些责任。当一个节点离开时,系统的其他部分可能需要承担一些离开节点的职责。
在本章中,我们将解释各种概念,这些概念通常作为系统的一部分嵌入。我们将简要概述不同的 P2P 应用程序,然后将讨论 Java 对该架构的支持。演示了分布式哈希表的使用,并提供了对 FreePastry 的深入检查,这将深入了解有多少 P2P 框架可以工作。
如果适用,我们将说明如何手动实现其中一些概念。虽然使用系统不需要这些实现,但它们将提供对这些基础概念的更深入的理解。
基于应用的 P2P 网络
有许多基于 P2P 网络的应用程序。它们可用于以下用途:
- 内容分发:这是文件共享(文件、音乐、视频、图像)
- 分布式计算:这是将问题分成较小的任务并以并行方式执行
- 协作:这是用户共同解决共同问题的时候
- 平台:这些是构建 P2P 应用程序的系统,例如 JXTA 和 Pastry
分布式计算利用大量小型计算机的能力来执行任务。这种方法的问题需要将它们分解成更小的单元,然后在多台机器上并发执行。然后需要组合这些较小任务的结果以产生最终结果。
P2P 网络支持许多应用程序,例如以下应用程序:
- Skype:这是一个视频会议应用程序
- Freecast:这是点对点流媒体音频节目
- BitTorrent:这是一个流行的点对点文件共享系统
- Tor : 这个程序会屏蔽用户的身份
- 海海软件:这是用于分发预先录制的电视节目
- WoW:这使用 P2P 进行游戏更新
- YaCy:这是一个搜索引擎和网络爬虫
- Octoshape : 这支持直播电视
在http://p2peducation.pbworks.com/w/page/8897427/FrontPage 中可以找到 P2P 应用程序的一个很好的概述。
对 P2P 应用程序的 Java 支持
除了前面章节中详述的低级套接字支持之外的Java 支持还包括各种框架。这些范围从众所周知的框架(如 JXTA)到小型的有限功能协议。这些框架为更专业的应用程序提供了基础。
下表列出了其中几个框架:
P2P框架 | 网址 |
点对点 | |
JXTA | |
蜂巢对蜂 | |
jnmp2p | |
FlexGP | |
杰迈 | |
P2P-MPI | |
糕点 |
这些框架使用算法在对等点之间路由消息。哈希表经常构成这些框架的基础,如下所述。
分布式哈希表
甲分布式哈希表(DHT)使用键/值对来定位网络中的资源。这个映射函数分布在对等点上,使其成为分布式的。这种架构允许 P2P 网络轻松扩展到大量节点,并处理随机加入和离开网络的对等点。DHT 是支持核心 P2P 服务的基础。许多应用程序使用 DHT,包括 BitTorrent、Freenet 和 YaCy。
下图说明了将键映射到值。键通常是一个包含资源标识的字符串,例如书名;并且该值是为表示资源而生成的数字。该编号可用于定位网络中的资源,并可对应于节点的标识符。
P2P 网络已经使用了一段时间。这些网络的演变反映在资源的映射方式上,以 Napster、Gnutella 和 Freenet 为代表:
- Napster ( https://en.wikipedia.org/wiki/Napster ) 是第一个大型 P2P内容交付系统。它使用服务器来跟踪网络中的节点。节点保存了实际数据。当客户端需要此数据时,服务器将查找当前保存数据的节点集,并将此节点的位置发送回客户端。然后客户端将联系保存数据的节点。这使得对其发起攻击变得容易,并最终通过诉讼导致其灭亡。
- Gnutella ( https://web.archive.org/web/20080525005017 , http://www.gnutella.com/ ) 不使用中央服务器,而是向网络中的每个节点广播。这导致网络充斥着消息,并且在以后的版本中对该方法进行了修改。
- Freenet ( https://freenetproject.org/ ) 使用基于启发式密钥的路由方案,专注于审查和匿名问题。但是,DHS 使用更加结构化的基于密钥的路由方法,结果如下:
- 权力下放
- 容错
- 可扩展性
- 效率
但是,DHT不支持精确匹配搜索。如果需要这种类型的搜索,则必须添加它。
DHT组件
甲密钥空间是一组160位串(键)的用于识别的元素。 密钥空间划分是在网络节点之间划分密钥空间的过程。覆盖网络连接节点。
常用的散列算法是安全散列算法( SHA-1 ) ( https://en.wikipedia.org/wiki/SHA-1 )。SHA-1由 NSA设计并生成一个 160 位的哈希值,称为消息摘要。大多数 P2P 不需要开发人员显式执行散列函数。但是,看看它是如何完成的还是很有指导意义的。以下是使用 Java 创建摘要的示例。
本MessageDigest
类的getInstance
方法接受一个字符串,指定要使用的算法,并返回一个MessageDigest
实例。它的update
方法需要一个包含要散列的键的字节数组。在这个例子中,使用了一个字符串。该digest
方法返回一个包含哈希值的字节数组。然后字节数组显示为十六进制数:
String message = "要散列的字符串";
尝试 {
MessageDigest messageDigest =
MessageDigest.getInstance("SHA-1");
messageDigest.update(message.getBytes());
byte[] 摘要 = messageDigest.digest();
StringBuffer 缓冲区 = new StringBuffer();
for(字节元素:摘要){
buffer.append(整数
.toString((元素 & 0xff) + 0x100, 16)
.substring(1));
}
System.out.println("十六进制格式:" +
buffer.toString());
} catch (NoSuchAlgorithmException ex) {
// 处理异常
}
执行此序列将产生以下输出:
十六进制格式:434d902b6098ac050e4ed79b83ad93155b161d72
要存储数据,例如文件,我们可以使用文件名来创建密钥。然后使用 put 类型函数来存储数据:
放置(键,数据)
要检索与键对应的数据,请使用 get 类型函数:
数据 = 获取(键)
覆盖中的每个节点要么包含由键表示的数据,要么是更靠近包含数据的节点的节点。路由算法确定在到达包含数据的节点的途中要访问的下一个节点。
DHT 实现
DHT有多种 Java 实现,如下表所示:
执行 | 网址 |
打开卡 | |
打开和弦 | |
点对点 | |
JDHT |
我们将使用Java 分布式哈希表( JDHT ) 来说明 DHT 的使用。
使用 JDHT
为了使用 JDHT,您将需要下表中列出dks.jar
的 JAR 文件。该文件是使用的主要 jar 文件。但是,其他两个 JAR 文件由 JDHT 使用。该dks.jar
文件的替代来源如下所列:
罐 | 地点 |
| |
| |
Apache log4j 1.2.17 |
以下示例改编自网站上的示例。首先,我们创建一个JDHT
实例。JDHT 使用端口4440
作为其默认值。有了这个实例,我们就可以使用它的put
方法向表中添加一个键/值对:
尝试 {
JDHT DHTExample = new JDHT();
DHTExample.put("Java SE API",
"http://docs.oracle.com/javase/8/docs/api/");
...
} catch (IOException ex) {
// 处理异常
}
为了让客户端连接到这个实例,我们需要获得对这个节点的引用。这是实现的,如下所示:
System.out.println(((JDHT) DHTExample).getReference());
以下代码将保持程序运行,直到用户终止它。close
然后使用该方法关闭表:
扫描仪scanner = new Scanner(System.in);
System.out.println("按回车终止应用程序:");
扫描仪。下一个();
DHTExample.close();
执行程序时,您将获得类似于以下内容的输出:
dksref://192.168.1.9:4440/0/2179157225/0/1952355557247862269
按 Enter 终止应用程序:
客户端应用程序描述如下。使用不同的端口创建了一个新的 JDHT 实例。第二个参数是对第一个应用程序的引用。您需要复制引用并将其粘贴到客户端。每次执行第一个应用程序时都会生成不同的引用:
尝试 {
JDHT myDHT = 新 JDHT(5550, "dksref://192.168.1.9:4440"
+ "/0/2179157225/0/19523555557247862269");
...
} catch (IOException | DKSTooManyRestartJoins |
DKSIdentifierAlreadyTaken | DKSRefNoResponse ex) {
// 处理异常
}
接下来,我们使用get
方法来检索与键关联的值。然后显示该值并关闭应用程序:
String value = (String) myDHT.get("Java SE API");
System.out.println(value);
myDHT.close();
输出如下:
http://docs.oracle.com/javase/8/docs/api/
这个简单的演示说明了分布式哈希表的基础知识。
使用 FreePastry
Pastry ( http://www.freepastry.org/ ) 是一个 P2P 路由覆盖系统。FreePastry ( http://www.freepastry.org/FreePastry/ ) 是Pastry 的开源实现,非常简单,我们可以用来说明 P2P 系统的许多功能。Pastry 将在O(log n)步中用n 个节点的网络路由消息。也就是说,给定一个节点网络,它最多需要n步的以2 为底数才能到达该节点。这是一种有效的路由方法。然而,虽然它可能只需要遍历三个节点才能到达一个资源,但它可能需要相当多的 IP 跃点才能到达它。
糕点使用概念的叶集路由过程。每个节点都有一个叶子集。叶集是在数值上最接近该节点的节点的 GUIDS 和 IP 地址的集合。节点在逻辑上排列成一个圆圈,如下所示。
在下图中,每个点代表一个带有标识符的节点。此处使用的地址范围从0
到FFFFFF
。实际地址范围从0
到2128
。如果表示请求的消息源自 address9341A2
并且需要发送到 address E24C12
,则基于数字地址,覆盖路由器可以通过中间节点路由消息,如箭头所示:
其他应用程序已构建在 FreePastry 之上,包括:
- SCRIBE:这是一个支持发布者/订阅者范式的群组通信和事件通知系统
- 过去:这是一个档案存储实用程序系统
- SplitStream:该程序支持内容流和分发
- Pastiche:这是备份系统
这些应用程序中的每一个都使用 API 来支持它们的使用。
FreePastry 演示
为了演示FreePastry 如何支持 P2P 应用程序,我们将根据https://trac.freepastry.org/wiki/FreePastryTutorial上的 FreePastry 教程创建一个应用程序。在本演示中,我们将创建两个节点并演示它们如何发送和接收消息。该演示使用三个类:
FreePastryExample
: 这用于引导网络FreePastryApplication
:这将执行节点的功能PastryMessage
:这是节点之间发送的消息
让我们从引导应用程序开始。
了解 FreePastryExample 类
有与FreePastry应用中的几个组件。这些包括:
- Environment:这个类代表应用程序的环境
- 绑定端口:这表示应用程序将绑定到的本地端口
- 引导端口:这是用于节点的引导端口
InetAddress
- 引导地址:这是引导节点的IP地址
在FreePastryExample
类中定义未来。它包含一个 main 方法和一个构造函数:
公共类 FreePastryExample {
...
}
我们将从main
方法开始。Environment
首先创建类的一个实例。此类保存节点的参数设置。接下来,将 NAT 搜索策略设置为从不,这使我们可以在本地 LAN 中毫无困难地使用该程序:
public static void main(String[] args) 抛出异常 {
环境环境=新环境();
环境.getParameters()
.setString("nat_search_policy", "从不");
...
}
端口和InetSocketAddress
实例被初始化。我们此时将两个端口设置为相同的数字。我们使用 IP 地址192.168.1.14
来实例化InetAddress
对象。您将需要使用您机器的地址。这是本地 LAN 地址。请勿使用127.0.0.1
,因为它将无法正常工作。在InetAddress
与沿着对象bootPort
的值被用来创建InetSocketAddress
实例。所有这些都放在一个 try 块中来处理异常:
尝试 {
int bindPort = 9001;
int bootPort = 9001;
InetAddress bootInetAddress =
InetAddress.getByName("192.168.1.14");
InetSocketAddress bootAddress =
新的 InetSocketAddress(bootInetAddress, bootPort);
System.out.println("Inet地址:" + bootInetAddress);
...
} 捕获(异常 e){
// 处理异常
}
最后一个任务是FreePastryExample
通过调用构造函数来创建类的实例:
FreePastryExample freePastryExample =
新的 FreePastryExample(bindPort, bootAddress, environment);
构造函数将创建并启动节点的应用程序。为此,我们需要创建一个PastryNode
实例并将应用程序附加到它。为了创建节点,我们将使用工厂。
每个节点都需要一个唯一的 ID。的RandomNodeIdFactory
类生成基于当前环境的ID。使用此对象与绑定端口和环境,SocketPastryNodeFactory
创建的实例。使用这个工厂,newNode
调用方法来创建我们的PastryNode
实例:
公共 FreePastryExample(int bindPort,
InetSocketAddress 引导地址,
环境环境)抛出异常{
NodeIdFactory nidFactory =
新的 RandomNodeIdFactory(环境);
PastryNodeFactory 工厂 =
新的 SocketPastryNodeFactory(
nidFactory、bindPort、环境);
PastryNode 节点 = factory.newNode();
...
}
接下来,FreePastryApplication
创建类的一个实例,并使用以下boot
方法启动节点:
FreePastryApplication 应用程序 =
新的 FreePastryApplication(节点);
节点引导(引导地址);
...
然后将显示节点的 ID,如下一个代码序列所示。由于网络中会有多个节点,我们暂停 10 秒以允许其他节点启动。我们使用 FreePastry 计时器来实现此延迟。创建一个随机节点 ID,并routeMessage
调用应用程序的消息向该节点发送消息:
System.out.println("节点" + node.getId().toString() + "已创建");
environment.getTimeSource().sleep(10000);
Id randomId = nidFactory.generateNodeId();
application.routeMessage (randomId);
在我们执行程序之前,我们需要开发应用程序类。
了解 FreePastryApplication 类
的FreePastryApplication
类实现了Application
接口,并实现该节点的功能性。构造函数创建并注册一个Endpoint
实例并初始化一条消息。Endpoint
节点使用该实例来发送消息。类和构造函数如下所示:
公共类 FreePastryApplication 实现应用程序 {
受保护的端点端点;
私人最终字符串消息;
私有最终字符串实例=“实例ID”;
公共 FreePastryApplication(节点节点){
this.endpoint = node.buildEndpoint(this, instance);
this.message = "你好!来自实例:"
+ 实例 + " 发送于:[" + getCurrentTime()
+ "]";
this.endpoint.register();
}
...
}
编译此代码时,您可能会收到“Leaking this in constructor”警告。这是由对构造函数对象的引用作为参数传递给buildEndpoint
使用this
关键字的方法引起的。这是一个潜在的错误做法,因为对象在传递时可能尚未完全构造。另一个线程可能会在对象准备好之前尝试对其进行处理。如果将其传递给执行通用初始化的包私有方法,则问题不大。在这种情况下,不太可能引起问题。
该Application
接口要求实现三个方法:
deliver
: 收到消息时调用forward
: 这用于转发消息update
:这会通知应用程序一个节点已经加入或离开了一组本地节点
我们只对deliver
这个应用程序的方法感兴趣。此外,我们将向应用程序添加getCurrentTime
和routeMessage
方法。我们将使用这些getCurrentTime
方法来显示我们的消息发送和到达的时间。该routeMessage
方法将向另一个节点发送消息。
该getCurrentTime
方法如下所述。它使用EndPoint
对象来访问节点的环境,然后是时间:
私人长 getCurrentTime() {
返回 this.endpoint
.getEnvironment()
.getTimeSource()
.currentTimeMillis();
}
该routeMessage
方法传递了目标节点的标识符。添加结束点和时间信息构造消息文本。甲PastryMessage
实例是使用终点标识符和消息文本创建。route
然后调用该方法来发送此消息:
公共无效路由消息(ID ID){
System.out.println(
"消息发送\n\t当前节点:" +
this.endpoint.getId()
+ "\n\t目的地:" + id
+ "\n\tTime: " + getCurrentTime());
Message msg = new PastryMessage(endpoint.getId(),
ID,消息);
endpoint.route(id, msg, null);
}
当节点接收到消息时,将deliver
调用该方法。该方法的实现如下。显示终点标识符、消息和到达时间。这将帮助我们了解消息的发送和接收方式:
公共无效传递(ID ID,消息消息){
System.out.println("收到消息\n\t当前节点:"
+ this.endpoint.getId() + "\n\tMessage: "
+ 消息 + "\n\tTime: " + getCurrentTime());
}
的PastryMessage
类实现的Message
接口,下,如图所示。构造函数接受目标、源和消息:
公共类 PastryMessage 实现 Message {
私人最终 ID 来自;
私人最终 ID 到;
私人最终字符串 messageBody;
public PastryMessage(Id from, Id to, String messageBody) {
this.from = 来自;
this.to = 到;
this.messageBody = messageBody;
}
...
}
该Message
接口拥有一个getPriority
需要被覆盖的方法。在这里,我们返回一个低优先级,以便它不会干扰底层 P2P 维护流量:
公共 int getPriority() {
返回 Message.LOW_PRIORITY;
}
该toString
方法被覆盖以提供更详细的消息描述:
公共字符串 toString() {
返回“来自:”+ this.from
+ " 至: " + this.to
+ " [" + this.messageBody + "]";
}
现在,我们已准备好执行该示例。执行FreePastryExample
类。初始输出将由以下输出组成。将显示缩写的节点标识符,在本例中为<0xB36864..>
。您获得的标识符将有所不同:
InetAddress:/192.168.1.14 节点 <0xB36864..> 已创建
在此之后,暂停消息被发送并随后被当前节点接收。为方便起见,此消息是FreePastryExample
使用此处重复的代码在类中创建的:
Id randomId = nidFactory.generateNodeId();
application.routeMessage(randomId);
使用随机标识符是因为我们没有将消息发送到的特定节点。发送消息后,将生成以下输出。这次运行的随机标识符是<0x83C7CD..>
:
消息已发送
当前节点:<0xB36864..>
目的地:<0x83C7CD..>
时间:1441844742906
收到消息
当前节点:<0xB36864..>
消息:发件人:<0xB36864..> 收件人:<0x83C7CD..> [你好!来自实例:实例 ID 发送地址:[1441844732905]]
时间:1441844742915
消息的发送和接收之间的时间最短。如果 P2P 网络包含更大的节点集,则会出现更严重的延迟。
在前面的输出中,节点地址被截断。我们可以使用toStringFull
如下所示的方法来获取完整地址:
System.out.println("节点" + node.getId().toStringFull()
+ "创建");
这将产生类似于以下内容的输出:
节点 B36864DE0C4F9E9C1572CBCC095D585EA943B1B4 创建
我们没有为我们的消息提供具体地址。相反,我们随机生成了地址。此应用程序演示了 FreePastry 应用程序的基本元素。附加层用于促进节点之间的通信,例如 Scribe 支持的发布者/提供者范式。
我们可以使用相同的程序启动第二个节点,但我们需要使用不同的绑定端口以避免绑定冲突。任一节点发送的消息不一定会被另一个节点接收。这是 FreePastry 生成的路由的结果。
向特定节点发送消息
要直接向节点发送消息,我们需要它的标识符。要获取远程节点的标识符,我们需要使用叶集。这个集合并不是严格的集合,因为对于小型网络,例如我们正在使用的网络,同一个节点可能会出现两次。
本LeafSet
类表示此集合,并具有get
将返回一个方法NodeHandle
实例的每个节点。如果我们有这个节点句柄,我们就可以向节点发送消息。
要演示此方法,请将以下方法添加到FreePastryApplication
类中。这与routeMessage
方法类似,但它使用节点句柄作为route
方法的参数:
public void routeMessageDirect(NodeHandle nh) {
System.out.println("消息直接发送\n\t当前节点:"
+ this.endpoint.getId() + " 目的地:" + nh
+ "\n\tTime: " + getCurrentTime());
消息 msg =
new PastryMessage(endpoint.getId(), nh.getId(),
"DIRECT-" + 消息);
endpoint.route(null, msg, nh);
}
将以下代码序列添加到FreePastryExample
构造函数的末尾。或者,注释掉之前使用该routeMessage
方法的代码。首先,我们暂停 10 秒钟以允许其他节点加入网络:
environment.getTimeSource().sleep(10000);
接下来,我们创建一个LeafSet
类的实例。该getUniqueSet
方法返回叶集,其中不包括当前节点。然后 for-each 语句将使用该routeMessageDirect
变量将消息发送到集合的节点:
LeafSet LeafSet = node.getLeafSet();
Collection<NodeHandle> collection = LeafSet.getUniqueSet();
for (NodeHandle nodeHandle : 集合) {
application.routeMessageDirect(nodeHandle);
environment.getTimeSource().sleep(1000);
}
FreePastryExample
使用 的绑定端口启动类9001
。然后,将绑定端口更改为9002
并再次启动该类。几秒钟后,您将看到类似于以下内容的输出。第一组输出对应于应用程序的第一个实例,而第二组输出对应于第二个实例。每个实例将向另一个实例发送一条消息。请注意发送和接收消息时使用的时间戳:
InetAddress: /192.168.1.9
Node <0xA5BFDA..> created
Message Sent
Current Node: <0xA5BFDA..> 目的地: [SNH: <0x2C6D18..>//192.168.1.9:9002]
时间: 144184924031
当前节点接收消息
<0xA5BFDA..>
消息:来自:<0x2C6D18..> 至:<0xA5BFDA..> [DIRECT-你好!from Instance: Instance ID Sent at: [1441849224879]]
时间: 1441849245038
InetAddress: /192.168.1.9
Node <0x2C6D18..> created
Message Received
Current Node: <0x2C6D18..>
Message: From: <0xA5BFDA..> To: <0x2C6D18..> [DIRECT-Hello there! from Instance: Instance ID Sent at: [1441849220308]]
时间: 1441849240349
消息发送直接
当前节点: <0x2C6D18..> 目的地: [SNH: <0xA5BFDA..>//192.168.1.9]
时间:904041
FreePastry 的功能远不止我们在这里能够说明的更多。但是,这些示例提供了对 P2P 应用程序开发本质的感觉。其他 P2P 框架以类似的方式工作。
概括
在本章中,我们探讨了 P2P 网络的性质和用途。这种架构将所有节点视为平等,避免使用中央服务器。节点使用覆盖网络进行映射,该网络有效地在 IP 地址空间中创建节点子网。这些节点的能力会有所不同,并且会以随机方式加入和离开网络。
我们看到了分布式哈希表如何支持识别和定位网络中的节点。路由算法使用此表通过在节点之间发送消息来满足请求。我们演示了 Java 分布式哈希表来说明 DHT 的使用。
有多种基于 Java 的开源 P2P 框架可用。我们使用 FreePastry 来演示 P2P 网络的工作原理。具体来说,我们向您展示了节点如何加入网络以及如何在节点之间发送消息。这提供了对这些框架如何运作的更好理解。
在下一章中,我们将研究 UDP 协议的性质以及它如何支持多播。