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
属性
主本地 dRuby 服务器。
这是由 start_service
调用创建的服务器。
主本地 dRuby 服务器。
这是由 start_service
调用创建的服务器。
公共类方法
获取当前服务器的配置。
如果没有当前服务器,则返回默认配置。请参阅 current_server
和 DRbServer::make_config。
# File lib/drb/drb.rb, line 1832 def config current_server.config rescue DRbServer.make_config end
获取“当前”服务器。
在 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
检索具有给定uri
的服务器。
另请参见regist_server
和remove_server。
# File lib/drb/drb.rb, line 1934 def fetch_server(uri) @server[uri] end
获取当前服务器的前端对象。
如果没有当前服务器,这会引发DRbServerNotFound
错误。请参见current_server
。
# File lib/drb/drb.rb, line 1843 def front current_server.front end
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
请参见DRb::DRbServer.default_acl
。
# File lib/drb/drb.rb, line 1888 def install_acl(acl) DRbServer.default_acl(acl) 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 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 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 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 lib/drb/drb.rb, line 1801 def stop_service @primary_server.stop_service if @primary_server @primary_server = nil end
获取主服务器的线程。
如果不存在主服务器,则返回 nil。请参阅 primary_server
。
# File lib/drb/drb.rb, line 1869 def thread @primary_server ? @primary_server.thread : nil end
使用当前服务器获取对象的引用 ID。
如果没有当前服务器,这会引发DRbServerNotFound
错误。请参见current_server
。
# File lib/drb/drb.rb, line 1860 def to_id(obj) current_server.to_id(obj) end
使用当前服务器将引用转换为对象。
如果没有当前服务器,这会引发DRbServerNotFound
错误。请参见current_server
。
# File lib/drb/drb.rb, line 1852 def to_obj(ref) current_server.to_obj(ref) end
获取定义本地 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
私有实例方法
获取当前服务器的配置。
如果没有当前服务器,则返回默认配置。请参阅 current_server
和 DRbServer::make_config。
# File lib/drb/drb.rb, line 1832 def config current_server.config rescue DRbServer.make_config end
获取“当前”服务器。
在 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
检索具有给定uri
的服务器。
另请参见regist_server
和remove_server。
# File lib/drb/drb.rb, line 1934 def fetch_server(uri) @server[uri] end
获取当前服务器的前端对象。
如果没有当前服务器,这会引发DRbServerNotFound
错误。请参见current_server
。
# File lib/drb/drb.rb, line 1843 def front current_server.front end
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
请参见DRb::DRbServer.default_acl
。
# File lib/drb/drb.rb, line 1888 def install_acl(acl) DRbServer.default_acl(acl) 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 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 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 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 lib/drb/drb.rb, line 1801 def stop_service @primary_server.stop_service if @primary_server @primary_server = nil end
获取主服务器的线程。
如果不存在主服务器,则返回 nil。请参阅 primary_server
。
# File lib/drb/drb.rb, line 1869 def thread @primary_server ? @primary_server.thread : nil end
使用当前服务器获取对象的引用 ID。
如果没有当前服务器,这会引发DRbServerNotFound
错误。请参见current_server
。
# File lib/drb/drb.rb, line 1860 def to_id(obj) current_server.to_id(obj) end
使用当前服务器将引用转换为对象。
如果没有当前服务器,这会引发DRbServerNotFound
错误。请参见current_server
。
# File lib/drb/drb.rb, line 1852 def to_obj(ref) current_server.to_obj(ref) end
获取定义本地 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