Home > OS >  How to send custom Python objects through sockets without having a reference to object's class
How to send custom Python objects through sockets without having a reference to object's class

Time:10-31

I am trying to send a custom Python object from a client to a server, but the additional challenge of not having the reference to the object's class in the server. If you have the class declaration in the server script, then it reconstructs the Python object correctly. Otherwise, it just fails to reconstruct.

I have tried using pickle and jsonpickle to send the Python object (you can select both options through the OPTION variable). Both methods do not work after commenting out the class declaration.

Server

from socket import socket, gethostbyname, AF_INET, SOCK_DGRAM
import pickle

import jsonpickle

OPTION = 'pickle'
PORT_NUMBER = 5000
SIZE = 4000

# Required (but I don't want this!)
# class A:
#     ...

s = socket(AF_INET, SOCK_DGRAM)
s.bind((gethostbyname('0.0.0.0'), PORT_NUMBER))

(data,addr) = s.recvfrom(SIZE)
print(data)

if OPTION == 'pickle':
    output = pickle.loads(data)
    print(output)

elif OPTION == 'jsonpickle':
    output = jsonpickle.decode(data)
    print(output)

Client

import sys
from socket import socket, AF_INET, SOCK_DGRAM
import pickle

import jsonpickle

OPTION = 'pickle'
SERVER_IP   = '127.0.0.1'
PORT_NUMBER = 5000

class A:
    ...

a = A()

s = socket(AF_INET, SOCK_DGRAM)
s.connect((SERVER_IP, PORT_NUMBER))

if OPTION == 'pickle':
    s.send(pickle.dumps(a))
elif OPTION == 'jsonpickle':
    s.send(jsonpickle.encode(a).encode())

Outputs

These are the outputs when class A is in the server:

$ python server.py # jsonpickle
b'{"py/object": "__main__.A"}'
<__main__.A object at 0x7f4557a675e0>
$ python server.py # pickle
b'\x80\x04\x95\x15\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x01A\x94\x93\x94)\x81\x94.'
<__main__.A object at 0x7fcab9fc9bb0>

These are the outputs when class A is not part of the server:

$ python server.py # pickle
b'\x80\x04\x95\x15\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x01A\x94\x93\x94)\x81\x94.'
Traceback (most recent call last):
  File "/home/eduardo/Downloads/SO_Question/server.py", line 21, in <module>
    output = pickle.loads(data)
AttributeError: Can't get attribute 'A' on <module '__main__' from '/home/eduardo/Downloads/SO_Questi
on/server.py'>
$ python server.py # jsonpickle
b'{"py/object": "__main__.A"}'
{'py/object': '__main__.A'}

I looked into other approaches, like Pyro, but I am not too sure if these will work for my use case. Maybe ther are other tools that could help here. This server-client example is just a toy problem to a larger project I am working on: ChimeraPy. The goal would be for a server to distribute custom and unique code to clients. By using clients' computational resources to execute the custom code, using proxies, like Pyro, are not ideal as they don't distribute the computational load the same way as a cluster does.

Any help would be greatly appreciated. Thanks!

Tried:

Hoping:

  • To find a solution for sending custom Python objects without reference in the other side of the socket

CodePudding user response:

Using the recommend links 1 and 2 by @metatoaster, I was able to achieve the desired behavior:

Server

from socket import socket, gethostbyname, AF_INET, SOCK_DGRAM
import pickle
import dill

import jsonpickle

OPTION = 'dill'
PORT_NUMBER = 5000
SIZE = 4000

# Required (but I don't want this!)
# class A:
#     ...

s = socket(AF_INET, SOCK_DGRAM)
s.bind((gethostbyname('0.0.0.0'), PORT_NUMBER))

(data,addr) = s.recvfrom(SIZE)
print(data)

if OPTION == 'pickle':
    output = pickle.loads(data)
    print(output)

elif OPTION == 'jsonpickle':
    output = jsonpickle.decode(data)
    print(output)

elif OPTION == 'dill':
    output = dill.loads(data)
    print(output)
    print(output.multiple(3))

Client

import sys
from socket import socket, AF_INET, SOCK_DGRAM
import pickle

import jsonpickle
import dill

OPTION = 'dill'
SERVER_IP   = '127.0.0.1'
PORT_NUMBER = 5000

class A:
    def __init__(self):
        self.num = 2

    def multiple(self, n):
        return self.num * n

a = A()

s = socket(AF_INET, SOCK_DGRAM)
s.connect((SERVER_IP, PORT_NUMBER))

if OPTION == 'pickle':
    s.send(pickle.dumps(a))
elif OPTION == 'jsonpickle':
    s.send(jsonpickle.encode(a).encode())
elif OPTION == 'dill':
    s.send(dill.dumps(a))

After running server.py and client.py, I get the following output:

$ python server.py 
b'\x80\x04\x95\x17\x02\x00\x00\x00\x00\x00\x00\x8c\ndill._dill\x94\x8c\x0c_create_type\x94\x93\x94(h\
x00\x8c\n_load_type\x94\x93\x94\x8c\x04type\x94\x85\x94R\x94\x8c\x01A\x94h\x04\x8c\x06object\x94\x85\
x94R\x94\x85\x94}\x94(\x8c\n__module__\x94\x8c\x08__main__\x94\x8c\x08__init__\x94h\x00\x8c\x10_creat
e_function\x94\x93\x94(h\x00\x8c\x0c_create_code\x94\x93\x94(K\x01K\x00K\x00K\x01K\x02KCC\nd\x01|\x00
_\x00d\x00S\x00\x94NK\x02\x86\x94\x8c\x03num\x94\x85\x94\x8c\x04self\x94\x85\x94\x8c-/home/eduardo/Do
wnloads/SO_Question/client.py\x94h\x10K\rC\x02\x00\x01\x94))t\x94R\x94c__builtin__\n__main__\nh\x10NN
t\x94R\x94}\x94}\x94(\x8c\x0f__annotations__\x94}\x94\x8c\x0c__qualname__\x94\x8c\nA.__init__\x94u\x8
6\x94b\x8c\x08multiple\x94h\x12(h\x14(K\x02K\x00K\x00K\x02K\x02KCC\n|\x00j\x00|\x01\x14\x00S\x00\x94N
\x85\x94h\x18h\x19\x8c\x01n\x94\x86\x94h\x1bh(K\x10C\x02\x00\x01\x94))t\x94R\x94c__builtin__\n__main_
_\nh(NNt\x94R\x94}\x94}\x94(h#}\x94h%\x8c\nA.multiple\x94u\x86\x94b\x8c\x07__doc__\x94N\x8c\r__slotna
mes__\x94]\x94ut\x94R\x94)\x81\x94}\x94h\x17K\x02sb.'
<__main__.A object at 0x7fe8ec0656d0>
6

Success! Thanks for the help!

  • Related