模組 DRb

概述

dRuby 是 Ruby 的分散式物件系統。它是用純 Ruby 編寫的,並使用自己的通訊協定。除了 Ruby 執行時期提供的功能(例如 TCP socket)之外,不需要其他附加服務。它不依賴或與其他分散式物件系統(例如 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 混入模組。

dRuby 支援使用區塊呼叫遠端方法。由於區塊(或表示區塊的 Proc 物件)無法封送,因此區塊會在本地執行,而不是在遠端執行。傳遞給區塊的每個值都會從遠端物件傳遞到本地區塊,然後每個區塊呼叫傳回的值會傳遞回遠端執行內容以進行收集,最後將收集的值傳回本地內容作為方法呼叫的傳回值。

使用範例

如需更多 dRuby 範例,請參閱完整 dRuby 發行版中的 samples 目錄。

dRuby 在用戶端/伺服器模式

這說明如何設定一個簡單的用戶端/伺服器 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 是使用三個主要元件實作的:遠端方法呼叫封送器/解封器、傳輸協定和 ID 至物件對應器。後兩個元件可以被直接替換,而第一個元件可以被間接替換,以提供不同的行為和功能。

遠端方法呼叫的封送和解封是由 DRb::DRbMessage 實例執行的。它使用 Marshal 模組在透過傳輸層傳送方法呼叫之前將其傾印,然後在另一端重新組成它。通常不需要替換此元件,而且也沒有提供直接替換它的方式。但是,可以將替代封送配置實作為傳輸層實作的一部分。

傳輸層負責開啟用戶端和伺服器網路連線,並透過它們轉送 dRuby 要求。通常,它在內部使用 DRb::DRbMessage 來管理封送和解封。傳輸層是由 DRb::DRbProtocol 管理的。可以在 DRbProtocol 中一次安裝多個協定;它們之間的選取是由 dRuby URI 的配置決定的。預設傳輸協定是由配置 'druby:' 選取的,並由 DRb::DRbTCPSocket 實作。它使用純粹的 TCP/IP socket 來進行通訊。另一個使用 UNIX 領域 socket 的替代協定是由 DRb::DRbUNIXSocket 在 drb/unix.rb 檔案中實作,並由配置 'drbunix:' 選取。可以在 dRuby 主要發行版的範例中找到一個透過 HTTP 的範例實作。

ID 至物件對應元件將 dRuby 物件 ID 對應到它們所參照的物件,反之亦然。可以使用 DRb::DRbServer 的設定來指定要使用的實作。預設實作是由 DRb::DRbIdConv 提供的。它使用物件的 ObjectSpace ID 作為它的 dRuby ID。這表示對該物件的 dRuby 參照只在物件處理程序的存續期間和物件在該處理程序中的存續期間內有意義。修改後的實作是由 DRb::TimerIdConv 在 drb/timeridconv.rb 檔案中提供的。此實作會保留對透過 dRuby 匯出的所有物件的區域參照一段可設定的時間(預設為十分鐘),以防止它們在此時間內被垃圾回收。另一個範例實作是在 dRuby 主要發行版的 sample/name.rb 中提供的。這允許物件指定它們自己的 ID 或「名稱」。透過讓每個處理程序使用相同的 dRuby 名稱註冊物件,可以讓 dRuby 參照在處理程序之間保持持續。

常數

VERSION

屬性

primary_server[RW]

主要的本機 dRuby 伺服器。

這是由 start_service 呼叫所建立的伺服器。

primary_server[RW]

主要的本機 dRuby 伺服器。

這是由 start_service 呼叫所建立的伺服器。

公共類別方法

config() 按一下以切換來源

取得目前伺服器的組態。

如果沒有目前的伺服器,這會傳回預設組態。請參閱 current_server 和 DRbServer::make_config。

# File lib/drb/drb.rb, line 1832
def config
  current_server.config
rescue
  DRbServer.make_config
end
current_server() 按一下以切換來源

取得「目前的」伺服器。

在 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
fetch_server(uri) 按一下以切換來源

擷取具有指定 uri 的伺服器。

另請參閱 regist_server 和 remove_server。

# File lib/drb/drb.rb, line 1934
def fetch_server(uri)
  @server[uri]
end
front() 按一下以切換來源

取得目前伺服器的前端物件。

如果沒有目前的伺服器,這會引發 DRbServerNotFound 錯誤。請參閱 current_server

# File lib/drb/drb.rb, line 1843
def front
  current_server.front
end
here?(uri) 按一下以切換來源

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
install_acl(acl) 按一下以切換來源

Set 預設 ACLacl

請參閱 DRb::DRbServer.default_acl

# File lib/drb/drb.rb, line 1888
def install_acl(acl)
  DRbServer.default_acl(acl)
end
install_id_conv(idconv) 按一下以切換來源

Set 預設 id 轉換物件。

這預期是一個像 DRb::DRbIdConv 的執行個體,它會回應 to_idto_obj,這些方法可以將物件轉換為 DRb 參照,反之亦然。

請參閱 DRbServer#default_id_conv。

# File lib/drb/drb.rb, line 1880
def install_id_conv(idconv)
  DRbServer.default_id_conv(idconv)
end
regist_server(server) 按一下以切換來源

使用 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
remove_server(server) 按一下以切換原始碼

從已註冊伺服器的清單中移除 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
start_service(uri=nil, front=nil, config=nil) 按一下以切換原始碼

在本地端啟動 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
stop_service() 按一下以切換原始碼

停止本機 dRuby 伺服器。

此方法會在主要伺服器上執行。如果目前沒有主要伺服器正在執行,則此方法為空操作。

# File lib/drb/drb.rb, line 1801
def stop_service
  @primary_server.stop_service if @primary_server
  @primary_server = nil
end
thread() 按一下以切換原始碼

取得主要伺服器的執行緒。

如果沒有主要伺服器,則此方法會傳回 nil。請參閱 primary_server

# File lib/drb/drb.rb, line 1869
def thread
  @primary_server ? @primary_server.thread : nil
end
to_id(obj) 按一下以切換原始碼

使用目前的伺服器取得物件的參考 ID。

如果沒有目前的伺服器,這會引發 DRbServerNotFound 錯誤。請參閱 current_server

# File lib/drb/drb.rb, line 1860
def to_id(obj)
  current_server.to_id(obj)
end
to_obj(ref) 按一下以切換原始碼

使用目前的伺服器將參考轉換為物件。

如果沒有目前的伺服器,這會引發 DRbServerNotFound 錯誤。請參閱 current_server

# File lib/drb/drb.rb, line 1852
def to_obj(ref)
  current_server.to_obj(ref)
end
uri() 按一下以切換原始碼

取得定義本機 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

私人執行個體方法

config() 按一下以切換來源

取得目前伺服器的組態。

如果沒有目前的伺服器,這會傳回預設組態。請參閱 current_server 和 DRbServer::make_config。

# File lib/drb/drb.rb, line 1832
def config
  current_server.config
rescue
  DRbServer.make_config
end
current_server() 按一下以切換來源

取得「目前的」伺服器。

在 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
fetch_server(uri) 按一下以切換來源

擷取具有指定 uri 的伺服器。

另請參閱 regist_server 和 remove_server。

# File lib/drb/drb.rb, line 1934
def fetch_server(uri)
  @server[uri]
end
front() 按一下以切換來源

取得目前伺服器的前端物件。

如果沒有目前的伺服器,這會引發 DRbServerNotFound 錯誤。請參閱 current_server

# File lib/drb/drb.rb, line 1843
def front
  current_server.front
end
here?(uri) 按一下以切換來源

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
install_acl(acl) 按一下以切換來源

Set 預設 ACLacl

請參閱 DRb::DRbServer.default_acl

# File lib/drb/drb.rb, line 1888
def install_acl(acl)
  DRbServer.default_acl(acl)
end
install_id_conv(idconv) 按一下以切換來源

Set 預設 id 轉換物件。

這預期是一個像 DRb::DRbIdConv 的執行個體,它會回應 to_idto_obj,這些方法可以將物件轉換為 DRb 參照,反之亦然。

請參閱 DRbServer#default_id_conv。

# File lib/drb/drb.rb, line 1880
def install_id_conv(idconv)
  DRbServer.default_id_conv(idconv)
end
regist_server(server) 按一下以切換來源

使用 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
remove_server(server) 按一下以切換原始碼

從已註冊伺服器的清單中移除 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
start_service(uri=nil, front=nil, config=nil) 按一下以切換原始碼

在本地端啟動 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
stop_service() 按一下以切換原始碼

停止本機 dRuby 伺服器。

此方法會在主要伺服器上執行。如果目前沒有主要伺服器正在執行,則此方法為空操作。

# File lib/drb/drb.rb, line 1801
def stop_service
  @primary_server.stop_service if @primary_server
  @primary_server = nil
end
thread() 按一下以切換原始碼

取得主要伺服器的執行緒。

如果沒有主要伺服器,則此方法會傳回 nil。請參閱 primary_server

# File lib/drb/drb.rb, line 1869
def thread
  @primary_server ? @primary_server.thread : nil
end
to_id(obj) 按一下以切換原始碼

使用目前的伺服器取得物件的參考 ID。

如果沒有目前的伺服器,這會引發 DRbServerNotFound 錯誤。請參閱 current_server

# File lib/drb/drb.rb, line 1860
def to_id(obj)
  current_server.to_id(obj)
end
to_obj(ref) 按一下以切換原始碼

使用目前的伺服器將參考轉換為物件。

如果沒有目前的伺服器,這會引發 DRbServerNotFound 錯誤。請參閱 current_server

# File lib/drb/drb.rb, line 1852
def to_obj(ref)
  current_server.to_obj(ref)
end
uri() 按一下以切換原始碼

取得定義本機 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