模块 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
属性
主要的本地 dRuby 服务器。
这是由 start_service
调用创建的服务器。
主要的本地 dRuby 服务器。
这是由 start_service
调用创建的服务器。
公共类方法
获取当前服务器的配置。
如果没有当前服务器,则返回默认配置。请参阅 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
获取“当前”服务器。
在 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
检索具有给定 uri
的服务器。
另请参阅 regist_server
和 remove_server。
# File drb-2.2.1/lib/drb/drb.rb, line 1934 def fetch_server(uri) @server[uri] end
获取当前服务器的前端对象。
如果没有当前服务器,则会引发 DRbServerNotFound
错误。请参阅 current_server
。
# File drb-2.2.1/lib/drb/drb.rb, line 1843 def front current_server.front end
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
将默认的 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
设置默认的 ID 转换对象。
这应该是诸如 DRb::DRbIdConv
之类的实例,它可以响应 to_id
和 to_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
向 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
从已注册服务器列表中移除 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
在本地启动一个 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
停止本地 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
获取主服务器的线程。
如果没有主服务器,则返回 nil。请参阅 primary_server
。
# File drb-2.2.1/lib/drb/drb.rb, line 1869 def thread @primary_server ? @primary_server.thread : nil end
使用当前服务器获取对象的引用 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
使用当前服务器将引用转换为对象。
如果没有当前服务器,则会引发 DRbServerNotFound
错误。请参阅 current_server
。
# File drb-2.2.1/lib/drb/drb.rb, line 1852 def to_obj(ref) current_server.to_obj(ref) end
获取定义本地 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
私有实例方法
获取当前服务器的配置。
如果没有当前服务器,则返回默认配置。请参阅 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
获取“当前”服务器。
在 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
检索具有给定 uri
的服务器。
另请参阅 regist_server
和 remove_server。
# File drb-2.2.1/lib/drb/drb.rb, line 1934 def fetch_server(uri) @server[uri] end
获取当前服务器的前端对象。
如果没有当前服务器,则会引发 DRbServerNotFound
错误。请参阅 current_server
。
# File drb-2.2.1/lib/drb/drb.rb, line 1843 def front current_server.front end
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
将默认的 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
设置默认的 ID 转换对象。
这应该是诸如 DRb::DRbIdConv
之类的实例,它可以响应 to_id
和 to_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
向 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
从已注册服务器列表中移除 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
在本地启动一个 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
停止本地 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
获取主服务器的线程。
如果没有主服务器,则返回 nil。请参阅 primary_server
。
# File drb-2.2.1/lib/drb/drb.rb, line 1869 def thread @primary_server ? @primary_server.thread : nil end
使用当前服务器获取对象的引用 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
使用当前服务器将引用转换为对象。
如果没有当前服务器,则会引发 DRbServerNotFound
错误。请参阅 current_server
。
# File drb-2.2.1/lib/drb/drb.rb, line 1852 def to_obj(ref) current_server.to_obj(ref) end
获取定义本地 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