科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件为RMI实现类Jini的发现机制(2)

为RMI实现类Jini的发现机制(2)

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

本文主要是针对没有用过Jini的RMI开发者,文章引导你为你的RMI开发实现一个类Jini的发现机制,

作者:Philip Bishop and Nigel Warren 来源:javaresearch  2007年9月3日

关键字:

  • 评论
  • 分享微博
  • 分享邮件
检查协议头



收到一个数据包后,我们接着检查所包含的消息是否我们想要的。要完成这项工作,我们用startsWith()方法将byte []转换成String。虽然我们将协议头RMI-DISCOVERY硬编码到了下面的例子中,不过在实际的源码中它是作为一个常量来存取的。

String msg=new String(packet.getData()).trim();

boolean validPacket=msg.startsWith("RMI-DISCOVERY");

解析消息



假设我们得到一个有效的数据包,我们可以从中解析出消息来。由于消息是用定界符划分的,我们可以用StringTokenizer来分开它:

private String [] parseMsg(String msg,String delim){

//符合格式的请求

//

StringTokenizer tok=new StringTokenizer(msg,delim);

tok.nextToken(); //protocol header

String [] strArray=new String[3];

strArray[0]=tok.nextToken();//回复端口

strArray[1]=tok.nextToken();//接口名

strArray[2]=tok.nextToken();//服务名

return strArray;

}

我们将消息数据包转换成了参数以后,就可以对照服务器的名字检查接口名和唯一服务器名。

匹配接口和服务器名



要用参数来匹配服务器的唯一名字,你只需简单地比较两个String对象。如果你下载了全部源码(见文后Resources),你可以看到RMILookup类有两个参数:一个指明了它的唯一名字,另一个是Remote对象。

你可以对存储服务器实现的所有接口的整个数组比较接口名字:

//在启动时完成

Class c=_service.getClass();

_serviceInterface=c.getInterfaces();

//进行匹配的部分代码

//interfaceName是请求的一部分

boolean match=false;

for(int i=0;!match && i<_serviceInterface.length;i++){

match=_serviceInterface[i].getName().equals(interfaceName);

}

返回到发现者的单播连接

如果唯一服务器名和接口类都匹配,我们就尝试连回客户端,并把服务器的stub串行化:

//repAddress已从进入的DatagramPacket中获得

//repPort已从消息数据包中解析出来

//_service 是RMI服务器的Remote ref(stub)

Socket sock=new Socket(repAddress,repPort);

ObjectOutputStream oos=new ObjectOutputStream(sock.getOutputStream());

oos.writeObject(new MarshalledObject(_service));

oos.flush();

oos.close();

值得注意的一点的是上面的例子中使用MarshalledObject。如果我们简单地串行化Remote对象到流,在客户端会发生一个ClassNotFoundException,除非客户端已经访问了服务器的stub(在大多数情况下这是糟糕的)。客户端会得到ClassNotFoundException是因为,不同于通过RMI传递对象,codebase会被附加到流中,在这儿我们是透过一个socket使用串行化,不会包含codebase。

MarshalledObject在Java 2中加入,提供了一个方便的途径来传递串行化的对象及其codebase。在内部,MarshalledObject将对象串行化到字节数组,这意味着当MarshalledObject被解串行化时,内部的对象不会解串行化。这对Jini服务,如查找服务,是极其有用的,因为它们不再被迫去下载注册的代理所代表的类。

要访问内部的对象,你要在客户端调用MarshalledObject的get()方法。

实现客户端的类



前面我们说明了RMI客户端怎样通过指定接口类名字和服务器的唯一名字来发现RMI服务器,如下所示:

Class clazz=Translator.class;

String id="Spanish";

Translator service

=(Translator)RMIDiscovery.lookup(clazz,id);

在考虑怎样实现我们的RMIDiscovery类以前,让我们先扼要重述一下它的职责:

1. 监听来自服务器的RMILookup的单播响应

2. 向多播地址发送UDP包

3. 从流中读取远程对象

4. 停止发送多播数据包

5. 停止在单播socket上监听

6. 使用服务器

建立单播TCP/IP监听器

要建立一个单播TCP/IP socket,我们必须选择一个监听的端口。不过,我们不能简单地将一个固定的端口号定义成常量,因为其他进程可能在使用这个端口。我们因此需要指定一个使用的端口号的范围:

private ServerSocket startListener(int port,int range){

ServerSocket listener=null;

for(int i=port;listener==null && i<(port+range+1);i++){try{

listener =new ServerSocket(i);

}catch(IOException ex){

//端口(可能)已经被使用

//处理违例

}}

return listener;

}

上面的startListener()方法尝试在指定范围内的一个端口上创建ServerSocket。此方法的调用者可以检查返回值是否为null(null意味着ServerSocket不能被创建)并获得使用的端口。另一个选择是在ServerSocket不能被创建时抛出一个违例:

ServerSocket listener=startListener(START_PORT,RANGE);

if(listener!=null){

int port=listener.getLocalPort();

//format message to include port number格式化消息以包含端口号

//start the multicast message dispatcher 启动多播消息分发器

Socket sock=listener.accept();

//read remote stub from stream 从流中读取remote stub

}

当我们成功地建立了单播监听器,我们就可以格式化消息数据包并启动多播消息分发器。

建立多播UDP分发器

如同多播监听器,我们必须使用一个已知的多播地址/端口联合。我们可以通过System属性或者通过一个常量来获取这项数据:

int port=6789;

String multicastAddress="228.5.6.7";

MulticastSocket socket=new MulticastSocket(port);

InetAddress address=InetAddress.getByName(multicastAddress);

socket.joinGroup(address);

//outMsg是用定界符划分的请求

byte [] buf=outMsg.getBytes();

//循环n次或一直到单播监听器收到响应为止

DatagramPacket packet=new DatagramPacket(buf,

buf.length,address,multicastPort);

socket.send(packet);

//结束循环

socket.leaveGroup(address);

socket.close();

一步一步察看上面的代码,你可以看到当我们配置好MulticastSocket之后,outMsg字符串被转换成一个字节数组以便从socket发送出去。注释说明了然后我们将发送消息预先指定的次数或者直到单播监听器收到响应为止。为了使例子简明,我们从中省略了与单播监听器的线程间的协调工作;你可以下载整个源码(见文后Resources)来看看这是怎样完成的。

读取服务器的stub

前面我们已经看到了怎样建立一个单播ServerSocket。现在我们要看看读取服务器的stub的代码。方法ServerSocket.accept()是阻塞的,所以它不会返回一个Socket对象,除非进入的连接已经完成:

Socket sock=listener.accept();

ObjectInputStream ois=new ObjectInputStream(sock.getInputStream());

MarshalledObject mo=(MarshalledObject)ois.readObject();

sock.close();

//server是一个成员域

server=(Remote)mo.get();

当我们获得了服务器的一个引用,我们接着可以唤醒调用RMIDiscovery.lookup()而被阻塞的线程,它将给客户端返回一个Remote对象。

采用Jini

在这篇文章中,我们向你展示了怎样为普通的RMI客户端和服务器应用一项类似Jini的发现概念的技术。虽然我们建议在新的项目中使用Jini,你还是能够用类似发现的机制来增强现有的RMI系统从而获得好处。

前面说明的RMI发现机制有一些Jini能够克服的局限。例如,多播UDP有受限制的范围,通常是一个子网内。这意味着使用我们的多播机制的客户端不能发现在多播范围以外运行的RMI服务器。然而,Jini有联合查找服务的概念可以“加入”不同的子网以使跨越WAN(广域网)的发现过程对客户端透明。

我们鼓励读者下载全部源码(见文后Resources)来进行试验。用一个RMI服务器为许多在多播范围以外运行的服务器的远程引用做委托或代理,并在其中使用RMILookup工具类,会是一个有趣的试验。

根本来说,Jini是一个更好更优雅的解决方案,所以我们强烈建议还没有体验过Jini的读者尽快体验一下。

最后,要指出的是,一般来说多播UDP在没有连接到集线器的独立的机器上不能工作。使用loopback适配器是可选的方案;不过,我们在基于Windows的机器上使用这种方法时遇到了错误。

查看本文来源

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章