要完成有用的工作PP 应用程序中的对等点必须能够彼此发现对方并与对方交互在上一篇文章中Todd 描述了几种不同的机制对等点可以使用这些机制彼此发现他还解释了每种机制的优缺点本月他提供了一种基于 IP 多播的发现的实现 在软件实体能够参与具有 PP 应用程序特征的直接的对等交互之前该实体必须发现将要与之交互的适当的对等点所有可行的 PP 体系结构都提供一种针对发现问题的解决方案上一次我们研究了实现发现的几种不同的方法本月我将描述其中一种机制的实现让我们通过回顾来开始今天的讨论 再访发现 对等点发现使 PP 应用程序中的对等点能够彼此定位以便相互之间可以交互实现对等点发现服务有多种方法最简单的机制是显式点到点配置这种机制通过要求每个对等点知道所有它可能与之交互的其它对等点并与它们相连来进行工作点到点配置的主要优点是简单它的主要缺点是缺乏灵活性并且缺少扩展到对等点的大型网络的能力 发现的另一个公共模型是使用中央目录作为中介该模型在许多传统的非 PP 分布式类型的应用程序中间很流行其优点是很好理解对等点向中央目录注册自己的存在并使用中央目录定位其它对等点这种模型的主要优点是易于管理和扩展的能力但是其集中化设计会导致单点故障因此它对自然力或网上沖浪人数增加所带来的危害缺乏抵御能力 许多流行的 PP 应用程序使用网络模型而不是中央目录在网络模型中单个对等点只知道局域网络上的对等点身份每个对等点都作为那些与之相连的对等点的目录对等点通过向相邻对等点传播目录查询并返回相关的响应来进行合作这种模型的主要优点是没有集中化它的主要缺点是由于传播查询耗费了大量的网络和处理能力 上面三种机制有无数种变体不讨论这些变体了让我们继续前进并研究另一种发现机制 IP 多播发现 就每个对等点维护自己的目录这点而言多播模型类似于网络模型但是对等点不通过合作来实现大规模网络查询另外对等点利用网络本身提供的特性(IP 多播)来定位和标识其它对等点 IP 多播是无连接和不可靠的(不象 TCP/IP 是面向连接和可靠的)虽然它使用 IP 数据报但是不象单播 IP 数据报那样是从一台主机发送到另一台主机多播 IP 数据报可以同时发往多台主机 对等点定期使用 IP 多播来宣布自己的存在宣布包含了它们的主机名和一个用于正常通信的端口对此消息感兴趣的对等点检测这个消息后抽取出主机名和端口号并使用该消息建立一个通信通道 回顾已经足够了让我们开始研究代码吧 简单的客户机与服务器 我们将从一个简单的示例开始该示例演示了两个进程如何使用 IP 多播进行通信为了简化演示我将分别从客户机和服务器进程这两个方面来介绍示例PP 应用程序通常会实现这两个进程将它们划分为客户机或服务器并不容易 在本例中服务器进程进行循环并等待数据报包的到来每接收到一个包服务器就会向控制台打印一条简短的诊断消息客户机角色要简单得多 — 它多播单个数据报包并退出 清单 和 说明了这两部分是如何组合在一起的代码中的注释说明了正在发生的事情 清单 简单服务器 public class Server { public static void main(String [] arstring) { try { // Create a multicast datagram socket for receiving IP // multicast packets Join the multicast group at // port MulticastSocket multicastSocket = new MulticastSocket(); InetAddress inetAddress = InetAddressgetByName(); multicastSocketjoinGroup(inetAddress); // Loop forever and receive messages from clients Print // the received messages while (true) { byte [] arb = new byte []; DatagramPacket datagramPacket = new DatagramPacket(arb arblength); multicastSocketreceive(datagramPacket); Systemoutprintln(new String(arb)); } } catch (Exception exception) { exceptionprintStackTrace(); } } } 清单 简单客户机 public class Client { public static void main(String [] arstring) { try { // Create a datagram package and send it to the multicast // group at port byte [] arb = new byte [] {hello}; InetAddress inetAddress = InetAddressgetByName(); DatagramPacket datagramPacket = new DatagramPacket(arb arblength inetAddress ); MulticastSocket multicastSocket = new MulticastSocket(); multicastSocketsend(datagramPacket); } catch (Exception exception) { exceptionprintStackTrace(); } } } 包中的两个类使它运行DatagramPacket 类保存了 IP 数据报包中包含的数据MulticastSocket 类创建一个调整到一个特定多播组的多播套接字 发现组件 尽管上述示例是一个很好的 IP 多播的演示但它没有说明实现基于 IP 多播的对等点发现需要什么要使它有用我们需要一个功能不仅限于发送和接收包的软件组件理想情况下这个组件将了解它所接收的包的源对等点并适当地丢弃一些信息这些信息是关于那些它认为已经消失死亡或以其它方式离去的对等点的 在这个新设计中对等点是一个多播组的成员请牢记发送到多播组的消息会透明地路由到该组的所有成员 设计包括两个核心类和三个接口(我使用术语接口似乎不太严谨 — 在技术上是一个接口和两个抽象类)Member 类的实例是一个多播组的成员这个类管理所有的通信细节MemberManager 类的一个实例负责了解参与多播组的其它成员 对等点通过向多播组发送一个消息来向属于多播组中的对等点宣布自己的存在每个消息包含关于发送消息的对等点的信息 — 通常是主机名和用于正常(与发现无关)通信的端口Member 类和 MemberManager 类对这些消息的内容几乎一无所知对该信息的访问权属于使用这两个类的应用程序 有三个接口跨越了消息传递/发现层和使用它的应用程序层之间的边界它们是 Reference 抽象类Message 接口和 MessageFactory 抽象类应用程序必须提供这三个接口的实现 Reference 抽象类定义了对多播组成员的引用MemberManager 类管理一个引用集应用程序将实现这个类的一个具体版本它将包含应用程序所需要的任何引用逻辑该类定义了两个方法名称是 equalsInternal() 和 hashCodeInternal()并且重新定义了 equals() 和 hashCode() 方法来调用这些方法它通过这样做来强制实现者为这两个关键功能提供实现 — MemberManager 依赖于它们 Message 接口定义了通过网络代码交换的消息数据的应用程序视图应用程序将该消息看作是相对于应用程序运行范围的高级概念 — 类似于主机名和端口的概念网络代码希望发送一个由字节组成的包Message 接口的实现定义了如何将这些高级信息与字节相互转换引用是信息的一个关键部分所有消息都必须包含因此该接口要求实现提供用于读和写 reference 的方法 问题的最后部分是 MessageFactory 抽象类这个类定义了生成新的 Message 实例的机制深藏在 Member 类内的网络代码使用一个工厂来创建从多播数据报中抽取出的数据的 Message 实例每个 MessageFactory 实例拥有一个随机生成的身份它使用这个身份来从接收的消息中滤出要发送的消息 总之这五个类和接口(MemberMemberManagerReferenceMessage 和 MessageFactory)构成了一个用于进行对等点发现的简单框架当然还有可以改进的空间可以很容易地添加一种基于事件的机制用于向感兴趣的侦听器通知成员的出现或消失一种用于过滤所接收消息的灵活机制将很有用但其实现却比较困难我将这些建议留作读者的作业 Member 类 上面描述的框架的完整源代码太长了这里就不详细展示了所以让我们只研究 Member 类的部分代码因为其中包含了操作的大多数内容更准确地说操作发生在两个内部类中MemberClient 类和 MemberServer 类 请再次考虑第一个示例它由一个发送 IP 多播数据报的客户机和一个接收数据报的服务器组成在本例中(清单 和 )两个单独的应用程序执行这两项功能PP 应用程序中的对等点的行为方式既象客户机又象服务器所以我们的 PP 应用程序应该同时包含两者才是适合的 清单 MemberClient 类 private class MemberClient extends Thread { public void run() { try { while (true) { try { Message message = m_messagefactorycreateSendMessage(); Reference reference = messagecreateReferen |