當前位置: 首頁>>編程示例 >>用法及示例精選 >>正文


Ruby DRb模塊用法及代碼示例

本文簡要介紹ruby語言中 DRb模塊 的用法。

概述

dRuby 是 Ruby 的分布式對象係統。它是用純 Ruby 編寫的,並使用自己的協議。除了 Ruby 運行時提供的服務(例如 TCP 套接字)之外,不需要 add-in 服務。它不依賴於或與其他分布式對象係統(如 CORBA、RMI 或 .NET)互操作。

dRuby 允許在一個 Ruby 進程中對位於另一個 Ruby 進程中的 Ruby 對象調用方法,即使在另一台機器上也是如此。對對象的引用可以在進程之間傳遞。 Method 參數和返回值以編組格式轉儲和加載。所有這些對遠程方法的調用者和調用它的對象都是透明的。

遠程進程中的對象在本地由 DRb::DRbObject 實例表示。這充當了遠程對象的一種代理。調用此 DRbObject 實例的方法將轉發到其遠程對象。這是在運行時動態安排的。遠程對象沒有靜態聲明的接口,例如 CORBA 的 IDL。

對進程進行的 dRuby 調用由該進程中的 DRb::DRbServer 實例處理。這重構了方法調用,在指定的本地對象上調用它,並將值返回給遠程調用者。任何對象都可以通過 dRuby 接收調用。不需要實現特殊的接口,或者 mixin 特殊的函數。在一般情況下,對象也不需要向 DRbServer 顯式注冊自己以接收 dRuby 調用。

一個進程希望對另一個進程進行 dRuby 調用,必須以某種方式獲得對遠程進程中對象的初始引用,而不是作為遠程方法調用的返回值,因為最初沒有遠程對象引用,它可以調用方法上。這是通過 URI 附加到服務器來完成的。每個 DRbServer 將自己綁定到 URI ,例如“druby://example.com:8787”。一個 DRbServer 可以附加一個對象,作為服務器的前端對象。 DRbObject 可以從服務器的 URI 顯式創建。這個 DRbObject 的遠程對象將是服務器的前端對象。然後這個前端對象可以返回對 DRbServer 進程中其他 Ruby 對象的引用。

通過 dRuby 進行的 Method 調用的行為與在進程中進行的普通 Ruby 方法調用基本相同。支持帶有塊的 Method 調用,以及引發異常。除了方法的標準錯誤之外,dRuby 調用還可能引發 dRuby-specific 錯誤之一,所有這些錯誤都是 DRb::DRbError 的子類。

任何類型的對象都可以作為參數傳遞給 dRuby 調用或作為其返回值返回。默認情況下,此類對象在本地轉儲或編組,然後在遠程端加載或解組。因此,遠程端接收到本地對象的副本,而不是對它的分布式引用;在此副本上調用的方法完全在遠程進程中執行,而不是傳遞給本地原始進程。這具有類似於pass-by-value 的語義。

然而,如果一個對象不能被編組,一個對它的 dRuby 引用將被傳遞或返回。這將在遠程端顯示為 DRbObject 實例。在這個遠程代理上調用的所有方法都被轉發到本地對象,如 DRbObjects 的討論中所述。這具有類似於普通 Ruby pass-by-reference 的語義。

表明我們希望將其他可編組對象作為 DRbObject 引用傳遞或返回而不是編組並作為副本發送的最簡單方法是包含 DRb::DRbUndumped mixin 模塊。

dRuby 支持使用塊調用遠程方法。由於塊(或者更確切地說是表示它們的 Proc 對象)不可編組,因此塊在本地而不是遠程上下文中執行。產生給塊的每個值都從遠程對象傳遞到本地塊,然後每個塊調用返回的值被傳遞回遠程執行上下文進行收集,最後收集的值作為本地上下文返回到本地上下文方法調用的返回值。

使用示例

有關更多 dRuby 示例,請參閱完整 dRuby 發行版中的 samples 目錄。

客戶端/服務器模式下的 dRuby

這說明了如何設置一個簡單的client-server drb 係統。在不同的終端運行服務端和客戶端代碼,先啟動服務端代碼。

服務器代碼

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

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://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 調用返回對對象的引用。 Logger 實例存在於服務器進程中。對它們的引用將返回給客戶端進程,可以在其中調用它們的方法。這些方法在服務器進程中執行。

服務器代碼

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(/[.\/\\\:]/, "_")
            @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://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 僅應在客戶端受信任時使用。

可以為 DRbServer 配置訪問控製列表,以選擇性地允許或拒絕來自指定 IP 地址的訪問。主要的 druby 發行版為此提供了 ACL 類。一般來說,這種機製應該隻與一個好的防火牆一起使用,而不是作為一個好的防火牆的替代品。

dRuby 內部結構

dRuby 使用三個主要組件實現:遠程方法調用 marshaller/unmarshaller;傳輸協議;和一個ID-to-object 映射器。後兩者可以直接替換,第一個間接替換,以提供不同的行為和函數。

遠程方法調用的編組和解組由 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-to-object 映射組件將 dRuby 對象 ID 映射到它們引用的對象,反之亦然。要使用的實現可以指定為 DRb::DRbServer 配置的一部分。默認實現由 DRb::DRbIdConv 提供。它使用對象的 ObjectSpace id 作為其 dRuby id。這意味著對該對象的 dRuby 引用僅在對象進程的生命周期和該進程內對象的生命周期內才有意義。修改後的實現由文件 drb/timeridconv.rb 中的 DRb::TimerIdConv 提供。此實現在可配置的時間段內(默認為十分鍾)保留對通過 dRuby 導出的所有對象的本地引用,以防止它們在這段時間內成為garbage-collected。在主 dRuby 發行版的 sample/name.rb 中提供了另一個示例實現。這允許對象指定自己的 id 或 “name”。通過讓每個進程使用相同的 dRuby 名稱注冊一個對象,可以使 dRuby 引用在進程間持久化。

相關用法


注:本文由純淨天空篩選整理自ruby-lang.org大神的英文原創作品 DRb模塊。非經特殊聲明,原始代碼版權歸原作者所有,本譯文未經允許或授權,請勿轉載或複製。