第 5 章点对点网络

本文介绍了对等网络(P2P)的基本概念,强调其去中心化、动态变化和自我调节的特性。P2P网络通过分布式哈希表(DHT)实现资源定位,支持内容分发、分布式计算和协作等多种应用。文章以Java为例,详细阐述了Java对P2P架构的支持,包括JXTA、FreePastry等框架,并演示了使用JDHT创建和操作DHT以及使用FreePastry发送和接收消息的过程。
摘要由CSDN通过智能技术生成

对等网络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框架

网址

点对点

http://tomp2p.net/

JXTA

https://jxta.kenai.com/

蜂巢对蜂

https://github.com/Hive2Hive/Hive2Hive

jnmp2p

https://code.google.com/p/jnmp2p/

FlexGP

http://flexgp.github.io/flexgp/javalibrary.html

杰迈

http://sourceforge.net/projects/jmaay/

P2P-MPI

http://grid.u-strasbg.fr/p2pmpi/

糕点

http://www.freepastry.org/

这些框架使用算法在对等点之间路由消息。哈希表经常构成这些框架的基础,如下所述。

分布式哈希表

分布式哈希表DHT)使用键/值对来定位网络中的资源。这个映射函数分布在对等点上,使其成为分布式的。这种架构允许 P2P 网络轻松扩展到大量节点,并处理随机加入和离开网络的对等点。DHT 是支持核心 P2P 服务的基础。许多应用程序使用 DHT,包括 BitTorrentFreenet YaCy

下图说明了将键映射到值。键通常是一个包含资源标识的字符串,例如书名;并且该值是为表示资源而生成的数字。该编号可用于定位网络中的资源,并可对应于节点的标识符。

P2P 网络已经使用了一段时间。这些网络的演变反映在资源的映射方式上,以 NapsterGnutella 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 实现,如下表所示:

执行

网址

打开卡

https://code.google.com/p/openkad/

打开和弦

http://open-chord.sourceforge.net/

点对点

http://tomp2p.net/

JDHT

http://dks.sics.se/jdht/

我们将使用Java 分布式哈希表JDHT ) 来说明 DHT 的使用。

使用 JDHT

为了使用 JDHT,您将需要下表中列出dks.jar JAR 文件。该文件是使用的主要 jar 文件。但是,其他两个 JAR 文件由 JDHT 使用。该dks.jar文件的替代来源如下所列:

地点

dks.jar

xercesImpl.jar

http://www.java2s.com/Code/Jar/x/DownloadxercesImpljar.htm

Apache log4j 1.2.17

https://logging.apache.org/log4j/1.2/download.html

以下示例改编自网站上的示例。首先,我们创建一个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 地址的集合。节点在逻辑上排列成一个圆圈,如下所示。

在下图中,每个点代表一个带有标识符的节点。此处使用的地址范围从0FFFFFF。实际地址范围从02128。如果表示请求的消息源自 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这个应用程序的方法感兴趣。此外,我们将向应用程序添加getCurrentTimerouteMessage方法。我们将使用这些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 协议的性质以及它如何支持多播。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值