模組 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
屬性
主要的本機 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
Set
預設 id 轉換物件。
這預期是一個像 DRb::DRbIdConv
的執行個體,它會回應 to_id
和 to_obj
,這些方法可以將物件轉換為 DRb
參照,反之亦然。
請參閱 DRbServer#default_id_conv。
# File lib/drb/drb.rb, line 1880 def install_id_conv(idconv) DRbServer.default_id_conv(idconv) 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
Set
預設 id 轉換物件。
這預期是一個像 DRb::DRbIdConv
的執行個體,它會回應 to_id
和 to_obj
,這些方法可以將物件轉換為 DRb
參照,反之亦然。
請參閱 DRbServer#default_id_conv。
# File lib/drb/drb.rb, line 1880 def install_id_conv(idconv) DRbServer.default_id_conv(idconv) 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