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)显示源
注册server
DRb。
这是在创建新的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(服务器)显示源
注册server
DRb。
这是在创建新的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