2013-12-15
说白了,远程调用就是将对象名、函数名、参数等传递给远程服务器,服务器将处理结果返回给客户端。远程调用使得调用远程服务器的对象、方法的方式就和调用本地对象、方法的方式差不多,因为我们通过网络编程把这些都隐藏起来了。远程调用是分布式系统的基础。
远程调用一般分为两种,远程过程调用(RPC)和远程方法调用(RMI)。
RPC
RPC属于函数级别的远程调用,其多是通过HTTP传输数据,数据形式有XML、JSON、序列化数据等。在此,用python做一个xml-rpc的示例。 先给服务器端server.py:
from SimpleXMLRPCServer import SimpleXMLRPCServer
def add(x, y):
return x + y
if __name__ == '__main__':
s = SimpleXMLRPCServer(('127.0.0.1', 8080))
s.register_function(add)
s.serve_forever()
s是一个绑定了本地8080端口的服务器对象,register_function()
方法将函数add注册到s中。serve_forever()
启动服务器。 再给个客户端client.py:
from xmlrpclib import ServerProxy
if __name__ == '__main__':
s = ServerProxy("http://127.0.0.1:8080")
print s.add(3,4)
现在,运行server.py,然后运行client.py,client.py所在的console会输出7
。
我们用wireshark看一下这期间传递的数据是什么样子的,请求的数据:
<?xml version='1.0' ?>
<methodCall>
<methodName>
add
</methodName>
<params>
<param>
<value>
<int> 3 </int>
</value>
</param>
<param>
<value>
<int> 4 </int>
</value>
</param>
</params>
</methodCall>
响应的数据:
<?xml version='1.0' ?>
<methodResponse>
<params>
<param>
<value>
<int> 7 </int>
</value>
</param>
</params>
</methodResponse>
好吧,言简意赅,不做赘述。
RMI
RMI意为远程方法调用,粒度比RPC要大,因为它的基本单位是对象。其大致思路是这样的:创建RMI服务器对象,将实例化的某个对象以指定的服务名称(也可以是多个对象,但是服务名称不应相同)注册到RMI服务器对象中,之后启动RMI服务器。服务器等待客户端发送的数据(包括服务名称、函数名、参数),将处理结果返回给客户端。 Pyro4是一个基于python的RMI实现,下面我们用Pyro4创建一个RMI服务器,请看server2.py:
import Pyro4
class GreetingMaker(object):
def get_fortune(self, name):
return "Hello, {0}. \n" .format(name)
greeting_maker=GreetingMaker()
daemon=Pyro4.Daemon()
uri=daemon.register(greeting_maker)
print "Ready. Object uri =", uri
daemon.requestLoop()
uri变量是Pyro4用自己的方法为greeting_maker
对象生成的uri
,其中包括套接字以及为greeting_maker生成的唯一的id。这个id相当于服务名称,当然也可以指定更易懂的服务名称。下面是客户端client2.py:
import Pyro4
uri=raw_input(" Pyro uri : ").strip()
name=raw_input("Your name: ").strip()
greeting_maker=Pyro4.Proxy(uri)
print greeting_maker.get_fortune(name)
这其中要输入的uri也就是server2.py生成的uri。通过给Pyro4.Proxy传递greeting_maker
的uri
,可以认为和服务器端的greeting_maker建立的连接,然后调用greeting_maker
的get_fortune()
方法。如果name是letian,那么print greeting_maker.get_fortune(name)
的结果是Hello, letian.
。
那么,自己动手实现吧
其实这样做是不好的,多浪费生命,但是闲着也是闲着,自己动手实现了一个简单的RMI——liteRMI。liteRMI的客户端和服务器端之间传输的数据是字典变量的序列化。在请求数据中,键service
指定服务名称(与对象对应),键method
指定函数,键args
和键kwargs
是传递给函数的参数。返回数据中键error
用来说明服务器端是否正确处理请求,若处理正确(error==false)则客户端取出键result
中的数据,否则抛出异常。在这里,类中的__getattr__
方法发挥了很大的作用。若在类中定义了__getattr__
方法,当调用类的对象调用不存在的方法时候,__getattr__
会帮忙处理。
liteRMI的具体实现,请见:https://github.com/letiantian/liteRMI