选择显示字体大小

为rmi实现类jini的发现机制(1)

如果你从事过jini开发,你会知道jini客户端是不需要知道服务的位置的;它们简单地通过发现机制来获得一个代理以访问它们需要的服务。相反,在rmi(远程方法调用)中,你必须知道你想访问的服务器的url。 在本文中,我们将向你展示怎样为rmi实现一个类jini的发现机制,这将使得一些客户端从必须知道rmi服务器url的麻烦中解脱出来。

你可能首先会想,为什么要这么麻烦;为什么不干脆用jini?我们也同意这样的逻辑,特别是对新的系统来说。不管怎样,已经有许多基于rmi的系统存在,并且在jini被java开发的主流接受以前,我们仍然要提供更优雅的rmi解决方案。事实上,我们在这儿描述的工作,是这样的需求的结果:开发一项jini服务使它同时可以作为一个独立的rmi服务器运行,但使用类jini的发现机制。

本文主要是针对没有用过jini的rmi开发者。通过深入观察jini内部的运作,我们希望你能开始了解jini的机制有多么强大。我们当然不是希望你重新实现jini,但这篇文章能帮助你理解这些机制是怎样运作的。甚至可能帮助你说服你的经理或部门头头,该考虑将jini作为一项可行的分布式系统技术。

我们不会太深入jini的发现机制,所以如果你对此不是很熟悉,我们建议你快速浏览一下bill venners的"locate services with the jini lookup service."( http://www.javaworld.com/javaworld/jw-02-2000/jw-02-jiniology.html

rmi基础和jini查找

在rmi中,客户端必须知道它所要连接的服务器的位置。rmi服务器的地址是uri的形式rmi://<主机>:<端口>/<服务名>,其中端口号是rmiregistry用来侦听请求的端口。例如:

translator service

=(translator)naming.lookup("rmi://thehost/spanishtranslator");

在jini中,客户端通过一个jini工具类来找到服务,比如servicediscoverymanager。在下面的例子中,我们创建了一个servicetemplate的实例,该实例包含一个类列表;在我们的例子中,是我们要匹配的类??translator.class:

class [] classes=new class[]{translator.class};

servicetemplate tmpl=new servicetemplate(null,classes,null);

servicediscoverymanager lmgr=new servicediscoverymanager(null,null);

serviceitem serviceitem =lmgr.lookup(tmpl,null);

translator service=serviceitem.service;

正如我们从例子中可以看到,servicediscoverymanager用lookup()方法来查找任何与servicetemplate匹配的可用的服务。你还可以在服务查找中使用任何数字或属性;在这里我们出于保持简单和精练的考虑而没有这样做。

比较两种查找机制,你会注意到在jini版本中没有指定服务的位置。值得一提的是,如果必要,你也可以指定一个查找服务的位置,但不是你想要访问的实际服务的位置。jini模型的强大之处是,我们不需要知道或关心服务位于何处。

比较了rmi和jini的发现机制之后,现在我们可以考虑怎样用类jini的风格来访问一个rmi服务器

位置中立的rmi查找

理想地,我们考虑查找translator所发现的第一个匹配的实例。

translator service

=(translator)rmidiscovery.lookup(clazz,id);

在这里clazz是rmi服务的接口,id是区分实现clazz接口的不同服务器实例的唯一字符串标识。例如,要找到一个西班牙语翻译器,我们用下面的代码:

class clazz=translator.class;

string id="spanish";

现在我们对如何使用rmi发现机制有了一个更好的主意,我们来研究一下怎样实现它。在我们尝试实现我们“简陋的”rmi发现机制以前,先来看看jini是怎样做的,再把这些原理/概念适用到rmi服务器和客户端上。

发现机制

jini的基本发现机制联合使用多播udp(用户数据报协议)(multicast udp 见文后的resources)和单播tcp/ip。简单来说,这意味着客户端发出一个多播的请求数据包,然后数据包被监听它的查找服务拾取。然后查找服务用单播连接连回客户端,并把查找服务的代理串行化成流通过此连接发送出去。此后客户端就可以和查找服务(的代理)交互以定位它需要的服务。

发现机制实际上比这要复杂得多,但我们只对其中多播udp和单播tcp/ip的关键概念感兴趣。我们并不打算实现一个等同的独立运行的rmi查找服务。相反我们将实现一个简单的多播监听器/单播分发器(multicast listener/unicast dispatcher)供rmi服务器使用,实际上我们使得每个rmi服务器作为它自己的查找服务。在客户端,我们为服务器socket写个配对物??一个多播分发器/单播监听器(multicast dispatcher/unicast listener)。

下面的表更详细地说明了rmi客户端和rmi服务器端间的交互。

rmi客户端和rmi服务器端的交互

服务器端客户端

在多播地址上开始监听

建立serversocket以监听来自服务器的单播响应。

开始向多播地址发送udp数据包

解析收到的udp数据包。如果有效,通

过单播tcp/ip连回客户端。

向客户端发送远程代理(remote stub)。

从流中读取远程对象。

关闭serversocket。停止发送udp多播数据包

开始使用服务。

发现协议

前面我们已经大致勾勒了客户端怎样发现服务器:它会指定一个接口类和一个唯一名字来确认一个服务器实例。这是因为多个实现相同接口的服务器可以同时运行。

在实现我们的rmi发现机制之前,我们必须为在参与者之间传递的消息定义一个协议。简单起见,我们用含定界符的字符串来包含rmi服务器对匹配的请求作出响应所需的全部信息。首先,我们定义一个协议头。这防止了服务器类尝试解析其他来源的数据包。消息数据包的剩余部分将包含一个单播响应端口,服务器的接口类名字,和服务器实例的唯一标识符。

下面是我们将使用的发现请求消息的格式:

,,,

现在我们看看一个消息数据包的例子,这个数据包是客户端发送来发现translator服务器的spanish实例的。rmi-discovery是协议头。5000是客户端将监听响应的端口号:

rmi-discovery,5000,translator,spanish

我们没有在请求中包括客户机的名字,因为这个信息可以从服务器收到的udp包中获得。定义了我们的消息格式,现在我们可以开始实现发现类了。

实现服务器端的类

我们的美好计划是写一个工具类,好让rmi服务器用它来实现它们自己的查找服务:

//初始化rmi服务器

remote server=new spanishtranslator();

//初始化发现监听器

rmilookup.bind(server,"spanish")

remote参数用于检查服务器是否是实现了客户端所要访问的接口,和哪一个rmi stub将最终被串行化返回给客户端。string参数用于比较服务器的名字和请求包中指定的名字。

在继续之前,我们扼要重述一下服务器端的类的职责:

1. 建立一个多播udp socket以监听请求

2. 当数据包到达时检查协议头

3. 解析消息数据包

4. 匹配唯一服务器名字参数

5. 匹配接口参数

6. 如果步骤4和5匹配,将服务器的远程代理(remote stub)通过单播tcp/ip socket串行化到客户端

建立多播udp监听器

要建立一个多播监听器,你必须使用一个确定的多播地址和端口;它的范围在224.0.0.1到239.255.255.255之间(包括224.0.0.1和239.255.255.255)。有些厂商保留了一些地址/端口的联合;例如,sun为jini保留了联合224.0.1.85:4160。(被保留地址的列表可以在http://www.iana.org/assignments/multicast-addresses找到。)不推荐在和别的厂商相同的地址/端口联合上运行,所以我们选择了和multicastsocket javadoc(见文后resources)例子相同的联合:

int port=6789;

string multicastaddress="228.5.6.7";

multicastsocket socket=new multicastsocket(port);

.netaddress address=.netaddress.getbyname(multicastaddress);

socket.joingroup(address);

byte[] buf = new byte[512];

datagrampacket packet=new datagrampacket(buf, buf.length);

socket.receive(packet);

//parse packet etc

socket.leavegroup(address);

从上面的例子可以看出,你要建立一个多播监听器并在此地址/端口联合上接收数据包有多么简单。在上面的例子中,只能处理单个数据包,所以我们必须在创建datagrampacket和socket.receive()处建立循环;否则只有一个客户端能够发现这个服务器

while(active){

byte[] buf=new byte[512];

datagrampacket packet=new datagrampacket(buf,buf.length);

socket.receive(packet);

//process packet

}

我们可以用一些策略来处理收到的数据包:

1. 每请求线程:为每个请求创建一个新的线程来处理

2. 来自线程池的线程:使用来自(可能固定的)资源线程池的预初始化的一个线程(见 "java tip 78: recycle broken objects in resource pools,http://www.javaworld.com/javaworld/javatips/jw-javatip78.html";)

3. 阻塞:在同一时间只处理一个请求,其他请求必须等待

由于我们从客户端发起一次发现,自然地,阻塞策略在这里是可行的。这是因为我们的客户端会以一定时间间隔持续发送发现消息,直到服务被定位或者请求失败达到了预定的次数。


 


关键字 本文所属关键字

相关 与本文相关文章

分类 所有文章关键字导航

源码编程相关

Java   Asp   PHP   .Net   XML   C/C++   CGI   VB   Jsp   J2ee   J2se   J2me   EJB   Servlet   Tomcat   Resin   Struts   Weblogic   Eclipse   ANT   GUI   JMS   Web servise   IDEA   Webphere   Hibernate   Spring   Jboss   Applet   Swing   Socket   Javamail   Perl   Ajax   P2P   安全   模式   框架   测试   开源   游戏

SQL数据库相关

My-SQL   Ms-SQL   Access   DB2   Oracle   Sybase   SQLserver   索引   存储过程   加密   数据库   分页   视图  

手机无线相关

3G   Wap   CDMA   GRPS   GSM   IVR   彩信   短信   无线   增值业务

网页设计制作相关

HTML   CSS   网页配色   网页特效   Javascript   VBscript   Dreamweaver   Frontpage   JS   Web   网站设计

网站建设推广相关

建站经验   网站优化   网站排名   推广   Alexa

操作系统/服务器相关

Windows XP   Windows 2000   Windows 2003   Windows Me   Windows 9.x   Linux   UNIX   注册表   操作系统   服务器   应用服务器

图形图像多媒体相关

Photoshop   Fireworks   Flash   Coreldraw   Illustrator   Freehand   Photoimpact   多媒体   图形图像

标准 网站致力的规范

Valid CSS!

无不良内容,无不良广告,无恶意代码

Valid XHTML 1.0 Transitional

creativecommons