模块 DRb

概述

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

dRuby 允许在一个 Ruby 进程中调用另一个 Ruby 进程(甚至在另一台机器上)中的 Ruby 对象上的方法。对象引用可以在进程之间传递。方法参数和返回值以编组格式转储和加载。所有这些对于远程方法的调用者和被调用的对象都是透明的。

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

进入进程的 dRuby 调用由该进程中的 DRb::DRbServer 实例处理。这会重新构建方法调用,在指定的本地对象上调用它,并将值返回给远程调用者。任何对象都可以通过 dRuby 接收调用。无需实现特殊接口或混合特殊功能。而且,在一般情况下,对象无需显式向 DRbServer 注册即可接收 dRuby 调用。

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

通过 dRuby 进行的方法调用在很大程度上与在进程内进行的正常 Ruby 方法调用行为相同。支持带块的方法调用,以及引发异常。除了方法的标准错误之外,dRuby 调用还可能引发 dRuby 特定的错误之一,所有这些错误都是 DRb::DRbError 的子类。

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

但是,如果无法编组对象,则会传递或返回对其的 dRuby 引用。这将在远程端显示为 DRbObject 实例。如 DRbObjects 的讨论中所述,在此远程代理上调用的所有方法都将转发到本地对象。这具有类似于普通 Ruby 按引用传递的语义。

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

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

使用示例

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

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

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

服务器代码

require 'drb/drb'

# The URI for the server to connect to
URI="druby://127.0.0.1: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://127.0.0.1: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://127.0.0.1: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://127.0.0.1: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 是使用三个主要组件实现的:远程方法调用编组器/解组器;传输协议;以及 ID 到对象的映射器。后两个组件可以直接替换,而第一个组件可以间接替换,以便提供不同的行为和功能。

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

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

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

常量

VERSION

属性

primary_server[RW]

主要的本地 dRuby 服务器。

这是由 start_service 调用创建的服务器。

primary_server[RW]

主要的本地 dRuby 服务器。

这是由 start_service 调用创建的服务器。

公共类方法

config() 单击以切换源代码

获取当前服务器的配置。

如果没有当前服务器,则返回默认配置。请参阅 current_server 和 DRbServer::make_config。

# File drb-2.2.1/lib/drb/drb.rb, line 1832
def config
  current_server.config
rescue
  DRbServer.make_config
end
current_server() 单击以切换源代码

获取“当前”服务器。

在 dRuby 服务器的主线程中执行的上下文中(通常是由于对服务器或其对象的远程调用而发生的),当前服务器是该服务器。否则,当前服务器是主服务器。

如果上述规则找不到服务器,则会引发 DRbServerNotFound 错误。

# File drb-2.2.1/lib/drb/drb.rb, line 1789
def current_server
  drb = Thread.current['DRb']
  server = (drb && drb['server']) ? drb['server'] : @primary_server
  raise DRbServerNotFound unless server
  return server
end
fetch_server(uri) 单击以切换源代码

检索具有给定 uri 的服务器。

另请参阅 regist_server 和 remove_server。

# File drb-2.2.1/lib/drb/drb.rb, line 1934
def fetch_server(uri)
  @server[uri]
end
front() 单击以切换源代码

获取当前服务器的前端对象。

如果没有当前服务器,则会引发 DRbServerNotFound 错误。请参阅 current_server

# File drb-2.2.1/lib/drb/drb.rb, line 1843
def front
  current_server.front
end
here?(uri) 单击以切换源代码

uri 是当前本地服务器的 URI 吗?

# File drb-2.2.1/lib/drb/drb.rb, line 1822
def here?(uri)
  current_server.here?(uri) rescue false
  # (current_server.uri rescue nil) == uri
end
install_acl(acl) 单击以切换源代码

将默认的 ACL 设置为 acl

请参阅 DRb::DRbServer.default_acl

# File drb-2.2.1/lib/drb/drb.rb, line 1888
def install_acl(acl)
  DRbServer.default_acl(acl)
end
install_id_conv(idconv) 单击以切换源代码

设置默认的 ID 转换对象。

这应该是诸如 DRb::DRbIdConv 之类的实例,它可以响应 to_idto_obj,可以来回转换对象 DRb 引用。

请参阅 DRbServer#default_id_conv。

# File drb-2.2.1/lib/drb/drb.rb, line 1880
def install_id_conv(idconv)
  DRbServer.default_id_conv(idconv)
end
regist_server(server) 单击以切换源代码

DRb 注册 server

当创建一个新的 DRb::DRbServer 时会调用此方法。

如果没有主服务器,则 server 将成为主服务器。

示例

require 'drb'

s = DRb::DRbServer.new # automatically calls regist_server
DRb.fetch_server s.uri #=> #<DRb::DRbServer:0x...>
# File drb-2.2.1/lib/drb/drb.rb, line 1912
def regist_server(server)
  @server[server.uri] = server
  mutex.synchronize do
    @primary_server = server unless @primary_server
  end
end
remove_server(server) 点击切换源

从已注册服务器列表中移除 server

# File drb-2.2.1/lib/drb/drb.rb, line 1921
def remove_server(server)
  @server.delete(server.uri)
  mutex.synchronize do
    if @primary_server == server
      @primary_server = nil
    end
  end
end
start_service(uri=nil, front=nil, config=nil) 点击切换源

在本地启动一个 dRuby 服务器。

新的 dRuby 服务器将成为主服务器,即使当前存在另一个主服务器。

uri 是服务器绑定的 URI。如果为 nil,则服务器将绑定到默认本地主机名上的随机端口,并使用默认的 dRuby 协议。

front 是服务器的前端对象。可以为 nil。

config 是新服务器的配置。可以为 nil。

请参阅 DRbServer::new

# File drb-2.2.1/lib/drb/drb.rb, line 1768
def start_service(uri=nil, front=nil, config=nil)
  @primary_server = DRbServer.new(uri, front, config)
end
stop_service() 点击切换源

停止本地 dRuby 服务器。

此操作在主服务器上执行。如果当前没有主服务器运行,则此操作为空操作。

# File drb-2.2.1/lib/drb/drb.rb, line 1801
def stop_service
  @primary_server.stop_service if @primary_server
  @primary_server = nil
end
thread() 点击切换源

获取主服务器的线程。

如果没有主服务器,则返回 nil。请参阅 primary_server

# File drb-2.2.1/lib/drb/drb.rb, line 1869
def thread
  @primary_server ? @primary_server.thread : nil
end
to_id(obj) 点击切换源

使用当前服务器获取对象的引用 ID。

如果没有当前服务器,则会引发 DRbServerNotFound 错误。请参阅 current_server

# File drb-2.2.1/lib/drb/drb.rb, line 1860
def to_id(obj)
  current_server.to_id(obj)
end
to_obj(ref) 点击切换源

使用当前服务器将引用转换为对象。

如果没有当前服务器,则会引发 DRbServerNotFound 错误。请参阅 current_server

# File drb-2.2.1/lib/drb/drb.rb, line 1852
def to_obj(ref)
  current_server.to_obj(ref)
end
uri() 点击切换源

获取定义本地 dRuby 空间的 URI。

这是当前服务器的 URI。请参阅 current_server

# File drb-2.2.1/lib/drb/drb.rb, line 1810
def uri
  drb = Thread.current['DRb']
  client = (drb && drb['client'])
  if client
    uri = client.uri
    return uri if uri
  end
  current_server.uri
end

私有实例方法

config() 单击以切换源代码

获取当前服务器的配置。

如果没有当前服务器,则返回默认配置。请参阅 current_server 和 DRbServer::make_config。

# File drb-2.2.1/lib/drb/drb.rb, line 1832
def config
  current_server.config
rescue
  DRbServer.make_config
end
current_server() 单击以切换源代码

获取“当前”服务器。

在 dRuby 服务器的主线程中执行的上下文中(通常是由于对服务器或其对象的远程调用而发生的),当前服务器是该服务器。否则,当前服务器是主服务器。

如果上述规则找不到服务器,则会引发 DRbServerNotFound 错误。

# File drb-2.2.1/lib/drb/drb.rb, line 1789
def current_server
  drb = Thread.current['DRb']
  server = (drb && drb['server']) ? drb['server'] : @primary_server
  raise DRbServerNotFound unless server
  return server
end
fetch_server(uri) 单击以切换源代码

检索具有给定 uri 的服务器。

另请参阅 regist_server 和 remove_server。

# File drb-2.2.1/lib/drb/drb.rb, line 1934
def fetch_server(uri)
  @server[uri]
end
front() 单击以切换源代码

获取当前服务器的前端对象。

如果没有当前服务器,则会引发 DRbServerNotFound 错误。请参阅 current_server

# File drb-2.2.1/lib/drb/drb.rb, line 1843
def front
  current_server.front
end
here?(uri) 单击以切换源代码

uri 是当前本地服务器的 URI 吗?

# File drb-2.2.1/lib/drb/drb.rb, line 1822
def here?(uri)
  current_server.here?(uri) rescue false
  # (current_server.uri rescue nil) == uri
end
install_acl(acl) 单击以切换源代码

将默认的 ACL 设置为 acl

请参阅 DRb::DRbServer.default_acl

# File drb-2.2.1/lib/drb/drb.rb, line 1888
def install_acl(acl)
  DRbServer.default_acl(acl)
end
install_id_conv(idconv) 单击以切换源代码

设置默认的 ID 转换对象。

这应该是诸如 DRb::DRbIdConv 之类的实例,它可以响应 to_idto_obj,可以来回转换对象 DRb 引用。

请参阅 DRbServer#default_id_conv。

# File drb-2.2.1/lib/drb/drb.rb, line 1880
def install_id_conv(idconv)
  DRbServer.default_id_conv(idconv)
end
regist_server(server) 单击以切换源代码

DRb 注册 server

当创建一个新的 DRb::DRbServer 时会调用此方法。

如果没有主服务器,则 server 将成为主服务器。

示例

require 'drb'

s = DRb::DRbServer.new # automatically calls regist_server
DRb.fetch_server s.uri #=> #<DRb::DRbServer:0x...>
# File drb-2.2.1/lib/drb/drb.rb, line 1912
def regist_server(server)
  @server[server.uri] = server
  mutex.synchronize do
    @primary_server = server unless @primary_server
  end
end
remove_server(server) 点击切换源

从已注册服务器列表中移除 server

# File drb-2.2.1/lib/drb/drb.rb, line 1921
def remove_server(server)
  @server.delete(server.uri)
  mutex.synchronize do
    if @primary_server == server
      @primary_server = nil
    end
  end
end
start_service(uri=nil, front=nil, config=nil) 点击切换源

在本地启动一个 dRuby 服务器。

新的 dRuby 服务器将成为主服务器,即使当前存在另一个主服务器。

uri 是服务器绑定的 URI。如果为 nil,则服务器将绑定到默认本地主机名上的随机端口,并使用默认的 dRuby 协议。

front 是服务器的前端对象。可以为 nil。

config 是新服务器的配置。可以为 nil。

请参阅 DRbServer::new

# File drb-2.2.1/lib/drb/drb.rb, line 1768
def start_service(uri=nil, front=nil, config=nil)
  @primary_server = DRbServer.new(uri, front, config)
end
stop_service() 点击切换源

停止本地 dRuby 服务器。

此操作在主服务器上执行。如果当前没有主服务器运行,则此操作为空操作。

# File drb-2.2.1/lib/drb/drb.rb, line 1801
def stop_service
  @primary_server.stop_service if @primary_server
  @primary_server = nil
end
thread() 点击切换源

获取主服务器的线程。

如果没有主服务器,则返回 nil。请参阅 primary_server

# File drb-2.2.1/lib/drb/drb.rb, line 1869
def thread
  @primary_server ? @primary_server.thread : nil
end
to_id(obj) 点击切换源

使用当前服务器获取对象的引用 ID。

如果没有当前服务器,则会引发 DRbServerNotFound 错误。请参阅 current_server

# File drb-2.2.1/lib/drb/drb.rb, line 1860
def to_id(obj)
  current_server.to_id(obj)
end
to_obj(ref) 点击切换源

使用当前服务器将引用转换为对象。

如果没有当前服务器,则会引发 DRbServerNotFound 错误。请参阅 current_server

# File drb-2.2.1/lib/drb/drb.rb, line 1852
def to_obj(ref)
  current_server.to_obj(ref)
end
uri() 点击切换源

获取定义本地 dRuby 空间的 URI。

这是当前服务器的 URI。请参阅 current_server

# File drb-2.2.1/lib/drb/drb.rb, line 1810
def uri
  drb = Thread.current['DRb']
  client = (drb && drb['client'])
  if client
    uri = client.uri
    return uri if uri
  end
  current_server.uri
end