Distribute already (or newly) implemented Python objects, allowing them to be remotely accessed without much hussle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | # Object adaptor for the client
import communication
from socket import *
class ObjectAdaptor(object):
def __init__(self, ip, port):
self.ip = ip
self.port = port
def send(self, *args):
communication.send(self.channel, args)
def receive(self):
return communication.receive(self.channel)
def remoteInvoke(self, fun, *args):
method = (fun.func_name, ) + args
self.channel = socket(AF_INET, SOCK_STREAM, 0)
self.channel.connect((self.ip, self.port))
self.send(*method)
result = self.receive()
self.channel.close()
return result[0]
# Object server for the actual object
import communication
import types
from socket import *
class ObjectServer(object):
def __init__(self, ip, port):
self.channel = socket(AF_INET, SOCK_STREAM, 0)
self.channel.bind((ip, port))
self.channel.listen(50)
self.info = self.channel.getsockname()
def send(self, client, *args):
communication.send(client, args)
def receive(self, client):
return communication.receive(client)
def getInfo(self):
return self.info
def dispatch(self, invoke, client):
dict = self.__class__.__dict__
method = invoke[0]
if (method in dict.keys() and type(dict[method]) == types.FunctionType):
method = dict[method]
params = invoke[1:]
result = method(self, *params)
self.send(client, result)
def start(self):
while (1):
client = self.channel.accept()[0]
invoke = self.receive(client)
self.dispatch(invoke, client)
# Communication code (the import communcation statements)
from socket import htonl, ntohl
import cPickle
import struct
marshall = cPickle.dumps
unmarshall = cPickle.loads
def send(channel, *args):
buf = marshall(args)
value = htonl(len(buf))
size = struct.pack("L", value)
channel.send(size)
channel.send(buf)
def receive(channel):
size = struct.calcsize("L")
size = channel.recv(size)
size = ntohl(struct.unpack("L", size)[0])
buf = ""
while len(buf) < size:
buf = channel.recv(size - len(buf))
return unmarshall(buf)[0]
# Echo server sample
class EchoServer(ObjectServer):
def __init__(self, ip, port):
ObjectServer.__init__(self, ip, port)
def echo(self, msg):
return "Message received: %s" % msg
es = EchoServer("127.0.0.1", 10000)
es.start()
# Echo client sample
class EchoClient(ObjectAdaptor):
def __init__(self, ip, port):
ObjectAdaptor.__init__(self, ip, port)
def echo(self, msg):
return self.remoteInvoke(self.echo, msg)
ec = EchoClient("127.0.0.1", 10000)
print ec.echo("Hello World!")
|
There are many alternatives for distributing objects in python (CORBA, Pyro, etc...), but sometimes a simple all-python solution can do the trick. This recipe implements the broker pattern and let's you distribute your objects with very little hussle.
Any object that must be accessed remotly must inherit ObjectServer. This object implements the core logic of receiving requests and dispatching them to the appropriate methods. It uses python`s reflexion to query the child object if a given method exists.
To access the remote object, a "client object" must inherit from ObjectAdaptor and define methods with the same names used by the remote object and call remoteInvoke with the parameters. As you can see, the first parameter is not the method name, but the method itself. Although passing the name would have been possible (as remoteInvoke uses only the name), this would make it easier to write the client object with method names diferent from the remote ones.
Nice. Nice and simple example!