问题
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