Ruby 2.4

DRb

module DRb

Overview

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

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

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

进入进程的dRuby调用由该进程中的DRb :: DRbServer实例处理。这将重构方法调用,在指定的本地对象上调用它,并将值返回给远程调用方。任何对象都可以通过dRuby接收呼叫。没有必要实现一个特殊的界面,或混入特殊的功能。在一般情况下,对象也不需要为了接收dRuby调用而明确地向DRbServer注册自己。

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

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

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

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

最简单的方式表示我们需要一个否则可编组的对象作为DRbObject引用传递或返回,而不是作为副本进行编组和发送,是包含DRb :: DRbUndumped混合模块。

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

使用的例子

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

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

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

Server code

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 $SAFE = 1 # disable eval() and friends DRb.start_service(URI, FRONT_OBJECT) # Wait for the drb server thread to finish before exiting. DRb.thread.join

Client code

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调用返回对象的引用。记录器实例位于服务器进程中。对它们的引用被返回给客户端进程,在这个进程中可以调用方法。这些方法在服务器进程中执行。

Server code

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(/[.\/\\:]/, "_").untaint @loggers[name] = Logger.new(name, @basedir + "/" + fname) end return @loggers[name] end end FRONT_OBJECT=LoggerFactory.new("/tmp/dlog") $SAFE = 1 # disable eval() and friends DRb.start_service(URI, FRONT_OBJECT) DRb.thread.join

Client code

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通常应该在$ SAFE设置为至少为1级的情况下运行。这将禁用通过线路传递的字符串的eval()和相关调用。上面给出的示例使用代码遵循这种做法。

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

dRuby internals

dRuby使用三个主要组件实现:远程方法调用编组器/解组器; 传输协议; 和一个ID到对象映射器。后两者可以是直接的,并且第一个是间接替换的,以提供不同的行为和能力。

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

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

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

Attributes

primary_serverRW

主要的本地dRuby服务器。

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

primary_serverRW

主要的本地dRuby服务器。

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

公共类方法

config() Show source

获取当前服务器的配置。

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

# File lib/drb/drb.rb, line 1765 def config current_server.config rescue DRbServer.make_config end

current_server()显示源文件

获取'当前'服务器。

在执行发生在dRuby服务器的主线程中的执行环境中(通常,由于服务器或其中一个对象的远程调用),当前服务器就是该服务器。否则,当前服务器是主服务器。

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

# File lib/drb/drb.rb, line 1722 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

# File lib/drb/drb.rb, line 1862 def fetch_server(uri) @server[uri] end

front()显示源代码

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

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

# File lib/drb/drb.rb, line 1776 def front current_server.front end

这里?(uri)显示源代码

uri当前本地服务器的URI?

# File lib/drb/drb.rb, line 1755 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 lib/drb/drb.rb, line 1821 def install_acl(acl) DRbServer.default_acl(acl) end

install_id_conv(idconv)显示源文件

设置默认的id转换对象。

这应该是一个实例,如DRb :: DRbIdConv,它响应to_id和to_obj,它们可以将对象转换为DRb引用或从中转换对象。

请参阅DRbServer#default_id_conv。

# File lib/drb/drb.rb, line 1813 def install_id_conv(idconv) DRbServer.default_id_conv(idconv) end

regist_server(server)显示源

注册serverDRb。

这是在创建新的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 1845 def regist_server(server) @server[server.uri] = server mutex.synchronize do @primary_server = server unless @primary_server end end

remove_server(sever)显示源

server从注册服务器列表中删除。

# File lib/drb/drb.rb, line 1854 def remove_server(server) @server.delete(server.uri) end

start_service(uri = nil,front = nil,config = nil)显示源文件

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

即使另一台服务器当前是主服务器,新的dRuby服务器也将成为主服务器。

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

front是服务器的前端对象。这可能是零。

config是新服务器的配置。这可能是零。

请参阅DRb :: DRbServer.new。

# File lib/drb/drb.rb, line 1701 def start_service(uri=nil, front=nil, config=nil) @primary_server = DRbServer.new(uri, front, config) end

stop_service()显示源文件

停止本地dRuby服务器。

这在主服务器上运行。如果当前没有主服务器在运行,那么这是一个noop。

# File lib/drb/drb.rb, line 1734 def stop_service @primary_server.stop_service if @primary_server @primary_server = nil end

线程()显示源文件

获取主服务器的线程。

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

# File lib/drb/drb.rb, line 1802 def thread @primary_server ? @primary_server.thread : nil end

to_id(obj)显示源代码

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

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

# File lib/drb/drb.rb, line 1793 def to_id(obj) current_server.to_id(obj) end

to_obj(ref) Show source

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

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

# File lib/drb/drb.rb, line 1785 def to_obj(ref) current_server.to_obj(ref) end

uri()显示源代码

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

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

# File lib/drb/drb.rb, line 1743 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 1765 def config current_server.config rescue DRbServer.make_config end

current_server()显示源文件

获取'当前'服务器。

在执行发生在dRuby服务器的主线程中的执行环境中(通常,由于服务器或其中一个对象的远程调用),当前服务器就是该服务器。否则,当前服务器是主服务器。

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

# File lib/drb/drb.rb, line 1722 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

# File lib/drb/drb.rb, line 1862 def fetch_server(uri) @server[uri] end

front()显示源代码

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

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

# File lib/drb/drb.rb, line 1776 def front current_server.front end

这里?(uri)显示源代码

uri当前本地服务器的URI?

# File lib/drb/drb.rb, line 1755 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 lib/drb/drb.rb, line 1821 def install_acl(acl) DRbServer.default_acl(acl) end

install_id_conv(idconv)显示源文件

设置默认的id转换对象。

这应该是一个实例,如DRb :: DRbIdConv,它响应to_id和to_obj,它们可以将对象转换为DRb引用或从中转换对象。

请参阅DRbServer#default_id_conv。

# File lib/drb/drb.rb, line 1813 def install_id_conv(idconv) DRbServer.default_id_conv(idconv) end

regist_server(服务器)显示源

注册serverDRb。

这是在创建新的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 1845 def regist_server(server) @server[server.uri] = server mutex.synchronize do @primary_server = server unless @primary_server end end

remove_server(服务器)显示源

server从注册服务器列表中删除。

# File lib/drb/drb.rb, line 1854 def remove_server(server) @server.delete(server.uri) end

start_service(uri = nil,front = nil,config = nil)显示源文件

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

即使另一台服务器当前是主服务器,新的dRuby服务器也将成为主服务器。

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

front是服务器的前端对象。这可能是零。

config是新服务器的配置。这可能是零。

请参阅DRb :: DRbServer.new。

# File lib/drb/drb.rb, line 1701 def start_service(uri=nil, front=nil, config=nil) @primary_server = DRbServer.new(uri, front, config) end

stop_service()显示源文件

停止本地dRuby服务器。

这在主服务器上运行。如果当前没有主服务器在运行,那么这是一个noop。

# File lib/drb/drb.rb, line 1734 def stop_service @primary_server.stop_service if @primary_server @primary_server = nil end

thread()显示源文件

获取主服务器的线程。

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

# File lib/drb/drb.rb, line 1802 def thread @primary_server ? @primary_server.thread : nil end

to_id(obj)显示源代码

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

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

# File lib/drb/drb.rb, line 1793 def to_id(obj) current_server.to_id(obj) end

to_obj(ref)显示源文件

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

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

# File lib/drb/drb.rb, line 1785 def to_obj(ref) current_server.to_obj(ref) end

uri()显示源代码

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

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

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