当前位置: 首页>>代码示例 >>用法及示例精选 >>正文


Ruby DRb模块用法及代码示例


本文简要介绍ruby语言中 DRb模块 的用法。

概述

dRuby 是 Ruby 的分布式对象系统。它是用纯 Ruby 编写的,并使用自己的协议。除了 Ruby 运行时提供的服务(例如 TCP 套接字)之外,不需要 add-in 服务。它不依赖于或与其他分布式对象系统(如 CORBA、RMI 或 .NET)互操作。

dRuby 允许在一个 Ruby 进程中对位于另一个 Ruby 进程中的 Ruby 对象调用方法,即使在另一台机器上也是如此。对对象的引用可以在进程之间传递。 Method 参数和返回值以编组格式转储和加载。所有这些对远程方法的调用者和调用它的对象都是透明的。

远程进程中的对象在本地由 DRb::DRbObject 实例表示。这充当了远程对象的一种代理。调用此 DRbObject 实例的方法将转发到其远程对象。这是在运行时动态安排的。远程对象没有静态声明的接口,例如 CORBA 的 IDL。

对进程进行的 dRuby 调用由该进程中的 DRb::DRbServer 实例处理。这重构了方法调用,在指定的本地对象上调用它,并将值返回给远程调用者。任何对象都可以通过 dRuby 接收调用。不需要实现特殊的接口,或者 mixin 特殊的函数。在一般情况下,对象也不需要向 DRbServer 显式注册自己以接收 dRuby 调用。

一个进程希望对另一个进程进行 dRuby 调用,必须以某种方式获得对远程进程中对象的初始引用,而不是作为远程方法调用的返回值,因为最初没有远程对象引用,它可以调用方法上。这是通过 URI 附加到服务器来完成的。每个 DRbServer 将自己绑定到 URI ,例如“druby://example.com:8787”。一个 DRbServer 可以附加一个对象,作为服务器的前端对象。 DRbObject 可以从服务器的 URI 显式创建。这个 DRbObject 的远程对象将是服务器的前端对象。然后这个前端对象可以返回对 DRbServer 进程中其他 Ruby 对象的引用。

通过 dRuby 进行的 Method 调用的行为与在进程中进行的普通 Ruby 方法调用基本相同。支持带有块的 Method 调用,以及引发异常。除了方法的标准错误之外,dRuby 调用还可能引发 dRuby-specific 错误之一,所有这些错误都是 DRb::DRbError 的子类。

任何类型的对象都可以作为参数传递给 dRuby 调用或作为其返回值返回。默认情况下,此类对象在本地转储或编组,然后在远程端加载或解组。因此,远程端接收到本地对象的副本,而不是对它的分布式引用;在此副本上调用的方法完全在远程进程中执行,而不是传递给本地原始进程。这具有类似于pass-by-value 的语义。

然而,如果一个对象不能被编组,一个对它的 dRuby 引用将被传递或返回。这将在远程端显示为 DRbObject 实例。在这个远程代理上调用的所有方法都被转发到本地对象,如 DRbObjects 的讨论中所述。这具有类似于普通 Ruby pass-by-reference 的语义。

表明我们希望将其他可编组对象作为 DRbObject 引用传递或返回而不是编组并作为副本发送的最简单方法是包含 DRb::DRbUndumped mixin 模块。

dRuby 支持使用块调用远程方法。由于块(或者更确切地说是表示它们的 Proc 对象)不可编组,因此块在本地而不是远程上下文中执行。产生给块的每个值都从远程对象传递到本地块,然后每个块调用返回的值被传递回远程执行上下文进行收集,最后收集的值作为本地上下文返回到本地上下文方法调用的返回值。

使用示例

有关更多 dRuby 示例,请参阅完整 dRuby 发行版中的 samples 目录。

客户端/服务器模式下的 dRuby

这说明了如何设置一个简单的client-server drb 系统。在不同的终端运行服务端和客户端代码,先启动服务端代码。

服务器代码

require 'drb/drb'

# The URI for the server to connect to
URI="druby://localhost:8787"

class TimeServer

  def get_current_time
    return Time.now
  end

end

# The object that handles requests on the server
FRONT_OBJECT=TimeServer.new

DRb.start_service(URI, FRONT_OBJECT)
# Wait for the drb server thread to finish before exiting.
DRb.thread.join

客户端代码

require 'drb/drb'

# The URI to connect to
SERVER_URI="druby://localhost:8787"

# Start a local DRbServer to handle callbacks.
#
# Not necessary for this small example, but will be required
# as soon as we pass a non-marshallable object as an argument
# to a dRuby call.
#
# Note: this must be called at least once per process to take any effect.
# This is particularly important if your application forks.
DRb.start_service

timeserver = DRbObject.new_with_uri(SERVER_URI)
puts timeserver.get_current_time

dRuby 下的远程对象

此示例说明从 dRuby 调用返回对对象的引用。 Logger 实例存在于服务器进程中。对它们的引用将返回给客户端进程,可以在其中调用它们的方法。这些方法在服务器进程中执行。

服务器代码

require 'drb/drb'

URI="druby://localhost:8787"

class Logger

    # Make dRuby send Logger instances as dRuby references,
    # not copies.
    include DRb::DRbUndumped

    def initialize(n, fname)
        @name = n
        @filename = fname
    end

    def log(message)
        File.open(@filename, "a") do |f|
            f.puts("#{Time.now}: #{@name}: #{message}")
        end
    end

end

# We have a central object for creating and retrieving loggers.
# This retains a local reference to all loggers created.  This
# is so an existing logger can be looked up by name, but also
# to prevent loggers from being garbage collected.  A dRuby
# reference to an object is not sufficient to prevent it being
# garbage collected!
class LoggerFactory

    def initialize(bdir)
        @basedir = bdir
        @loggers = {}
    end

    def get_logger(name)
        if !@loggers.has_key? name
            # make the filename safe, then declare it to be so
            fname = name.gsub(/[.\/\\\:]/, "_")
            @loggers[name] = Logger.new(name, @basedir + "/" + fname)
        end
        return @loggers[name]
    end

end

FRONT_OBJECT=LoggerFactory.new("/tmp/dlog")

DRb.start_service(URI, FRONT_OBJECT)
DRb.thread.join

客户端代码

require 'drb/drb'

SERVER_URI="druby://localhost:8787"

DRb.start_service

log_service=DRbObject.new_with_uri(SERVER_URI)

["loga", "logb", "logc"].each do |logname|

    logger=log_service.get_logger(logname)

    logger.log("Hello, world!")
    logger.log("Goodbye, world!")
    logger.log("=== EOT ===")

end

安全

与所有网络服务一样,使用 dRuby 时需要考虑安全性。通过允许外部访问 Ruby 对象,您不仅允许外部客户端调用您为该对象定义的方法,而且默认情况下可以在您的服务器上执行任意 Ruby 代码。考虑以下:

# !!! UNSAFE CODE !!!
ro = DRbObject::new_with_uri("druby://your.server.com:8989")
class << ro
  undef :instance_eval  # force call to be passed to remote object
end
ro.instance_eval("`rm -rf *`")

instance_eval 和朋友带来的危险是,DRbServer 仅应在客户端受信任时使用。

可以为 DRbServer 配置访问控制列表,以选择性地允许或拒绝来自指定 IP 地址的访问。主要的 druby 发行版为此提供了 ACL 类。一般来说,这种机制应该只与一个好的防火墙一起使用,而不是作为一个好的防火墙的替代品。

dRuby 内部结构

dRuby 使用三个主要组件实现:远程方法调用 marshaller/unmarshaller;传输协议;和一个ID-to-object 映射器。后两者可以直接替换,第一个间接替换,以提供不同的行为和函数。

远程方法调用的编组和解组由 DRb::DRbMessage 实例执行。这使用 Marshal 模块在通过传输层发送方法调用之前转储方法调用,然后在另一端重构它。通常不需要更换这个组件,也没有提供直接的方法来这样做。然而,作为传输层实现的一部分,可以实现替代的编组方案。

传输层负责打开客户端和服务器网络连接并在它们之间转发 dRuby 请求。通常,它在内部使用 DRb::DRbMessage 来管理编组和解组。传输层由 DRb::DRbProtocol 管理。 DRbProtocol 可以同时安装多个协议;它们之间的选择由 dRuby URI 的方案确定。默认传输协议由方案‘druby:’选择,并由 DRb::DRbTCPSocket 实现。这使用普通的 TCP/IP 套接字进行通信。另一种使用 UNIX 域套接字的协议由文件 drb/unix.rb 中的 DRb::DRbUNIXSocket 实现,并由方案“drbunix:”选择。可以在主要 dRuby 发行版随附的示例中找到基于 HTTP 的示例实现。

ID-to-object 映射组件将 dRuby 对象 ID 映射到它们引用的对象,反之亦然。要使用的实现可以指定为 DRb::DRbServer 配置的一部分。默认实现由 DRb::DRbIdConv 提供。它使用对象的 ObjectSpace id 作为其 dRuby id。这意味着对该对象的 dRuby 引用仅在对象进程的生命周期和该进程内对象的生命周期内才有意义。修改后的实现由文件 drb/timeridconv.rb 中的 DRb::TimerIdConv 提供。此实现在可配置的时间段内(默认为十分钟)保留对通过 dRuby 导出的所有对象的本地引用,以防止它们在这段时间内成为garbage-collected。在主 dRuby 发行版的 sample/name.rb 中提供了另一个示例实现。这允许对象指定自己的 id 或 “name”。通过让每个进程使用相同的 dRuby 名称注册一个对象,可以使 dRuby 引用在进程间持久化。

相关用法


注:本文由纯净天空筛选整理自ruby-lang.org大神的英文原创作品 DRb模块。非经特殊声明,原始代码版权归原作者所有,本译文未经允许或授权,请勿转载或复制。