DRb 模块

概述

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

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

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

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

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

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

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

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

发出信号的最简单方法是我们希望以 DRbObject 引用(而不是编组并作为副本发送)的形式传递或返回一个原本可编组的对象,即包含 DRb::DRbUndumped mixin 模块。

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 套接字进行通信。备用协议使用 UNIX 域套接字,由 drb/unix.rb 文件中的 DRb::DRbUNIXSocket 实现,并由方案“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 lib/drb/drb.rb, line 1832
def config
  current_server.config
rescue
  DRbServer.make_config
end
current_server() 单击以切换源

获取“当前”服务器。

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

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

# File 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 lib/drb/drb.rb, line 1934
def fetch_server(uri)
  @server[uri]
end
front() 单击以切换源

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

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

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

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

# File 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) 单击以切换源

Set将默认ACL设置为acl

请参见DRb::DRbServer.default_acl

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

Set默认id转换对象。

预计这是一个实例,例如DRb::DRbIdConv,它响应to_idto_obj,这些方法可以将对象转换为DRb引用,也可以将对象从DRb引用转换为对象。

请参见DRbServer#default_id_conv。

# File 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 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 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 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 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 lib/drb/drb.rb, line 1869
def thread
  @primary_server ? @primary_server.thread : nil
end
to_id(obj) 单击以切换源

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

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

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

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

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

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

获取定义本地 dRuby 空间的 URI

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

# File 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 lib/drb/drb.rb, line 1832
def config
  current_server.config
rescue
  DRbServer.make_config
end
current_server() 单击以切换源

获取“当前”服务器。

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

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

# File 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 lib/drb/drb.rb, line 1934
def fetch_server(uri)
  @server[uri]
end
front() 单击以切换源

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

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

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

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

# File 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) 单击以切换源

Set将默认ACL设置为acl

请参见DRb::DRbServer.default_acl

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

Set默认id转换对象。

预计这是一个实例,例如DRb::DRbIdConv,它响应to_idto_obj,这些方法可以将对象转换为DRb引用,也可以将对象从DRb引用转换为对象。

请参见DRbServer#default_id_conv。

# File 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 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 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 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 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 lib/drb/drb.rb, line 1869
def thread
  @primary_server ? @primary_server.thread : nil
end
to_id(obj) 单击以切换源

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

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

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

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

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

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

获取定义本地 dRuby 空间的 URI

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

# File 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