#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.