How do I prevent .recv() overflows or not overload the client with data. Python-pygame space game

106 views Asked by At

I'm attempting to build a client server game in Python for a Space Invaders for 2 players.

This might be bad practice, but I've used dictionaries to store the positions of the enemies and then use the server to store, update, and send positions to each player.

I send the enemy positions with JSON. I decided against Pickle as I read that it isn't too secure.

Is there a way which would let me somehow get all data without it being cutoff? As I don't think I can stop sending position in whole as the clients get out of sync? As with my server, I end up overloading the client with data that eventually the read buffer gets full and the processing of the data through the server makes it crash.

I use 2 threads on the client side (1 for send/recv each), 3 on the server (1 for each client, 1 to broadcast game updates).

server.py:

import socket
import time
from threading import Thread,Event

import json
import pygame
import math


server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

server_socket.bind((socket.gethostbyname(socket.gethostname()),7011))

server_socket.listen(2)

print("SERVER IS RUNNING")

connections = []
Threads = []
e = None

DIRECTION = 1
GAMESCREEN_CONSTRAINTS = [422,870]
START_POSITIONS = {"0": "434.0:226.0", "1": "464.0:226.0", "2": "494.0:226.0", "3": "524.0:226.0", "4": "554.0:226.0", "5": "584.0:226.0", "6": "614.0:226.0", "7": "644.0:226.0", "8": "674.0:226.0", "9": "704.0:226.0", "10": "434.0:256.0", "11": "464.0:256.0", "12": "494.0:256.0", "13": 
"524.0:256.0", "14": "554.0:256.0", "15": "584.0:256.0", "16": "614.0:256.0", "17": "644.0:256.0", "18": "674.0:256.0", "19": "704.0:256.0", "20": "434.0:286.0", 
"21": "464.0:286.0", "22": "494.0:286.0", "23": "524.0:286.0", "24": "554.0:286.0", "25": "584.0:286.0", "26": "614.0:286.0", "27": "644.0:286.0", "28": "674.0:286.0", "29": "704.0:286.0", "30": "434.0:316.0", "31": 
"464.0:316.0", "32": "494.0:316.0", "33": "524.0:316.0", "34": "554.0:316.0", "35": "584.0:316.0", "36": "614.0:316.0", "37": "644.0:316.0", "38": "674.0:316.0", 
"39": "704.0:316.0", "40": "434.0:346.0", "41": "464.0:346.0", "42": "494.0:346.0", "43": "524.0:346.0", "44": "554.0:346.0", "45": "584.0:346.0", "46": "614.0:346.0", "47": "644.0:346.0", "48": "674.0:346.0", "49": 
"704.0:346.0", "50": "434.0:376.0", "51": "464.0:376.0", "52": "494.0:376.0", "53": "524.0:376.0", "54": "554.0:376.0", "55": "584.0:376.0", "56": "614.0:376.0", 
"57": "644.0:376.0", "58": "674.0:376.0", "59": "704.0:376.0", "60": "434.0:406.0", "61": "464.0:406.0", "62": "494.0:406.0", "63": "524.0:406.0", "64": "554.0:406.0", "65": "584.0:406.0", "66": "614.0:406.0", "67": 
"644.0:406.0", "68": "674.0:406.0", "69": "704.0:406.0"}
Lasers = {}
LASER_INDEX = 0
PLAYER_POSITIONS = [[646.0,648.0],[646.0,648.0]]

def acceptClients():
    global e
    while len(connections) < 2:
        conn,addr = server_socket.accept()
        connections.append(conn)
        print("Connected to a Client on",addr)
        e = Event()
        Threads.append(Thread(target = runClient,args=(len(connections)-1,e)))
    Threads.append(Thread(target=alienThread,args=(e,)))

def runClient(connectionNo,e):
    connections[connectionNo].sendall(("START:" + str(connectionNo) + ",").encode())
    while not e.is_set():
        
        try:
            data = connections[connectionNo].recv(2048).decode().split("\n")[0]
        except ConnectionResetError:
            print("A Client disconnected")
            try:
                connections.pop(connectionNo)
            except IndexError:
                connections.pop(connectionNo - 1)
            print("Closing thread.")
            break
        else:
            if "SHOOT" in data:
                shootClient(connectionNo)
            elif "p:" in data:
                PLAYER_POSITIONS[connectionNo][0] = data.split("p:")[1]
                relay(connectionNo,data)

        # if not data:
        #     print("A Client disconnected")
        #     try:
        #         connections.pop(connectionNo)
        #     except IndexError:
        #         connections.pop(connectionNo - 1)
        #     print("Closing thread.")
        #     break
def shootClient(connectionNo):
    global LASER_INDEX,Lasers
    #Determine Spawnpoint For Laser Using Player Positions angle = 0
    center = pygame.Vector2()
    center.x = (PLAYER_POSITIONS[0])[0]
    center.y = (PLAYER_POSITIONS[0])[1]
    radius = 25/2
    spawnpoint = [center.x + (math.cos(((0*-1) -90)/180 * math.pi) * radius),center.y + (math.sin(((0*-1) -90)/180 * math.pi) * radius)]
    Lasers[LASER_INDEX] = [spawnpoint[0],spawnpoint[1]]
    LASER_INDEX += 1
    online_lasers = "k:" + jsonEncode(Lasers) + "\n"
    broadcast(online_lasers)

def broadcast(string_data):
    for connection in connections:
        connection.sendall(string_data.encode())

def jsonEncode(raw_data):
    json_string = json.dumps(raw_data)
    return json_string

def moveDown():
    for key in START_POSITIONS:
        positionx,positiony = START_POSITIONS[key].split(":")
        positiony = float(positiony) + 0.5
        START_POSITIONS.update({key:(str(positionx) + ":" + str(positiony))})

def checkCollisions():
    global DIRECTION
    for key in START_POSITIONS:
        if float(START_POSITIONS[key].split(":")[0]) - 10 <= GAMESCREEN_CONSTRAINTS[0] or float(START_POSITIONS[key].split(":")[0]) + 10 >= GAMESCREEN_CONSTRAINTS[1]:
            DIRECTION *= - 1
            moveDown()

def updateLasers():
    global Lasers
    for key in Lasers:
        x = (Lasers[key])[0]
        y = (Lasers[key])[1]
        y += 3
        Lasers[key] = [x,y]
        string = "l:" + jsonEncode(Lasers) + "\n"
        # broadcast(string)
def alienThread(e):
    global DIRECTION
    while not e.is_set():
        try:
            broadcast("a:" + jsonEncode(START_POSITIONS) + "\n")
        except ConnectionResetError:
            print("A-Thread ending.")
            break
        for key in START_POSITIONS:
            positionx,positiony = START_POSITIONS[key].split(":")
            positionx = float(positionx) + DIRECTION 
            START_POSITIONS.update({key:(str(positionx) + ":" + str(positiony))})
        checkCollisions()   
        updateLasers()
        time.sleep(1/(3*120))

def relay(connectionNo,data):
    # print("Client sent: " + data)
    if connectionNo - 1 == 0:
        connections[0].sendall((str(data) + "\n").encode())
    elif connectionNo == 0:
        connections[1].sendall((str(data) + "\n").encode())

def startThreads():
    for thread in Threads:
        thread.start()

def closeThreads():
    for thread in Threads:
        thread.join()
# def checkThreads():
#     global e
#     if len(Threads) < 2:
#         print("A Client has disconnected.")
#         e.set()
        

def main():
    acceptClients()
    startThreads()
    # checkThreads()
    closeThreads()

main()

and the main.py or the client side threads.

def send_online_input(self,e,sentflag):
        while not e.is_set():
            try:
                keys = pygame.key.get_pressed()
                if keys[self.player.sprite.keys[0]]:
                    self.player.sprite.position.x += self.player.sprite.speed
                    self.client_socket.sendall(("p:" + str(self.player.sprite.position.x) + "\n").encode())
                elif keys[self.player.sprite.keys[1]]:
                    self.player.sprite.position.x -= self.player.sprite.speed
                    self.client_socket.sendall(("p:" + str(self.player.sprite.position.x) + "\n").encode())
                # elif keys[player.sprite.keys[2]]:
                #     pass
                #     # player.sprite.turnright()
                #     # client_socket.sendall((str(player.sprite.keys[2]) + "\n").encode())
                # elif keys[player.sprite.keys[3]]:
                #     pass
                    # player.sprite.turnleft()
                    # client_socket.sendall((str(player.sprite.keys[3]) + "\n").encode())
                elif keys[self.player.sprite.keys[4]] and self.player.sprite.ready:
                    self.client_socket.sendall(("SHOOT\n").encode())
                    sentflag.set()
                    # client_socket.sendall((str(player.sprite.keys[4]) + "\n").encode())
                    #player.sprite.shoot()
                    self.player.sprite.ready = False
                    self.player.sprite.lasertime = pygame.time.get_ticks()
                time.sleep(1/(2*60))

            except Exception:
                pass

    def get_online_input(self,e,sentflag):
        mod_aliens = self.aliens.sprites()
        while not e.is_set():
            data = self.client_socket.recv(4096).decode()
            messages = data.split("\n")
            for message in messages:
                if "a:" in message:
                    packet = message.split("a:")[1]
                    packet = json.loads(packet)
                    for key in packet:
                        positions = packet[key].split(":")
                        positionx,positiony = positions[0],positions[1]
                        positionx = float(positionx)
                        positiony = float(positiony)
                        mod_aliens[int(key)].pos.x,mod_aliens[int(key)].pos.y = positionx,positiony
                # for key in decodeddata:
        #           positionx = float(decodeddata[key].split(":")[0])
        #           positionx = round(positionx)
        #           aliens[key].pos.x = positionx
                elif "k:" in message:
                    laser_dict = message.split("k:")[1]
                    laser_dict = json.loads(laser_dict)
                    for key in laser_dict:
                        x,y = laser_dict[key][0],laser_dict[key][1]
                        spawnpoint = (float(x),float(y))
                        if sentflag.is_set():
                            self.player.sprite.lasers.add(Laser(0,3,spawnpoint))
                            sentflag.clear()
                        else:
                            self.player2.sprite.lasers.add(Laser(0,3,spawnpoint))
                elif len(message) > 0:
                    # print("SERVER SENT: " + message)
                    try:
                        self.do_input(message)

I've tried making the thread sending enemy updates on the server run slower, but it isn't too great of a solution, as the game will run then slower.

I've tried thinking of not overloading them by sending just inputs across the server, then sending a big update every so often, but I feel eventually the read buffer (4096 - already quite high in my opinion) will get full and I won't get correct data to process.

0

There are 0 answers