最后,我们来看看Ruby中的分布式编程。现在网络已经非常普遍,我们有时候想在网络上传递各种对象,但是不幸的是,像CORBA,RMI这些协议使用起来非常费力,需要特殊规定的编码,异常处理,而且还要在任何调用前定义接口。
Ruby对此有一个简单的解决方法,消除了上面方法的繁琐之处。分布式Ruby(也叫drb或者druby)是一个独立的库,完全由Ruby写成,通过这个库,你可以通过TCP在不同的Ruby进程中传送各种对象(Ruby对象),而且只需要很少的步骤。
清单8显示了这样的一个例子,这个服务端共享了一个对象,通过这个对象,你可以得到服务器的时间。第3行到第7行定义了这个取得本地时间要被共享的对象,第9行将这个对象绑定到一个drb服务器(本例中端口为2222),因为服务器程序在一个独立的线程,所以第10行确保主程序会在这个线程结束后才能退出。
清单8: A simple distributed Ruby server 1 require 'drb' 2 3 class Info 4 def get_time 5 "It is now #{Time.now}" 6 end 7 end 8 9 DRb.start_service("druby://your.host.name:2222", Info.new) 10 DRb.thread.join |
清单9是一个客户端程序,也非常简单,第4行的DRbObject 调用和远程服务器建立了一个连接,返回了远程对象的一个代理,然后,你就可以像调用本地对象一样调用远程对象的方法了。
清单9: A simple distributed Ruby client 1 require 'drb' 2 3 DRb.start_service 4 info = DRbObject.new(nil, "druby://your.host.name:2222") 5 6 3.times do 7 puts info.get_time 8 sleep 2 9 end |
下面我们来看一下另一个很有意思的东西tuplesapces,它是David Gelernter在Linda系统中首先提出来的,tuplespaces像一个共享的bbs系统,程序可以向它贴消息,或者从它取得消息,这里所说的消息都是一些值组成的数组。out方法用来向tuplespace写消息,所以,下面的代码代码创建了一个含有4条内容的tuplespace:
require 'tuplespace' ts = TupleSpace.new ts.out [ "dave", "car", "blazer" ] ts.out [ "dave", "computer", "dell" ] ts.out [ "andy", "car", "explorer" ] ts.out [ "andy", "os", "linux" ]
使tuplespaces有趣的是你要想取得它里面的内容,你不是根据地址来取,而是根据内容本身(通过匹配)来取。Ruby实现的tuplespace更加强大,取得消息的匹配模式可以是值,对象的类,正则表达式,range等等。nil表示你不关心它的内容是什么,即表示任何东西都匹配nil。要想取得消息内容可以用in方法,如果in方法中指定的模式能在tuplespace中找到匹配的项,那么这个匹配的项将被从tuplespace中删除,并且将这项返回给调用者;否则的话,in将等待直到有匹配的项目出现。如果有多个记录匹配in中指定的模式,那么将会随机的返回其中的一个记录。
继续前面的例子,下面的例子从tuplespace中读取已经存入的记录,注意最后一个语句,它使用了正则表达式作为匹配的模式。
# read one of Dave's possessions res1 = ts.in ["dave", nil, nil] # someone owning a car res2 = ts.in [nil, "car", nil] # a possession containing the "x" or "z" res3 = ts.in [nil, nil, /[xz]/]
从这个简单的例子开始,你能轻松地编写出复杂,协作,并行的系统。
比如,你可以用tuplespace解决复杂地AI问题。一个进程可以通过将一组数据组合起来放到tuplespace中来产生一个问题,其他进程则通过匹配地方式从tuplespace中读取它能解决地问题;一个进程得到一个错误地时候,它也可能把这个问题细分为更小的问题 ,然后将它们放到tuplespace里面。其它的进程又取得这个问题,然后解决它们或者继续分解这些问题。
这个过程将继续,直到所有的问题都得到解决。
为了完成这篇文章,我们将使用drb和tuplespace编写一个简单的P2P的聊天程序,这个系统将由三个元素组成的消息存放在tuplespace中,即消息的发送者,接收者,和消息内容。
客户端的程序在清单10中,客户端由两个线程一起运行,发送线程是主线程,在它里面显示的创建了接收线程。发送线程从第16行到22行,它从用户的控制台读取字符串,格式如下:
to: message text
to是接收者姓名,第17行将上面的输入分成两部分,前面的是姓名,剩下的是消息内容。然后,第19行将这个消息组成一个数组写到tuplespace里面。
清单10: A chat client based on tuplespaces 1 require 'drb' 2 require 'tuplespace' 3 4 DRb.start_service 5 ts = DRbObject.new(nil, / "druby://server.host:12321") 6 7 my_name = ARGV[0] 8 9 Thread.new do 10 loop do 11 from, unused, line = ts.in / [ nil, my_name, nil ] 12 puts "#{from} says: #{line}" 13 end 14 end 15 16 while line = gets 17 to, text = line.split(/:/, 2) 18 if text 19 ts.out [ my_name, to, text ] 20 else 21 puts '** use "to: message"' 22 end 23 end |
接收线程也很简单,第9行到14行运行一个简单的循环,从tuplespace中读取数据,匹配条件是消息接收者是我们自己的名字,然后将结果打印在控制台上。
Tuplespaces需要一个服务器存放tuples,在Ruby中用drb服务器来存放tuplespace对象,可以用下面代码实现:
require 'drb' require 'tuplespace' DRb.start_service("druby://server.host:12321", / TupleSpace.new) DRb.thread.join
首先运行服务端程序,然后运行客户端,并且在客户端程序指定你想用的名字作为参数。
如果你运行它你会发现,如果你不在线,服务器端会把给你的消息保存在服务器上。我们的这个应用程序不恩那个取代Yahoo IM,Jabber或者IRC等,但这段代码却非常有用。