Python - [Errno 10054] An existing connection was forcibly closed by the remote host - sending pickled data issues

时间秒杀一切 提交于 2021-01-06 05:49:49

问题


I followed an online tutorial that showed how to make a rock paper scissors game that can handle multiple clients connecting to a server, allowing multiplayer. The game works so I tried to use the same network logic for a different game but I ran into some errors there. Particularly when using the send function: Using the send function I get an EOFError: Ran out of input error. In this post Online game give the error "("ConnectionAbortedError: [WinError 10053] " I found a supposed fix where the send function waits until it has received all of the data, as it sometimes may have issues when receiving pickled data.

However, when I replace the send function with the new send2 function (temporary name) I run into different errors. Namely: [Errno 10054] An existing connection was forcibly closed by the remote host.

SOLVED, SEE FOLLOWING PARAGRAPh:

Essentially, what I'm trying to do is replacing my send function (which also receives data) by a better send function that sends pickled data and receives the exact amount of sent information.

def send(self, data):
    try:
        self.client.send(pickle.dumps(data))
        return pickle.loads(self.client.recv(2048))
    except socket.error as e:
        print(e)

# source: https://stackoverflow.com/questions/62361900/online-game-give-the-error-connectionabortederror-winerror-10053
def send2(self, data):
    data_to_send = pickle.dumps(data)
    data_size = struct.pack( '!I', len(data_to_send))
    try:
        self.client.send(data_size)
        self.client.send(pickle.dumps(data))
        
        package = self.receive()
        return package
    except socket.error as e:
        print(e)

        
def receive(self):
    packet = None
    buffer = bytes()
    expected = -1
    print("receiving stuff")
    while len(buffer) < 4:
        print("received a buffer")
        try:
            partial_data = self.client.recv(4 - len(buffer))
            if partial_data:
                 buffer.append(partial_data)
                 if len(buffer) == 4:
                     expected = struct.unpack('!I', buffer)
        except Exception as e:
            print(e)
            break

    # If we received a buffer size, try to receive the buffer
    if expected > 0:
        buffer = bytes()
        while len(buffer) < expected: 
            try:
                partial_data = self.client.recv(expected - len(buffer))
                if partial_data:
                     buffer.append(partial_data)
                     # Have we received the full data-set yet?
                     if len(buffer) == expected:
                         packet = pickle.loads(buffer)
            except:
                break
            
    print(packet)
    return packet

I'm assuming I'm overlooking something incredibly easy. If needed, I can post the 4 .py files that run the game. All I need is to create a better send function that doesn't crash the client when it is dealing with sending data that is sometimes not pickle-worthy.

EDIT:

Server.py code

import socket
import pickle
from _thread import *
import sys
sys.path.append("C:/Program Files/Hero Realms/Multiplayer/")
from game import Game
from network import Network

server = "" # this is defined but I removed it for this page
port = 5555

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

try:
    s.bind((server, port))
except socket.error as e:
    print(e)
    
s.listen(2)
print("Waiting for a connection, Server Started")

connected = set()
games = {}
id_count = 0

def threadedClient(conn, p, game_id):
    global id_count 
    conn.send(str.encode(str(p)))
    
    reply = ""
    while True:
        try:
            #data = pickle.loads(conn.recv(4096))
            data = Network.receive(s)
            print(data)
            # check if the game still exists
            if game_id in games:
                game = games[game_id]
                
                if not data:
                    break
                else:
                    if data == "reset":
                        game.resetWent()
                    elif data != "get":
                        game.play(p, data)
                        
                    reply = game
                    conn.sendall(pickle.dumps(reply))
                    
            else:
                break
        except:
            break
        
    print("Lost connection")
    
    try:
        print("Closing game", game_id)
        del games[game_id]
    except:
        pass
    
    id_count -=1
    conn.close()
    
    
while True:
    conn, addr = s.accept()
    print("Connected to: ", addr)
    
    id_count += 1
    p = 0
    game_id = (id_count - 1) // 2
    
    if id_count % 2 == 1:
        games[game_id] = Game(game_id)
        print("Creating a new game...")
    else:
        games[game_id].ready = True
        p = 1
        
    
    start_new_thread(threadedClient, (conn, p, game_id))

Client.py

import pygame

import sys
sys.path.append("C:/Program Files/Hero Realms/Multiplayer/")
from network import Network
import pickle

pygame.font.init()
pygame.init()

width = 700
height = 700
win = pygame.display.set_mode((width, height))
pygame.display.set_caption("Client")

class Button:
    def __init__(self, text, x, y, color):
        self.text = text
        self.x = x
        self.y = y
        self.color = color
        self.width = 150
        self.height = 100
        
    def draw(self, win):
        pygame.draw.rect(win, self.color, (self.x, self.y, self.width, self.height))
        font = pygame.font.SysFont("comicsans", 40)
        text = font.render(self.text, 1, (255, 255, 255))
        win.blit(text, (self.x + round(self.width / 2) - round(text.get_width() / 2), self.y + round(self.height / 2) - round(text.get_height() / 2)))
        
    def click(self, pos):
        x1 = pos[0]
        y1 = pos[1]
        
        if self.x <= x1 <= self.x + self.width and self.y <= y1 <= self.y + self.height:
            return True
        else:
            return False
        
def redrawWindow(win, game, p):
    win.fill((128, 128, 128))
    
    if not(game.connected()):
        font = pygame.font.SysFont("comicsans", 80)
        text = font.render("Waiting for Player...", 1, (255, 0, 0), True)
        win.blit(text, (width / 2 - text.get_width() / 2, height / 2 - text.get_height() / 2))
    else:
        font = pygame.font.SysFont("comicsans", 60)
        text = font.render("Your Move", 1, (0, 255, 255))
        win.blit(text, (80, 200))
        
        text = font.render("Opponents", 1, (0, 255, 255))
        win.blit(text, (380, 200))
        
        move1 = game.getPlayersMove(0)
        move2 = game.getPlayersMove(1)
        if game.bothWent():
            text1 = font.render(move1, 1, (0, 0, 0))
            text2 = font.render(move2, 1, (0, 0, 0))
        else:
            if game.p1_went and p == 0:
                text1 = font.render(move1, 1, (0, 0, 0))
            elif game.p1_went:
                text1 = font.render("Locked in", 1, (0, 0, 0))
            else:
                text1 = font.render("Waiting...", 1, (0, 0, 0))
                
            if game.p2_went and p == 1:
                text2 = font.render(move2, 1, (0, 0, 0))
            elif game.p2_went:
                text2 = font.render("Locked in", 1, (0, 0, 0))
            else:
                text2 = font.render("Waiting...", 1, (0, 0, 0))
                
        if p == 1:
            win.blit(text2, (100, 350))
            win.blit(text1, (400, 350))
        else:
            win.blit(text1, (100, 350))
            win.blit(text2, (400, 350))
                
        for btn in btns:
            btn.draw(win)
            
    pygame.display.update()
        
        
        

    
    
    
btns = [Button("Rock", 50, 500, (0, 0, 0)), Button("Scissors",  250, 500, (255, 0, 0)), Button("Paper", 450, 500, (0, 255, 0))]


def main():
    run = True
    clock = pygame.time.Clock()
    n = Network()
    player = int(n.getP())
    print("You are player ", player)
    
    while run:
        clock.tick(60)
        try:
            game = n.send2("get")
        except:
            run = False
            print("Couldn't get game")
            break
        
        if game.bothWent():
            redrawWindow(win, game, player)
            pygame.time.delay(500)
            try:
                game = n.send2("reset")
            except:
                run = False
                print("Couldn't get game")
                break
            
            font = pygame.font.SysFont("comicsans", 90)
            if (game.winner() == 1 and player == 1) or (game.winner() == 0 and player == 0):
                text = font.render("You won!", 1, (255, 0, 0))
            elif game.winner() == -1:
                text = font.render("Tie game!", 1, (255, 0, 0))
            else:
                text = font.render("You lost", 1, (255, 0, 0))
                
            win.blit(text, (width / 2 - text.get_width() / 2, height / 2 - text.get_height() / 2))
            pygame.display.update()
            pygame.time.delay(2000)

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
                pygame.quit()
            if event.type == pygame.MOUSEBUTTONDOWN:
                pos = pygame.mouse.get_pos()
                for btn in btns:
                    if btn.click(pos) and game.connected():
                        if player == 0:
                            if not game.p1_went:
                                n.send2(btn.text)
                        else:
                            if not game.p2_went:
                                n.send2(btn.text)
                                
        redrawWindow(win, game, player)
                        
def menuScreen():
    run = True
    clock = pygame.time.Clock()
    
    while run:
        clock.tick(60)
        win.fill((128, 128, 128))
        font = pygame.font.SysFont("comicsans", 60)
        text = font.render("Click to play!", 1, (255, 0, 0))
        win.blit(text, (100, 200))
        pygame.display.update()
        
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                run = False
            if event.type == pygame.MOUSEBUTTONDOWN:
                run = False
                
    main()
    
while True:
    menuScreen()

回答1:


I managed to finally accomplish what I wanted to do in the first place. I wanted to write a send function that sent out X amount of pickled data alongside a receive function that received X amount of pickled data without throwing errors when it received data that was too small to be pickled.

See the send_data and the receive_data functions for the solution in network.py. The server file server.py requires these functions as well although slightly different. In your client file you should thus make use of the network.send_data function to send data from the client to the server.

network.py

import socket
import pickle
HEADERSIZE = 10

class Network:
    def __init__(self):
        self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server = "enter server address"
        self.port = 5555
        self.addr = (self.server, self.port)
        self.p = self.connect()
        
    def getP(self):
        return self.p
    
    def connect(self):
        try:
            self.client.connect(self.addr)
            return self.client.recv(2048).decode()
        except:
            pass

    # works but only sends and receives string encoded data without a buffer
    def send1(self, data):
        try:
            self.client.send(str.encode(data))
            return pickle.loads(self.client.recv(2048))
        except socket.error as e:
            print(e)
            
    # works but only sends and receives pickled data without a buffer
    def send2(self, data):
        try:
            self.client.send(pickle.dumps(data))
            return pickle.loads(self.client.recv(2048))
        except socket.error as e:
            print(e)
        
    # this is the function you should use, it uses a receive function that has a buffer
    # and ensures that you receive ALL the information that you sent without throwing errors
    def send_data(self, data):
        data_to_send = pickle.dumps(data)
        data_size = bytes(f'{len(data_to_send):<{10}}', "utf-8")
        try:
            self.client.send(data_size + data_to_send)
            
            package = self.receive_data()
            return package
        except socket.error as e:
            print(e)
    
    def receive_data(self):
        full_msg = b''
        new_msg = True
        while True:
            msg = self.client.recv(16)
            if new_msg:
                msglen = int(msg[:HEADERSIZE])
                new_msg = False
                
            full_msg += msg
    
            if len(full_msg)-HEADERSIZE == msglen:
                data = pickle.loads(full_msg[HEADERSIZE:])
                break
    
        return data

server.py

import socket
import pickle
from _thread import *
import sys
from game import Game

server = "enter server address"
port = 5555

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

try:
    s.bind((server, port))
except socket.error as e:
    print(e)
    
s.listen(2)
print("Waiting for a connection, Server Started")

connected = set()
games = {}
id_count = 0
HEADERSIZE = 10

def receive_data(sock):
    full_msg = b''
    new_msg = True
    while True:
        msg = sock.recv(16)
        if new_msg:
            msglen = int(msg[:HEADERSIZE])
            new_msg = False

        full_msg += msg

        if len(full_msg)-HEADERSIZE == msglen:
            data = pickle.loads(full_msg[HEADERSIZE:])
            break

    return data

def send_data(clientsocket, data):
    data_to_send = pickle.dumps(data)
    data_size = bytes(f'{len(data_to_send):<{10}}', "utf-8")
    try:
        clientsocket.send(data_size + data_to_send)
        
    except socket.error as e:
        print(e)

def threadedClient(conn, p, game_id):
    global id_count 
    conn.send(str.encode(str(p)))
    
    reply = ""
    while True:
        try:
            data = receive_data(conn)
            # check if the game still exists
            if game_id in games:
                game = games[game_id]
                
                if not data:
                    break
                else:
                    if data == "reset":
                        game.resetWent()
                    elif data != "get":
                        game.play(p, data)
                        
                    reply = game
                    
                    send_data(conn, reply)

            else:
                break
        except Exception as e:
            print("Failed try")
            print(e)
            break
        
    print("Lost connection")
    
    try:
        print("Closing game", game_id)
        del games[game_id]
    except:
        pass
    
    id_count -=1
    conn.close()
    
    
while True:
    conn, addr = s.accept()
    print("Connected to: ", addr)
    
    id_count += 1
    p = 0
    game_id = (id_count - 1) // 2
    
    if id_count % 2 == 1:
        games[game_id] = Game(game_id)
        print("Creating a new game...")
    else:
        games[game_id].ready = True
        p = 1
        
    
    start_new_thread(threadedClient, (conn, p, game_id))


来源:https://stackoverflow.com/questions/65323084/python-errno-10054-an-existing-connection-was-forcibly-closed-by-the-remote

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!