Cannot send class instances over the network

173 views
Skip to first unread message

Or

unread,
Apr 29, 2010, 2:27:16 PM4/29/10
to PodSixNet
I've tried this lib for the first time today, yet I can't get it to
work.
I've followed the examples, and everything seems find until I try
sending a class instance over a Channel object.

I've got code similar to this:
class Tile(object):
def __init__(self):
self.x = 2

.....

Eventually, in the server:

player.Send(
{
"action": "init",
"board": Tile()
}

this fails with

error: uncaptured python exception, closing channel
<__main__.PyWalkServer listening localhost:6002 at 0x25d9d40> (<type
'exceptions.KeyError'>:<class 'tile.Tile'> [/usr/lib/python2.6/
asyncore.py|read|76] [/usr/lib/python2.6/asyncore.py|handle_read_event|
411]
-- snip.. --
[/home/or/Programming/Python/Eclipse/PyWalk/src/PodSixNet/rencode.py|
encode_dict|472]

The problem indeed lies in the function encode_dict in rencode.py,
which simply doesn't support class instances as dictionary values (The
specific line is 472, compared it to lines like 460, 448)

As a workaround I wrapped my object in a tuple, and now I'm receiving
the following error in the client:
... Snip of my code...
File "/home/or/Programming/Python/Eclipse/PyWalk/src/PodSixNet/
Connection.py", line 29, in Pump
[getattr(self, n)(data) for n in ("Network_" + data['action'],
"Network") if hasattr(self, n)]
File "/home/or/Programming/Python/Eclipse/PyWalk/src/listener.py",
line 31, in Network_error
raise Exception(data["error"])
Exception: string index out of range

It seems to happen in asyncore.py, in the function read().
Seems like it can't read the instance data sent over the pipe.

Is there any way to send and receive class instances?

--
You received this message because you are subscribed to the Google Groups "PodSixNet" group.
To post to this group, send email to pods...@googlegroups.com.
To unsubscribe from this group, send email to podsixnet+...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/podsixnet?hl=en.

Or

unread,
Apr 30, 2010, 6:21:36 AM4/30/10
to PodSixNet
I can answer my own question.
Here's what needed to send and receive class instances:
1) Designate what attributes of your class should be sent over using
the __init__ method, and adding a _pack method:
before:

class Tile(object):
def __init__(self):
self.x = 2

after:

class Tile(object):
def __init__(self, x=2):
self.x = x

def _pack(self):
return (self.x, ) # Must be a tuple

The attributes must be in the same order in the tuple received by
_pack and in the __init__ parameter list.

2) Somewhere in your code, do the following:
from rencode import serializable
serializable.register(Tile)

3) Send the instance over the PodSixNet Channel object as normal, just
remember to wrap it in a tuple or list (See last post)
player.Send(
{
"action": "init",
"board": (tile_instance,)
}


I suggest adding a decorator for step 2, and documenting the process.
> You received this message because you are subscribed to the Google Groups "PodSixNet" group.To post to this group, send email to pods...@googlegroups.com.To unsubscribe from this group, send email to podsixnet+...@googlegroups.com.

Bruce Smith

unread,
Apr 30, 2010, 3:39:27 PM4/30/10
to pods...@googlegroups.com

IIRC, it was documented somewhere, though maybe only in rencode docstrings. (I forget the details; it was awhile ago when I used PodSixNet in a local project.)

FYI, also IIRC, registration of encodable classes is only required on the sending side. As a suggested change to PodSixNet: for security and robustness I suggest registration should also be required on the receiving side (sorry if it already is and I'm misremembering!); and for flexibility (in upgrading receiving code and preserving compatibility with old sending code), the receiving side should be allowed to specify an alternate constructor for any class it permits itself to receive.

- Bruce Smith (another PodSixNet user)

chrism

unread,
May 1, 2010, 11:14:36 PM5/1/10
to pods...@googlegroups.com
Hi Or,

On Thu, 29 Apr 2010 11:27:16 -0700 (PDT), Or <or.z...@gmail.com> wrote:
> I've tried this lib for the first time today, yet I can't get it to
> work.
> I've followed the examples, and everything seems find until I try
> sending a class instance over a Channel object.
>
> I've got code similar to this:
> class Tile(object):
> def __init__(self):
> self.x = 2
>
> .....
>
> Eventually, in the server:
>
> player.Send(
> {
> "action": "init",
> "board": Tile()
> }
>
> this fails with
>
> The problem indeed lies in the function encode_dict in rencode.py,
> which simply doesn't support class instances as dictionary values (The
> specific line is 472, compared it to lines like 460, 448)
>
> Seems like it can't read the instance data sent over the pipe.
>
> Is there any way to send and receive class instances?

The underlying encoding algorithm, rencode.py does not do a pickling type
of thing that you need. So there are a few ways to get around that and
"send" your class instance across the network. A basic way do this if your
class
is very simple and only contains Python data types, is to send
tileinstance.__dict__ over the network instead of the Tile() object itself
(where tileinstance = Tile() for example). On the other side you can
initialise a new Tile() object from the received data by doing something
like tileinstance.__dict__ = received_data. This can be suboptimal though,
depending on the structure of your class and what's in it.

If you want more control over exactly what is sent, you could create a
method in Tile() called e.g. network_repr() and from_network_repr(). Those
calls would return a network safe version of the important data inside
Tile() that you want to send, and re-initialise a Tile() instance with
given data on the other side. For example Tile.network_repr() might return
a structure looking like {"x": 5, "y": 10}, and Tile.from_network_data()
might initialise an instance of Tile() with x and y from that dict.

If you have a situation where Tile() also contains some other class like a
Bitmap() which has binary data or something and you want to make sure that
all elements of the class, including other instances of classes embedded
in
it, and their data get sent over the network, then you could try using
Python's cPickle to turn the class into a data-blob and send it as a
string. So you would Pickle() your whole Tile instance and the sent
structure would end up looking like {"action": "newtile", "Tile":
"PICKLED-DATA-BLOB"}. One problem with Pickle is that unpickling pickle
data can be a security risk as someone can inject all kinds of Python code
over the network by modifying the client.

Hope this helps!

Best,

Chris.

-------------------
http://mccormick.cx
Reply all
Reply to author
Forward
0 new messages