> 文章列表 > 【Ruby学习笔记】23.Ruby Web Service 应用 - SOAP4R及多线程

【Ruby学习笔记】23.Ruby Web Service 应用 - SOAP4R及多线程

【Ruby学习笔记】23.Ruby Web Service 应用 - SOAP4R及多线程

前言

本章介绍Ruby的Web Service 应用 - SOAP4R和多线程

Ruby Web Service 应用 - SOAP4R

什么是 SOAP?
简单对象访问协议(SOAP,全写为Simple Object Access Protocol)是交换数据的一种协议规范。

SOAP 是一种简单的基于 XML 的协议,它使应用程序通过 HTTP 来交换信息。

简单对象访问协议是交换数据的一种协议规范,是一种轻量的、简单的、基于XML(标准通用标记语言下的一个子集)的协议,它被设计成在WEB上交换结构化的和固化的信息。

SOAP4R 安装
SOAP4R 由Hiroshi Nakamura开发实现,用于 Ruby 的 SOAP 应用。

注意:你的ruby环境可能已经安装了该该组件。

Linux 环境下你也可以使用 gem 来安装该组件,命令如下:

gem install soap4r --include-dependencies

如果你是window环境下开发,你需要下载zip压缩文件,并通过执行 install.rb 来安装。

SOAP4R 服务
SOAP4R 支持两种不同的服务类型:

  • 基于 CGI/FastCGI 服务 (SOAP::RPC::CGIStub)
  • 独立服务 (SOAP::RPC:StandaloneServer)

本教程将为大家介绍如何建立独立的 SOAP 服务。步骤如下:

第1步 - 继承SOAP::RPC::StandaloneServer
为了实现自己的独立的服务器,你需要编写一个新的类,该类为 SOAP::RPC::StandaloneServer 的子类:

class MyServer < SOAP::RPC::StandaloneServer...............
end

注意:如果你要编写一个基于FastCGI的服务器,那么你需要继承 SOAP::RPC::CGIStub 类,程序的其余部分将保持不变。

第二步 - 定义处理方法
接下来我们定义Web Service的方法,如下我们定义两个方法,一个是两个数相加,一个是两个数相除:

class MyServer < SOAP::RPC::StandaloneServer...............# 处理方法def add(a, b)return a + benddef div(a, b) return a / b end
end

第三步 - 公布处理方法
接下来添加我们在服务器上定义的方法,initialize方法是公开的,用于外部的连接:

class MyServer < SOAP::RPC::StandaloneServerdef initialize(*args)add_method(receiver, methodName, *paramArg)end
end

以下是各参数的说明:

参数 描述
receiver 包含方法名的方法的对象。 如果你在同一个类中定义服务方法,该参数为 self。
methodName 调用 RPC 请求的方法名。
paramArg 参数名和参数模式

为了理解 inout 和 out 参数,考虑以下服务方法,需要输入两个参数:inParam 和 inoutParam,函数执行完成后返回三个值:retVal、inoutParam 、outParam:

def aMeth(inParam, inoutParam)retVal = inParam + inoutParamoutParam = inParam . inoutParaminoutParam = inParam * inoutParamreturn retVal, inoutParam, outParam
end

公开的调用方法如下:

add_method(self, 'aMeth', [%w(in inParam),%w(inout inoutParam),%w(out outParam),%w(retval return)
])

第四步 - 开启服务
最后我们通过实例化派生类,并调用 start 方法来启动服务:

myServer = MyServer.new('ServerName','urn:ruby:ServiceName', hostname, port)myServer.start

以下是请求参数的说明:

参数 描述
ServerName 服务名,你可以取你喜欢的
urn:ruby:ServiceName Here urn:ruby 是固定的,但是你可以为你的服务取一个唯一的 ServiceName
hostname 指定主机名
port web 服务端口

接下来我们通过以上的步骤,创建一个独立的服务:

实例

require "soap/rpc/standaloneserver"beginclass MyServer < SOAP::RPC::StandaloneServer# Expose our servicedef initialize(*args)add_method(self, 'add', 'a', 'b')add_method(self, 'div', 'a', 'b')end# Handler methodsdef add(a, b)return a + benddef div(a, b) return a / b endendserver = MyServer.new("MyServer", 'urn:ruby:calculation', 'localhost', 8080)trap('INT){server.shutdown}server.start
rescue => errputs err.message
end

执行以上程序后,就启动了一个监听 8080 端口的本地服务,并公开两个方法:add 和 div。

你可以再后台执行以上服务:

$ ruby MyServer.rb &

SOAP4R 客户端
ruby 中使用 SOAP::RPC::Driver 类开发 SOAP 客户端。接下来我们来详细看下 SOAP::RPC::Driver 类的使用。

调用 SOAP 服务需要以下信息:

  • SOAP 服务 URL 地址 (SOAP Endpoint URL)
  • 服务方法的命名空间(Method Namespace URI)
  • 服务方法名及参数信息

接下来我们就一步步来创建 SOAP 客户端来调用以上的 SOAP 方法:add 、 div:

  • 第一步 - 创建 SOAP Driver 实例

我们可以通过实例化 SOAP::RPC::Driver 类来调用它的新方法,如下所示:

SOAP::RPC::Driver.new(endPoint, nameSpace, soapAction)

以下是参数的描述:

参数 描述
endPoint 连接 SOAP 服务的 URL 地址
nameSpace 命名空间用于 SOAP::RPC::Driver 对象的所有 RPC .
soapAction 用于 HTTP 头部的 SOAPAction 字段值。如果是字符串是"" 则默认为 nil

第二步 - 添加服务方法
为 SOAP::RPC::Driver 添加 SOAP 服务方法,我们可以通过实例 SOAP::RPC::Driver 来调用以下方法:

driver.add_method(name, *paramArg)

以下是参数的说明:

参数 描述
name 远程web服务的方法名
paramArg 指定远程程序的参数

第三步 - 调用SOAP服务
最后我们可以使用 SOAP::RPC::Driver 实例来调用 SOAP 服务:

result = driver.serviceMethod(paramArg...)

serviceMethod SOAP服务的实际方法名,paramArg为方法的参数列表。

基于以上的步骤,我们可以编写以下的 SOAP 客户端:

实例

#!/usr/bin/ruby -wrequire 'soap/rpc/driver'NAMESPACE = 'urn:ruby:calculation'
URL = 'http://localhost:8080/'begindriver = SOAP::RPC::Driver.new(URL, NAMESPACE)# Add remote sevice methodsdriver.add_method('add', 'a', 'b')# Call remote service methodsputs driver.add(20, 30)
rescue => errputs err.message
end

Ruby 多线程

每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。

线程是程序中一个单一的顺序控制流程,在单个程序中同时运行多个线程完成不同的工作,称为多线程。

Ruby 中我们可以通过 Thread 类来创建多线程,Ruby的线程是一个轻量级的,可以以高效的方式来实现并行的代码。

创建 Ruby 线程
要启动一个新的线程,只需要调用 Thread.new 即可:

# 线程 #1 代码部分
Thread.new {# 线程 #2 执行代码
}
# 线程 #1 执行代码

实例
以下实例展示了如何在Ruby程序中使用多线程:

#!/usr/bin/rubydef func1i=0while i<=2puts "func1 at: #{Time.now}"sleep(2)i=i+1end
enddef func2j=0while j<=2puts "func2 at: #{Time.now}"sleep(1)j=j+1end
endputs "Started At #{Time.now}"
t1=Thread.new{func1()}
t2=Thread.new{func2()}
t1.join
t2.join
puts "End at #{Time.now}"

以上代码执行结果为:

Started At Wed May 14 08:21:54 -0700 2014
func1 at: Wed May 14 08:21:54 -0700 2014
func2 at: Wed May 14 08:21:54 -0700 2014
func2 at: Wed May 14 08:21:55 -0700 2014
func1 at: Wed May 14 08:21:56 -0700 2014
func2 at: Wed May 14 08:21:56 -0700 2014
func1 at: Wed May 14 08:21:58 -0700 2014
End at Wed May 14 08:22:00 -0700 2014

线程生命周期
1、线程的创建可以使用Thread.new,同样可以以同样的语法使用Thread.start 或者Thread.fork这三个方法来创建线程。

2、创建线程后无需启动,线程会自动执行。

3、Thread 类定义了一些方法来操控线程。线程执行Thread.new中的代码块。

4、线程代码块中最后一个语句是线程的值,可以通过线程的方法来调用,如果线程执行完毕,则返回线程值,否则不返回值直到线程执行完毕。

5、Thread.current 方法返回表示当前线程的对象。 Thread.main 方法返回主线程。

6、通过 Thread.Join 方法来执行线程,这个方法会挂起主线程,直到当前线程执行完毕。

线程状态
线程有5种状态:

线程状态 返回值
可执行 run
睡眠 Sleeping
退出 aborting
正常终止 false
发生异常终止 nil

线程和异常
当某线程发生异常,且没有被rescue捕捉到时,该线程通常会被无警告地终止。但是,若有其它线程因为Thread#join的关系一直等待该线程的话,则等待的线程同样会被引发相同的异常。

begint = Thread.new doThread.pass    # 主线程确实在等joinraise "unhandled exception"endt.join
rescuep $!  # => "unhandled exception"
end

使用下列3个方法,就可以让解释器在某个线程因异常而终止时中断运行。

  • 启动脚本时指定-d选项,并以调试模时运行。
  • 用Thread.abort_on_exception设置标志。
  • 使用Thread#abort_on_exception对指定的线程设定标志。

当使用上述3种方法之一后,整个解释器就会被中断。

t = Thread.new { ... }
t.abort_on_exception = true

线程同步控制
在Ruby中,提供三种实现同步的方式,分别是:

  1. 通过Mutex类实现线程同步

  2. 监管数据交接的Queue类实现线程同步

  3. 使用ConditionVariable实现同步控制

通过Mutex类实现线程同步
通过Mutex类实现线程同步控制,如果在多个线程钟同时需要一个程序变量,可以将这个变量部分使用lock锁定。 代码如下:

实例

#!/usr/bin/rubyrequire "thread"
puts "Synchronize Thread"@num=200
@mutex=Mutex.newdef buyTicket(num)@mutex.lockif @num>=num@num=@num-numputs "you have successfully bought #{num} tickets"elseputs "sorry,no enough tickets"end@mutex.unlock
endticket1=Thread.new 10 do10.times do |value|ticketNum=15buyTicket(ticketNum)sleep 0.01end
endticket2=Thread.new 10 do10.times do |value|ticketNum=20buyTicket(ticketNum)sleep 0.01end
endsleep 1
ticket1.join
ticket2.join

以上代码执行结果为:

Synchronize Thread
you have successfully bought 15 tickets
you have successfully bought 20 tickets
you have successfully bought 15 tickets
you have successfully bought 20 tickets
you have successfully bought 15 tickets
you have successfully bought 20 tickets
you have successfully bought 15 tickets
you have successfully bought 20 tickets
you have successfully bought 15 tickets
you have successfully bought 20 tickets
you have successfully bought 15 tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets

除了使用lock锁定变量,还可以使用try_lock锁定变量,还可以使用Mutex.synchronize同步对某一个变量的访问。

监管数据交接的Queue类实现线程同步
Queue类就是表示一个支持线程的队列,能够同步对队列末尾进行访问。不同的线程可以使用统一个对类,但是不用担心这个队列中的数据是否能够同步,另外使用SizedQueue类能够限制队列的长度

SizedQueue类能够非常便捷的帮助我们开发线程同步的应用程序,应为只要加入到这个队列中,就不用关心线程的同步问题。

经典的生产者消费者问题:

实例

#!/usr/bin/rubyrequire "thread"
puts "SizedQuee Test"queue = Queue.newproducer = Thread.new do10.times do |i|sleep rand(i) # 让线程睡眠一段时间queue << iputs "#{i} produced"end
endconsumer = Thread.new do10.times do |i|value = queue.popsleep rand(i/2)puts "consumed #{value}"end
endconsumer.join

程序的输出:

SizedQuee Test
0 produced
1 produced
consumed 0
2 produced
consumed 1
consumed 2
3 produced
consumed 34 producedconsumed 4
5 produced
consumed 5
6 produced
consumed 6
7 produced
consumed 7
8 produced
9 produced
consumed 8
consumed 9

线程变量
线程可以有其私有变量,线程的私有变量在线程创建的时候写入线程。可以被线程范围内使用,但是不能被线程外部进行共享。

但是有时候,线程的局部变量需要别别的线程或者主线程访问怎么办?ruby当中提供了允许通过名字来创建线程变量,类似的把线程看做hash式的散列表。通过[]=写入并通过[]读出数据。我们来看一下下面的代码:

实例

#!/usr/bin/rubycount = 0
arr = []10.times do |i|arr[i] = Thread.new {sleep(rand(0)/10.0)Thread.current["mycount"] = countcount += 1}
endarr.each {|t| t.join; print t["mycount"], ", " }
puts "count = #{count}"

以上代码运行输出结果为:

8, 0, 3, 7, 2, 1, 6, 5, 4, 9, count = 10

主线程等待子线程执行完成,然后分别输出每个值。 。

线程优先级
线程的优先级是影响线程的调度的主要因素。其他因素包括占用CPU的执行时间长短,线程分组调度等等。

可以使用 Thread.priority 方法得到线程的优先级和使用 Thread.priority= 方法来调整线程的优先级。

线程的优先级默认为 0 。 优先级较高的执行的要快。

一个 Thread 可以访问自己作用域内的所有数据,但如果有需要在某个线程内访问其他线程的数据应该怎么做呢? Thread 类提供了线程数据互相访问的方法,你可以简单的把一个线程作为一个 Hash 表,可以在任何线程内使用 []= 写入数据,使用 [] 读出数据。

athr = Thread.new { Thread.current["name"] = "Thread A"; Thread.stop }
bthr = Thread.new { Thread.current["name"] = "Thread B"; Thread.stop }
cthr = Thread.new { Thread.current["name"] = "Thread C"; Thread.stop }
Thread.list.each {|x| puts "#{x.inspect}: #{x["name"]}" }

可以看到,把线程作为一个 Hash 表,使用 [] 和 []= 方法,我们实现了线程之间的数据共享。

线程互斥
Mutex(Mutal Exclusion = 互斥锁)是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。

不使用Mutax的实例
实例

#!/usr/bin/ruby
require 'thread'count1 = count2 = 0
difference = 0
counter = Thread.new doloop docount1 += 1count2 += 1end
end
spy = Thread.new doloop dodifference += (count1 - count2).absend
end
sleep 1
puts "count1 :  #{count1}"
puts "count2 :  #{count2}"
puts "difference : #{difference}"

以上实例运行输出结果为:

count1 :  9712487
count2 :  12501239
difference : 0

使用 mutex 的实例
实例

#!/usr/bin/ruby
require 'thread'
mutex = Mutex.newcount1 = count2 = 0
difference = 0
counter = Thread.new doloop domutex.synchronize docount1 += 1count2 += 1endend
end
spy = Thread.new doloop domutex.synchronize dodifference += (count1 - count2).absendend
end
sleep 1
mutex.lock
puts "count1 :  #{count1}"
puts "count2 :  #{count2}"
puts "difference : #{difference}"

以上实例运行输出结果为:

count1 :  1336406
count2 :  1336406
difference : 0

死锁
两个以上的运算单元,双方都在等待对方停止运行,以获取系统资源,但是没有一方提前退出时,这种状况,就称为死锁。

例如,一个进程 p1占用了显示器,同时又必须使用打印机,而打印机被进程p2占用,p2又必须使用显示器,这样就形成了死锁。

当我们在使用 Mutex 对象时需要注意线程死锁。

实例

#!/usr/bin/ruby
require 'thread'
mutex = Mutex.newcv = ConditionVariable.new
a = Thread.new {mutex.synchronize {puts "A: I have critical section, but will wait for cv"cv.wait(mutex)puts "A: I have critical section again! I rule!"}
}puts "(Later, back at the ranch...)"b = Thread.new {mutex.synchronize {puts "B: Now I am critical, but am done with cv"cv.signalputs "B: I am still critical, finishing up"}
}
a.join
b.join

以上实例输出结果为:

A: I have critical section, but will wait for cv
(Later, back at the ranch...)
B: Now I am critical, but am done with cv
B: I am still critical, finishing up
A: I have critical section again! I rule!

线程类方法
完整的 Thread(线程) 类方法如下:

序号 方法描述
1 Thread.abort_on_exception
若其值为真的话,一旦某线程因异常而终止时,整个解释器就会被中断。它的默认值是假,也就是说,在通常情况下,若某线程发生异常且该异常未被Thread#join等检测到时,该线程会被无警告地终止。
2 Thread.abort_on_exception=
如果设置为 true, 一旦某线程因异常而终止时,整个解释器就会被中断。返回新的状态
3 Thread.critical
返回布尔值。
4 Thread.critical=
当其值为true时,将不会进行线程切换。若当前线程挂起(stop)或有信号(signal)干预时,其值将自动变为false。
5 Thread.current
返回当前运行中的线程(当前线程)。
6 Thread.exit
终止当前线程的运行。返回当前线程。若当前线程是唯一的一个线程时,将使用exit(0)来终止它的运行。
7 Thread.fork { block }
与 Thread.new 一样生成线程。
8 Thread.kill( aThread )
终止线程的运行.
9 Thread.list
返回处于运行状态或挂起状态的活线程的数组。
10 Thread.main
返回主线程。
11 Thread.new( [ arg ]* ) {| args | block }
生成线程,并开始执行。数会被原封不动地传递给块. 这就可以在启动线程的同时,将值传递给该线程所固有的局部变量。
12 Thread.pass
将运行权交给其他线程. 它不会改变运行中的线程的状态,而是将控制权交给其他可运行的线程(显式的线程调度)。
13 Thread.start( [ args ]* ) {| args | block }
生成线程,并开始执行。数会被原封不动地传递给块. 这就可以在启动线程的同时,将值传递给该线程所固有的局部变量。
14 Thread.stop
将当前线程挂起,直到其他线程使用run方法再次唤醒该线程。

线程实例化方法
以下实例调用了线程实例化方法 join:

实例

#!/usr/bin/rubythr = Thread.new do   # 实例化puts "In second thread"raise "Raise exception"
end
thr.join   # 调用实例化方法 join

以下是完整实例化方法列表:

序号 方法描述
1 thr[ name ]
取出线程内与name相对应的固有数据。 name可以是字符串或符号。 若没有与name相对应的数据时, 返回nil。
2 thr[ name ] =
设置线程内name相对应的固有数据的值, name可以是字符串或符号。 若设为nil时, 将删除该线程内对应数据。
3 thr.abort_on_exception
返回布尔值。
4 thr.abort_on_exception=
若其值为true的话,一旦某线程因异常而终止时,整个解释器就会被中断。
5 thr.alive?
若线程是"活"的,就返回true。
6 thr.exit
终止线程的运行。返回self。
7 thr.join
挂起当前线程,直到self线程终止运行为止. 若self因异常而终止时, 将会当前线程引发同样的异常。
8 thr.key?
若与name相对应的线程固有数据已经被定义的话,就返回true
9 thr.kill
类似于 Thread.exit 。
10 thr.priority
返回线程的优先度. 优先度的默认值为0. 该值越大则优先度越高.
11 thr.priority=
设定线程的优先度. 也可以将其设定为负数.
12 thr.raise( anException )
在该线程内强行引发异常.
13 thr.run
重新启动被挂起(stop)的线程. 与wakeup不同的是,它将立即进行线程的切换. 若对死进程使用该方法时, 将引发ThreadError异常.
14 thr.safe_level
返回self 的安全等级. 当前线程的safe_level与$SAFE相同.
15 thr.status
使用字符串"run"、“sleep"或"aborting” 来表示活线程的状态. 若某线程是正常终止的话,就返回false. 若因异常而终止的话,就返回nil。
16 thr.stop?
若线程处于终止状态(dead)或被挂起(stop)时,返回true.
17 thr.value
一直等到self线程终止运行(等同于join)后,返回该线程的块的返回值. 若在线程的运行过程中发生了异常, 就会再次引发该异常.
18 thr.wakeup
把被挂起(stop)的线程的状态改为可执行状态(run), 若对死线程执行该方法时,将会引发ThreadError异常。