Welcome, guest | Sign In | My Account | Store | Cart

Distribute already (or newly) implemented Python objects, allowing them to be remotely accessed without much hussle

Python, 102 lines
  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.

1 comment

Nicola Muneratto 15 years, 10 months ago  # | flag

Nice. Nice and simple example!