Home > Software engineering >  How do you send multiple objects using pickle using socket?
How do you send multiple objects using pickle using socket?

Time:09-15

#SERVER
import socket
from _thread import *
from plyer import Player, FootBall
import pickle


class GameServer:
    def __init__(self):
        self.Server = '127.0.0.1'
        self.Port = 52674
        self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.PlayerId = 0
        st1 = Player(630, 375, (255, 0, 0), 'Arsenal', 'ST')
        lw1 = Player(900, 300, (255, 0, 0), 'Arsenal', 'CB')
        rw1 = Player(370, 300, (255, 0, 0), 'Arsenal', 'RW')
        st2 = Player(630, 300, (0, 255, 0), 'Spurs', 'ST')
        lw2 = Player(370, 425, (0, 255, 0), 'Spurs', 'CB')
        rw2 = Player(900, 425, (0, 255, 0), 'Spurs', 'RW')
        self.football = FootBall(630, 375) <-- Object I wish to send to both players
        self.Player1_slot = st1
        self.Player2_slot = st2

        self.p = [self.Player1_slot, self.Player2_slot]  # Players that are currently being used

    def Bind(self):
        # Connects the client to the server
        try:
            self.s.bind((self.Server, self.Port))
        except socket.error:
            print("Error")

        # Listens to maximum 2 players only
        self.s.listen(2)
        print("Waiting for a connection...")

    def ReturnSocket(self):
        return self.s

    def ThreadedClient(self, conn, curr_player):
        conn.send(pickle.dumps(self.p[curr_player])) <--- Objects I am currently able to send to both players
        #conn.send(pickle.dumps(self.football))

        while True:
            # Continuously runs when the client is connected
            try:
                pos_data = pickle.loads(conn.recv(2048))
                self.p[curr_player] = pos_data

                if not pos_data:
                    print("Player Left")
                    break
                else:
                    if curr_player == 1:
                        reply = self.p[0]
                    else:
                        reply = self.p[1]

                    reply2 = self.football

                    print("[PLAYER] Received: ", pos_data)
                    print("[BALL] Received", reply2)
                    print("[PLAYER] Sending: ", reply)

                conn.sendall(pickle.dumps(reply))
                #conn.sendall(pickle.dumps(reply2))
            except:
                break

        print("Connection Lost")
        conn.close()


def main():
    GS = GameServer()
    GS.Bind()
    while True:
        s = GS.ReturnSocket()
        # Continuously looking for connections
        conn, addr = s.accept()
        print(addr, "has joined the game")

        # Function runs in the background
        start_new_thread(GS.ThreadedClient, (conn, GS.PlayerId))
        GS.PlayerId  = 1


if __name__ == '__main__':
    main()

#CLIENT
import socket
import pickle


class Network:
    def __init__(self):
        self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.host = '127.0.0.1'
        self.port = 52674
        self.addr = (self.host, self.port)
        self.p = self.Connect()
        print(self.p)

    def ReturnP(self):
        return self.p

    def Connect(self):
        try:
            self.client.connect(self.addr)
            return pickle.loads(self.client.recv(4096))
        except:
            pass

    def Send(self, data):
        try:
            print(data)
            self.client.send(pickle.dumps(data))
            reply = pickle.loads(self.client.recv(4096))
            return reply
        except socket.error as e:
            return str(e)

I would like to send the self.football class to a client alongside with the current classes I have already sent. I have tried doing sendall.pickle.dumps(self.football) however the football does not send. How can I make it so that when it is sent it is separate to the other classes. Thank you in advanced

CodePudding user response:

If you wrap the socket in a file-like object via socket.makefile, you can read pickled objects directly from the socket. Here's an example:

import socket
import threading
import pickle

class Football:
    def __init__(self, a, b):
        self.a = a
        self.b = b
    def __repr__(self):
        return f'Football(a={self.a}, b={self.b})'

class Player:
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return f'Player(name={self.name!r})'

def server():
    s = socket.socket()
    s.bind(('', 5000))
    s.listen()
    with s:
        c, a = s.accept()
        print(f'{a}: connected')
        with c, c.makefile('rb') as r: # make a file-like object to be used directly by pickle
            while True:
                try:
                    obj = pickle.load(r)
                except EOFError: # caused by client close
                    break
                print(obj)
            print(f'{a}: disconnected')

threading.Thread(target=server).start()

# client

s = socket.socket()
s.connect(('localhost', 5000))
# buffering=0 sends pickle.dump immediately,
# otherwise need w.flush() or with exit to ensure it is sent
with s, s.makefile('wb', buffering=0) as w:
    f = Football(1,2)
    p1 = Player('Mark')
    p2 = Player('Joe')
    pickle.dump(f, w)
    pickle.dump(p1, w)
    pickle.dump(p2, w)

Output:

('127.0.0.1', 28974): connected
Football(a=1, b=2)
Player(name='Mark')
Player(name='Joe')
('127.0.0.1', 28974): disconnected

CodePudding user response:

You cannot assume the recv(2048) will read the entire pickled object. TCP does not respect message boundaries.

There are other ways to handle this, but it's often done by prefacing each chunk of data with a fixed-length field providing the length of the variable length field. In other words, call pickle.dumps, find the length of the resulting data, send a 32-bit (say) binary field containing that length (in python, that usually involves the struct module), then send the pickled data.

On the receiving side, first read the 32-bit field, then read as many bytes as it tells you, ensuring you loop and continue receiving until you have the entire thing. Only then pickle.loads on it.

Note that recv(2048) receives up to 2048 bytes. But it may also return just 1 or 10 or 200 bytes -- depending on how much data is available in the local OS buffer at that instant.

  • Related