`
huxiaojun_198213
  • 浏览: 101122 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

O'reilly<<Java RMI>> 第18章:使用定制Socket (翻译)

    博客分类:
  • RMI
阅读更多
以下翻译来自:o'reilly的RMI书籍:<<Java RMI>>

chapter18.使用定制的Socket

page 367

本书的主要主题是RMI,对于所有RMI的强大,便利功能,事实上只是在JDK的标准Socket对象上的一层实现.在本章中,将向你展示怎样使用其他的Socket来代替RMI使用的标准socket.
事实上RMI也允许你这样做,针对不同类型的服务器使用不同的socket,这是一种非常强大的特性.在本章的结束部分,你将理解怎样改变RMI使用的sockets,并知道当这样做的时候是一种恰当的设计策略.

正如我多次提到的,RMI是构建在sockets上的.这意味着:

1.在客户端与服务器端(双向)间传输数据是通过流的形式进行的.
2.客户端与服务器之间的连接的创建和维护,使用的是定义在java.net包中的socket类.
3.关于底层网络的公共设施和通信协议,RMI是不做任何假设的.(译者注:即底层的网络通信是由socket来完成的,对于RMI来说是透明的).

理解了这些重点,并知道它们背后隐含的含义,对于理解RMI的底层如何工作是至关重要的.然而,由于严格的分离,RMI作为一方面,操作系统和网络传输协议作为另一方面,自然会有下面的想法:

如果我能定义一个新的socket类,并且我的socket级的通信协议使用它来工作,那么构建在sockets上的且对底层网络不作任何假设的RMI应用程序也能够运行在我的新socket上.

的确,结果就是这样的.通过定制socket工厂实现的RMI,包含一种显著灵活的方式:让你使用你喜欢的socket来处理在网络上传送的未加工的二进制数据.

为什么要使用定制的Socket类?

默认情况下,RMI使用java.net包中的标准socket类来实现在网络上发送信息.服务端使用ServerSocket的实例来监听连接,而客户端使用Socket的实例来发起连接.
这些socket是按纯文本方式来发送数据的,数据没有经过任何加密或压缩.从积极的方面看,它们至少包含了CPU的使用(信息不以任何方式传送),并且大部分是直接使用的.
后者通常是这样的,因为压缩也是需要一些技巧的(get a little trick).flush()语法,特别是与GZIPOutputStream相关的语法,并不与发送远程方法调用的想法相一致.
从消极的方面看,它们可能需要使用更多的带宽,当信息在网络进行传送的时候,就显得越发脆弱.

这就导致了使用定制socket的两个理由:

1.如果你处在不安全的网络,并且在网络上发送的数据比较敏感,你可能想使用经过加密的socket,如SSL socket来发送数据.
2.如果你处在一个极度堵塞的网络,带宽是一个问题,你可能想在数据经过网络发送前,先对数据进行压缩.

除了这些理由外,还有一些其他的理由使用定制socket.一个最普遍的理由就是监控socket的使用并追踪通过网络发送的信息量.
这是性能分析的一部分,有可能实际导致压缩socket的使用.

18.1 定制socket工厂

让我们以假设开始,假设一个恰当的定制socket和服务端socket类已经被写好了并且准备好使用.我们以这样的假设开始有两个理由.首先,
在本书的第二章节中已经探讨过怎样创建一个定制socket.第二,com.ora.rmibook.chapter18.sockets包中包含了一个简单的定制socket类,其名称为MonitoringSocket.
MonitoringSocket有一个相关的ServerSocket的子类,其名称为MonitoringServerSocket.它们组合在一起,使得你可以监控当前已经分配了多少个sockets, RMI使用它们做了些什么.
MonitoringSocket类也可以让你追踪在任何时候,存在多少个打开的连接,并为你提供一个简单的方式,让你对应用程序的负载和资源消耗有一个感官认识.
同样地,对于调试它也是非常有用的.我们已经有了一系列的定制socket,我们还需要做两件事情:创建定制socket工厂和修改我们的服务器.

18.1.1 创建定制socket工厂

java.rmi.server包定义了两个socket工厂接口:RMIClientSocketFactory和RMIServerSocketFactory.为了使用定制的socket,必须实现这两个接口.幸运的是,它们的接口非常简单;每个接口
均只包含一个单一的方法.

在RMIClientSocketFactory中,其方法是:
public Socket createSocket(String host,int port)

在RMIServerSocketFactory中,其方法是:
public ServerSocket createServerSocket(int port)

这些方法只是Socket和ServerSocket标准构造函数的简单翻译.也就是说,你可以将其想像成RMIClientSocketFactory的实现就是在调用Socket类的构造函数,就像下面的代码片段一样:

public Socket createSocket(String host,int port){
   return new Socket(host,port);
}


其结果是完全实现socket工厂是只是比这稍稍精细一点.以下使用我们的MonitoringSocket实现客户端socket工厂的全部代码:


   public class MonitoringSocket_RMIClientSocketFactory implements RMIClientSocketFactory,Serializable{
        private int _hashCode = "MonitoringSocket_RMIClientSocketFactory".hashCode();
	
	public Socket createSocket(String host,int port){
	  try{
	    return new MonitoringSocket(host,port);
	  }catch(IOException e){
	    return null;
	  }
	}

	public boolean equals(Object object){
	
	   if(object instanceof MonitoringSocket_RMIClientSocketFactory){
	     return true;
	   }
	   return false;
	}
    
        public int hashCode(){
	    return hashCode();
	}
   }


这个类完成三件事情:它创建一个MoitoringSocket的实例,实现了Serializable接口,以及正确地覆盖了equals()和hashCode()方法.
对于为什么我们的socket工厂需要创建MonitoringSocket实例,这一点是很明确的,但其他两个任务也同样重要.

18.1.1.1 实现序列化

RMIClientSocketFactory的实现类必须实现序列化接口,因为服务器使用的socket类型完全是服务端属性.为了使这个客户端能连接上服务器,它必须使用正确的socket类型.
为了使得在客户端的RMI运行时环境能够辨认出使用的socket类型,它必须能反序列化一个恰当的socket工厂实例.当客户端在反序列化stub(stub拥有一个实现了RMIClientSocketFactory的实例引用)的时候,
这种动作是自动发生的.当stub绑定到一个命名服务的时候,序列化创建了一个正确socket工厂的拷贝.当客户端从命名服务中获取stub时,同时也会获得socket工厂的拷贝,因此它能连接到服务器上.


18.1.1.2 实现equals()和hashCode()方法
RMI运行时环境通过两种方式来使用socket工厂实例:创建sockets和索引已存在的sockets.换句话说,当stub想要发送消息时,RMI运行时将会按照下面的动作进行:

1.运行时从stub中获取RMIClientSocketFactory的stub实例
2.运行时将RMIClientSocketFactory的stub实例作为hashtable的键来查找那些已经打开但当前没有被使用的sockets.
3.如果获取sockets失败,RMI运行时将RMIClientSocketFactory的stub实例来创建一个socket
4.当远程方法调用完成了的时候,客户端运行时将此socket放入步骤中的hashtable中.

你可能认为有一种更好的对象来作为步骤2中hashtable的键。比如,stub自身就是一个很好的选择。然而,如果这样做的话,stub将无法工作。
为什么?不同服务器的两个stub,甚至使用同一个socket工厂的stub,它们会返回不同的hashcode,且当equals()方法被调用的时候,也会返回false.
使用stub来索引sockets意味着在一个特定的服务器中调用的时候,sockets能被重用,但对于不同的服务器则不能。

你不能使用stub对象也不能使用RMIClientSocketFactory的stub实例。使用它们中的任何一个作为hashtable的key都忽略这种可能性:即客户端工厂是有状态的,在不同的时期,将返回不同的sockets.

考虑一下以下完全合法的RMIClientSocketFactory的实现代码:

package com.ora.rmibook.chapter18.sockets;


import java.rmi.server.*;
import java.io.*;
import java.net.*;


public class PropertyBasedMonitoringSocket_RMIClientSocketFactory
    implements RMIClientSocketFactory, Serializable {
    private static final String USE_MONITORING_SOCKETS_PROPERTY = "com.ora.rmibook.useMonitoringSockets";
    private static final String TRUE = "true";

    private int _hashCode = "PropertyBasedMonitoringSocket_RMIClientSocketFactory".hashCode();
    private boolean _isMonitoringOn;

    public PropertyBasedMonitoringSocket_RMIClientSocketFactory() {
        String monitoringProperty = System.getProperty(USE_MONITORING_SOCKETS_PROPERTY);

        if ((null != monitoringProperty) && (monitoringProperty.equalsIgnoreCase(TRUE))) {
            _isMonitoringOn = true;
            _hashCode++;
        } else {
            _isMonitoringOn = false;
        }
        return;
    }

    public Socket createSocket(String host, int port) {
        try {
            if (_isMonitoringOn) {
                return new MonitoringSocket(host, port);
            } else {
                return new Socket(host, port);
            }
        } catch (IOException e) {
        }
        return null;
    }

    public boolean equals(Object object) {
        if (object instanceof PropertyBasedMonitoringSocket_RMIClientSocketFactory) {
            return true;
        }
        return false;
    }

    public int hashCode() {
        return _hashCode;
    }
}



这个类做了一些不同寻常的事情。在程序的初始化部分,它检查系统属性以了解是否要使用监控socket.不同的服务器可能运行在不同的JVM中,并对这个值有不同的设置(可通过-D argument来改变其值)。
比如,下面的两行调用代码就会产不同类型的socket:

java -Dcom.ora.rmibook.useMonitoringSocket=false...
java -Dcom.ora.rmibook.useMonitoringSocket=true...


然而,如果我们使用stub的类对象或socket factory的类对象作为hashtable中可用sockets的键,服务器之间的区别将会消失,并且错误的socket类型有可能被使用。

几乎所有的这些讨论都可以用在RMIServerSocketFactory的实现上。生产服务端socket的工厂由于不会向客户端传递数据,所以它可以不需要序列化。

另一方面,服务器RMI运行时内部使用equals()和hashCode()方法。因为大部分服务器使用同样类型的sockets,使得看起来多个服务器在共享sockets.
为了解决这个问题,RMi维护已打开的sockets的映射。当一个服务器需要一个socket的时候,RMI使用socket factory作为key来确定是否有可用的sockets.为使用RMI能够有效地查找,我们必须覆盖equals()和hashCode()方法。

package com.ora.rmibook.chapter18.sockets;


import java.rmi.server.*;
import java.net.*;
import java.io.*;


public class MonitoringSocket_RMIServerSocketFactory
    implements RMIServerSocketFactory {
    private int _hashCode = "MonitoringSocket_RMIServerSocketFactory".hashCode();
    public ServerSocket createServerSocket(int port) {
        try {
            return new MonitoringServerSocket(port);
        } catch (IOException e) {
        }
        return null;
    }

    public boolean equals(Object object) {
        if (object instanceof MonitoringSocket_RMIServerSocketFactory) {
            return true;
        }
        return false;
    }

    public int hashCode() {
        return _hashCode;
    }
}



page 260

C:\Users\qbna>rmic -keep -classpath D:\myproject\JavaRMI\bin com.pa.rmi.test.upload.FileUploadHandlerImpl -d D:\myproject\JavaRMI\bin

147

废话就不多说了,现在开始进入正题.何为分布式,简单地说,就是一个JVM中的方法可以调用另一个JVM中的方法.在Java平台中,实现分布式应用程序通常有三种选择:
  1.RMI,Java远程方法调用,支持Java的分布式对象之间的方法调用.
  2.CORBA,通常对象请求代理架构,支持任何编程语言编写的对象之间的方法调用.使用
Internet Inter-ORB协议(IIOP)
  3.webservice,独立于编程语言,使用基于xml的通信格式.用于传输对象的格式则是简单对象访问协议,即SOAP.



18.2将定制的socket集成到应用程序中
现在我们已经有了一个定制的socket factory,下一步是将其集成到我们的应用程序中。通常有两中方法可以实现集成:
1.可以修改服务器类
2.可以设置RMI的默认socket factory.
第一种选择,通过修改个别服务器,可以使我们能够细粒度地控制每个服务器能够使用不同的定制socket类。设置RMI的默认socket factory是以一种粗粒度的方式来控制的。改变RMI的默认socket factory则意味着任何服务器将使用新的默认的socket factory.

18.2.1修改原始服务器

UnicastRemoteObject有如下三个构造函数:

protected UnicastRemoteObject()

protected UnicastRemoteObject(int port)

protected UnicastRemoteObject(int port,RMIClientSocketFactory csf,RMIServerSocketFactory ssf)


直到现在为止,我们只在服务器程序中使用了前两种构造函数。事实上,我们主要在使用第一个构造函数,就像下面的代码片段:
public class Account_Impl extends UnicastRemoteObject implements Account {
    private Money _balance;
    public Account_Impl(Money startingBalance)
        throws RemoteException {
        _balance = startingBalance;
    }
    //...
}


有一个完美的理由这样做,无参的构造函数赋予了RMI运行时重用已经存在的server socket能力,带有一个参数的构造函数强迫
RMI运行时创建监听指定端口的server socket.然而,为了使用定制socket factory,我们必须使用第三个构造函数。因此,主要的代码改变就是使用第三个构造函数:

public class Account_Impl extends UnicastRemoteObject implements Account {
    private Money _balance;
    public Account_Impl(Money startingBalance)
        throws RemoteException {
	super(0,new PropertyBaseMonitoringSocket_RMIClientSocketFactory(),new PropertyBasedMonitoringSocket_RMIServerSocketFactory());
        _balance = startingBalance;
    }
    //...
}


第一个参数为0,表示RMI可以自由选择一个端口作为server socket的端口,这样RMI可以重用已经存在的server socket.

UnicastRemoteObject的三个静态导出方法:

static RemoteStub exportObject(Remote obj)

static Remote exportObject(Remote obj,int port)

static Remote exportObject(Remote obj,int port,RMIClientSocketFactory csf,RMIServerSocketFactory ssf)

原始导出远程对象的代码为:

 private static void launchServer(NameBalancePair serverDescription) {
        try {
            Account_Impl2 newAccount = new Account_Impl2(serverDescription.balance);
            RemoteStub stub = UnicastRemoteObject.exportObject(newAccount);

            Naming.rebind(serverDescription.name, stub);
            System.out.println("Account " + serverDescription.name + " successfully launched.");
        } catch (Exception e) {
        }
    }


修改后的导出代码为:

 private static void launchServer(NameBalancePair serverDescription) {
        try {
            Account_Impl2 newAccount = new Account_Impl2(serverDescription.balance);
            RemoteStub stub = UnicastRemoteObject.exportObject(newAccount,0,new PropertyBasedMonitoringSocket_RMIClientSocketFactory(),new PropertyBasedMonitoringSocket_RMIServerSocketFactory());

            Naming.rebind(serverDescription.name, stub);
            System.out.println("Account " + serverDescription.name + " successfully launched.");
        } catch (Exception e) {
        }
    }


18.2.2修改活动的服务器
激活一个服务器的最大改变是从UnicastRemoteObject切换到Activatable.继承至UnicastRemoteObject服务器现在要继承Activatable,原来使用UnicastRemoteObject的exportObject()方法来导出远程对象,现在要使用Activatable的exportObject()方法来导出远程对象。

public class Account_Impl extends Activatable implements Account {
    private Money _balance;
    public Account_Impl(ActivationID id, MarshalledObject data)
        throws RemoteException {
        super (id, 0, new PropertyBasedMonitoringSocket_RMIClientSocketFactory(),
            new PropertyBasedMonitoringSocket_RMIServerSocketFactory());
        try {
            _balance = (Money) data.get();
        } catch (Exception e) {

            /* Both ClassNotFoundException and IOException can
             be thrown.*/
        }
    }
    //....
}


18.2.3 修改默认的服务器

位于java.rmi.server包中的RMISocketFactory是RMI中的一个抽象类,它同时实现了RMIClientSocketFactory和RMIServerSocketFactory接口,RMI使用它作为默认的socket factory.

也就是说,除非服务器指定了使用一个不同的socket factory,否则RMI将使用RMISocketFactory的实例来作为它的客户端工厂和服务端工厂。

RMISocketFactory有5个静态方法来定制RMI运行时行为,它们是:

public static RMISocketFactory getDefaultSocketFactory()

public static RMISocketFactory getSocketFactory()

public static void setSocketFactory(RMISocketFactory fac)

public static RMIFailureHandler getFailureHandler()

public static void setFailureHandler(RMIFailureHandler fh)

以下一个RMISocketFactory的具体实现类:

package com.ora.rmibook.chapter18.sockets;


import java.rmi.server.*;
import java.io.*;
import java.net.*;


public class PropertyBasedMonitoringSocket_RMISocketFactory extends RMISocketFactory {
    private static final String USE_MONITORING_SOCKETS_PROPERTY = "com.ora.rmibook.useMonitoringSockets";
    private static final String TRUE = "true";

    private int _hashCode = "PropertyBasedMonitoringSocket_RMISocketFactory".hashCode();
    private boolean _isMonitoringOn;

    public PropertyBasedMonitoringSocket_RMISocketFactory() {
        String monitoringProperty = System.getProperty(USE_MONITORING_SOCKETS_PROPERTY);

        if ((null != monitoringProperty) && (monitoringProperty.equalsIgnoreCase(TRUE))) {
            _isMonitoringOn = true;
            _hashCode++;
        } else {
            _isMonitoringOn = false;
        }
        return;
    }

    public Socket createSocket(String host, int port) {
        try {
            if (_isMonitoringOn) {
                return new MonitoringSocket(host, port);
            } else {
                return new Socket(host, port);
            }
        } catch (IOException e) {
        }
        return null;
    }

    public ServerSocket createServerSocket(int port) {
        try {
            if (_isMonitoringOn) {
                return new MonitoringServerSocket(port);
            } else {
                return new ServerSocket(port);
            }
        } catch (IOException e) {
        }
        return null;
    }

    public boolean equals(Object object) {
        if (object instanceof PropertyBasedMonitoringSocket_RMIClientSocketFactory) {
            return true;
        }
        return false;
    }

    public int hashCode() {
        return _hashCode;
    }
}


18.2.3.1失败处理

在RMISocketFactory中定义了两个静态方法:getFailureHandler()和setFailureHandler().RMIFailureHandler是当RMI需要ServerSocket时,但由于某种原因无法创建时调用的一个接口。

RMIFailureHandler定义了一个方法:

public boolean failure(Exception ex)

此方法的返回值如果为true,则告诉RMI再次创建ServerSocket的实例(这也是默认行为),反之,告诉RMI试图创建server socket的操作已经完全失败。

18.2.4交互式参数(Interaction with Parameters)

在16章中,我们讨论许多的传输层参数。这些全局参数用于配置sockets的RMI行为。这些传输层参数是:

sun.rmi.transport.connectionTimeout


sun.rmi.transport.tcp.readTimeout

sun.rmi.transport.proxy.connectionTimeout

RMISocketFactory的默认连接策略
1.使用JRMP(RMI默认协议)来直接socket连接
2.如果上一步失败,将远程方法调用包装在HTTP POST命令里,并将其发送到给远程服务器端口。
3.如果上一步失败,则将远程方法调用包装在HTTP POST命令里,将其通过代理发送给远程服务器端口。
4.如果上一步失败,则将远程方法调用包装在HTTP POST命令里,将其通过代理发送给远程服务器80端口。
这种策略既有用但也有安全风险。

答案是RMI使用定制的socket factories来创建sockets, 然后调用sockets上的方法配置它们。RMI使用下面的步骤获取server socket:

1.使用与server相关的RMIServerSocketFactory来检查server socket是否已经分配过,如果是,则使用已存在的server socket.

2.如果没有,则调用与服务器相关的RMIServerSocketFactory的createServerSocket()方法,在socket被创建后,再配置socket.

分享到:
评论

相关推荐

    Java网络编程(第三版)中文版.part07.rar

    第十八章 远程方法调用 617 何为远程方法调用? 617 实现 623 在运行时加载类 631 java.rmi包 634 java.rmi.registry包 640 java.rmi.server包 642 第十九章 JavaMail API 648 何为JavaMail API? 649 发送...

    Java网络编程(第三版)中文版.part09.rar

    第十八章 远程方法调用 617 何为远程方法调用? 617 实现 623 在运行时加载类 631 java.rmi包 634 java.rmi.registry包 640 java.rmi.server包 642 第十九章 JavaMail API 648 何为JavaMail API? 649 发送...

    Java网络编程(第三版)中文版.part11.rar

    第十八章 远程方法调用 617 何为远程方法调用? 617 实现 623 在运行时加载类 631 java.rmi包 634 java.rmi.registry包 640 java.rmi.server包 642 第十九章 JavaMail API 648 何为JavaMail API? 649 发送...

    Java网络编程(第三版)中文版.part06.rar

    第十八章 远程方法调用 617 何为远程方法调用? 617 实现 623 在运行时加载类 631 java.rmi包 634 java.rmi.registry包 640 java.rmi.server包 642 第十九章 JavaMail API 648 何为JavaMail API? 649 发送...

    Java网络编程(第三版)高清中文版.part01.rar

    第十八章 远程方法调用 617 何为远程方法调用? 617 实现 623 在运行时加载类 631 java.rmi包 634 java.rmi.registry包 640 java.rmi.server包 642 第十九章 JavaMail API 648 何为JavaMail API? 649 发送...

    Java网络编程(第三版)中文版.part01.rar

    第十八章 远程方法调用 617 何为远程方法调用? 617 实现 623 在运行时加载类 631 java.rmi包 634 java.rmi.registry包 640 java.rmi.server包 642 第十九章 JavaMail API 648 何为JavaMail API? 649 发送...

    Java网络编程(第三版)中文版.part03.rar

    第十八章 远程方法调用 617 何为远程方法调用? 617 实现 623 在运行时加载类 631 java.rmi包 634 java.rmi.registry包 640 java.rmi.server包 642 第十九章 JavaMail API 648 何为JavaMail API? 649 发送...

    Java网络编程(第三版)中文版.part02.rar

    第十八章 远程方法调用 617 何为远程方法调用? 617 实现 623 在运行时加载类 631 java.rmi包 634 java.rmi.registry包 640 java.rmi.server包 642 第十九章 JavaMail API 648 何为JavaMail API? 649 发送...

    Java网络编程(第三版)中文版.part04.rar

    第十八章 远程方法调用 617 何为远程方法调用? 617 实现 623 在运行时加载类 631 java.rmi包 634 java.rmi.registry包 640 java.rmi.server包 642 第十九章 JavaMail API 648 何为JavaMail API? 649 发送...

    Java网络编程(第三版)中文版.part10.rar

    第十八章 远程方法调用 617 何为远程方法调用? 617 实现 623 在运行时加载类 631 java.rmi包 634 java.rmi.registry包 640 java.rmi.server包 642 第十九章 JavaMail API 648 何为JavaMail API? 649 发送...

    Java网络编程(第三版)中文版.part13.rar

    第十八章 远程方法调用 617 何为远程方法调用? 617 实现 623 在运行时加载类 631 java.rmi包 634 java.rmi.registry包 640 java.rmi.server包 642 第十九章 JavaMail API 648 何为JavaMail API? 649 发送...

    Java网络编程(第三版)中文版.part05.rar

    第十八章 远程方法调用 617 何为远程方法调用? 617 实现 623 在运行时加载类 631 java.rmi包 634 java.rmi.registry包 640 java.rmi.server包 642 第十九章 JavaMail API 648 何为JavaMail API? 649 发送...

    Java网络编程(第三版)中文版.part12.rar

    第十八章 远程方法调用 617 何为远程方法调用? 617 实现 623 在运行时加载类 631 java.rmi包 634 java.rmi.registry包 640 java.rmi.server包 642 第十九章 JavaMail API 648 何为JavaMail API? 649 发送...

    Java网络编程(第三版)中文版.part08.rar

    第十八章 远程方法调用 617 何为远程方法调用? 617 实现 623 在运行时加载类 631 java.rmi包 634 java.rmi.registry包 640 java.rmi.server包 642 第十九章 JavaMail API 648 何为JavaMail API? 649 发送...

Global site tag (gtag.js) - Google Analytics