问题描述
我使用 pygame 和套接字在 python 中编写了一个简单的 Slither.io 克隆,但我遇到了三个问题:
- 当我独自在笔记本电脑上玩游戏时,游戏断断续续。每十秒我的游戏就会卡住一段时间(一毫秒)然后继续。这不是什么大问题,但很烦人。
- 当我在本地网络中的两台计算机上玩游戏时,我看到另一个玩家(另一条蛇)也断断续续。
- 最奇怪的问题是,当我在主笔记本电脑上运行服务器,然后在我的第二台笔记本电脑上运行游戏时,游戏开始并在几秒钟后崩溃。客户端上的调试器说pickle 数据在从服务器接收数据时被截断。但是,当我在第二台笔记本电脑上运行服务器程序并在主笔记本电脑上运行游戏时,一切正常。为什么?
我试过了:
问题一、客户端修改FPS,服务器修改time.sleep
问题2.更改服务器上的time.sleep
问题3.改变recv()方法的输入值
服务器代码:
import socket
import threading
import pickle
import random
import time
import math
ip = socket.gethostbyname(socket.gethostname())
port = 5555
address = (ip,port)
server = socket.socket(socket.AF_INET,socket.soCK_STREAM)
snakes = []
colors = [(80,0),(0,80,140),(80,80),80)]
food = []
for i in range(0,300,1):
food.append([random.randint(10,3290),random.randint(10,2090),100),10,1])
number_of_players = 0
def start(server,address):
server.bind(address)
def new_clients(server):
global number_of_players
server.listen()
while True:
conn,addr = server.accept()
thread = threading.Thread(target=client,args=(conn,addr,number_of_players))
thread.start()
number_of_players += 1
print(f"Active connections: {threading.activeCount() - 1}")
def client(conn,player):
print(f"New connection: {addr}\n")
global snakes
global colors
global food
try:
snakes.append([])
name = pickle.loads(conn.recv(1024))
while True:
verity = True
x = random.randint(100,3200)
y = random.randint(100,2000)
for i in range(0,len(snakes),1):
if snakes[i] != []:
if math.sqrt((snakes[i][0][0] - x)**2 + (snakes[i][0][1] - y)**2) > snakes[i][0][4]*2:
for a in range(0,len(snakes[i][1])):
if math.sqrt((snakes[i][1][a][0] - x)**2 + (snakes[i][1][a][1] - y)**2) < snakes[i][0][4]*2:
verity = False
if verity == False:
break
else:
verity = False
if verity == False:
break
if verity == True:
break
snakes[player] = [[x,y,random.choice(colors),4,30,name,3],[]]
conn.send(pickle.dumps(snakes[player]))
except (ConnectionAbortedError,ConnectionResetError,EOFError):
print("Connection lost")
print(f"Active connections: {threading.activeCount() - 2}")
conn.close()
return
while True:
try:
snakes_to_send = []
for i in range(0,1):
if (i != player) and (len(snakes[i]) != 0):
snakes_to_send.append(snakes[i])
conn.send(pickle.dumps([snakes_to_send,food]))
received = pickle.loads(conn.recv(8192*4))
snakes[player] = received[0]
eaten = received[1]
for i in range(0,len(eaten),1):
try:
food.remove(eaten[i])
if eaten[i][4] != 5:
food.append([random.randint(10,1])
except ValueError:
continue
dead_snake = received[2]
if dead_snake != []:
for i in range(0,len(dead_snake),1):
food.append(dead_snake[i])
time.sleep(1/1000)
except (ConnectionAbortedError,EOFError):
snakes[player] = []
print("Connection lost")
print(f"Active connections: {threading.activeCount() - 2}")
conn.close()
break
print(f"Server is starting on IP: {ip} and PORT: {port}")
start(server,address)
new_clients(server)
游戏:
#import
import pygame
import sys
import math
import random
import pygame_textinput
from client import Client
#pygame initialize
pygame.init()
win = pygame.display.set_mode((1100,700))
pygame.display.set_caption("Slither.io")
#connect to the server
ip = "10.0.0.3"
port = 5555
c = Client(ip,port)
c.server()
#fonts
font = pygame.font.SysFont("comicsansms",20)
big_font = pygame.font.SysFont("comicsansms",35)
#time
clock = pygame.time.Clock()
class Snake:
def __init__(self,x,color,length,diameter,score,step):
self.x = x
self.y = y
self.color = color
self.length = length
self.diameter = diameter
self.name = name
self.score = score
self.step = step
self.pos_list = []
#move the snake
def move(self):
self.mousex,self.mousey = pygame.mouse.get_pos()
self.difx = self.mousex - 550
self.dify = self.mousey - 350
try:
self.xstep = math.sqrt((self.steP**2)/((self.dify/self.difx)**2 + 1))
self.ystep = (self.dify/self.difx)*self.xstep
except ZeroDivisionError:
self.xstep = 0
self.ystep = self.step
if math.sqrt(self.difx**2 + self.dify**2) > self.diameter/3:
if(self.difx > 0):
self.x += self.xstep
self.y += self.ystep
elif(self.difx < 0):
self.x -= self.xstep
self.y -= self.ystep
else:
if self.dify > 0:
self.y += self.ystep
else:
self.y -= self.ystep
self.pos_list.append([self.x,self.y])
if len(self.pos_list) == 100:
del self.pos_list[0]
#check if the snake isn't out of the area or not crashed
def collision(self):
global run
if (self.x + self.diameter/3 >= 3299) or (self.x - self.diameter/3 <= 1) or (self.y + self.diameter/3 >= 2099) or (self.y - self.diameter/3 <= 1):
run = False
for i in range(0,len(other_snakes),1):
if math.sqrt((other_snakes[i].x - snake.x)**2 + (other_snakes[i].y - snake.y)**2) < other_snakes[i].diameter*(2/3):
run = False
for i in range(0,len(other_segments),1):
if math.sqrt((other_segments[i].x - snake.x)**2 + (other_segments[i].y - snake.y)**2) < other_segments[i].diameter*(2/3):
run = False
#draw all snakes
def draw(self):
pygame.draw.rect(win,self.color,[self.x - snake.x + 550 - self.diameter/2,self.y - snake.y + 350 - self.diameter/2,self.diameter,self.diameter],border_radius=int(self.diameter/2))
class Segment(Snake):
def __init__(self,step,circle):
self.x = x
self.y = y
self.color = (80,80)
self.diameter = diameter
self.step = step
self.circle = circle
self.pos_list = []
def move(self):
self.diameter = snake.diameter
try:
self.x = self.circle.pos_list[-int(self.diameter/self.step)][0]
self.y = self.circle.pos_list[-int(self.diameter/self.step)][1]
except IndexError:
self.x = self.circle.pos_list[0][0]
self.y = self.circle.pos_list[0][1]
self.pos_list.append([self.x,self.y])
if len(self.pos_list) == 100:
del self.pos_list[0]
class Food(Snake):
def __init__(self,points):
self.x = x
self.y = y
self.color = color
self.diameter = diameter
self.points = points
other_snakes = []
segments = []
other_segments = []
food = []
eaten = []
dead_snake = []
#get other snakes
def return_snakes():
global other_snakes
global other_segments
global food
other_snakes = []
other_segments = []
food = []
message = c.receive_message()
received = message[0]
for i in range(0,len(received),1):
s = Snake(received[i][0][0],received[i][0][1],received[i][0][2],received[i][0][3],received[i][0][4],received[i][0][5],received[i][0][6],snake.step)
other_snakes.append(s)
for a in range(0,len(received[i][1]),1):
sg = Segment(received[i][1][a][0],received[i][1][a][1],s.diameter,snake.step,0)
other_segments.append(sg)
rec_fd = message[1]
for i in range(0,len(rec_fd),1):
f = Food(rec_fd[i][0],rec_fd[i][1],rec_fd[i][2],rec_fd[i][3],rec_fd[i][4])
food.append(f)
#send the snake to server
def send_snake():
global snake
global segments
global food
to_send = [[[snake.x,snake.y,snake.color,snake.length,snake.diameter,snake.name,snake.score],[]]]
for i in range(0,len(segments),1):
to_send[0][1].append([segments[i].x,segments[i].y])
to_send.append(eaten)
to_send.append(dead_snake)
c.send_message(to_send)
#function for ordering snakes by score
def order(o):
return o["score"]
#draw screen
def screen():
for i in range(0,3301,100):
pygame.draw.line(win,(60,60,60),[i - snake.x + 550,0 - snake.y + 350],2100 - snake.y + 350],1)
for i in range(0,2101,[0 - snake.x + 550,i - snake.y + 350],[3300 - snake.x + 550,1)
#enter your nickname
textinput = pygame_textinput.TextInput(font_family="comicsansms",max_string_length=12)
while True:
win.fill((100,100,100))
events = pygame.event.get()
for event in events:
keys = pygame.key.get_pressed()
if (event.type == pygame.QUIT) or keys[pygame.K_ESCAPE] or keys[pygame.K_F4] and pygame.key.get_mods() & pygame.KMOD_ALT:
sys.exit()
if keys[pygame.K_RETURN]:
break
name = big_font.render("Nickname:",True,0))
center = name.get_rect(center=(400,350))
win.blit(name,center)
textinput.update(events)
win.blit(textinput.get_surface(),(500,325))
pygame.display.update()
c.send_message(textinput.get_text())
received = c.receive_message()
snake = Snake(received[0][0],received[0][1],received[0][2],received[0][3],received[0][4],received[0][5],received[0][6],received[0][7])
segment = Segment(snake.x,snake)
segments.append(segment)
for i in range(0,snake.length - 2,1):
segment = Segment(snake.x,segment)
segments.append(segment)
while True:
run = True
while run:
eaten = []
#quit game
for event in pygame.event.get():
keys = pygame.key.get_pressed()
if (event.type == pygame.QUIT) or keys[pygame.K_ESCAPE] or keys[pygame.K_F4] and pygame.key.get_mods() & pygame.KMOD_ALT:
sys.exit()
return_snakes()
snakes = [{"name": snake.name,"score": snake.score}]
for i in range(0,1):
snakes.append({"name": other_snakes[i].name,"score": other_snakes[i].score})
win.fill((100,100))
screen()
#draw all food
for i in range(0,len(food),1):
food[i].draw()
#draw the other snakes and describe
for i in range(0,1):
other_segments[i].draw()
for i in range(0,1):
other_snakes[i].draw()
name = font.render(other_snakes[i].name,0))
center = name.get_rect(center=(other_snakes[i].x - snake.x + 550,other_snakes[i].y - snake.y + 350))
win.blit(name,center)
#move,draw snake and check if the snake is alive
snake.move()
for i in range(0,1):
segments[i].move()
for i in range(0,1):
segments[i].draw()
snake.draw()
snake.collision()
#check if the snake ate food and add score
for i in range(len(food) - 1,-1,-1):
if math.sqrt((food[i].x - snake.x)**2 + (food[i].y - snake.y)**2) < (snake.diameter/2 + food[i].diameter/2):
snake.score += food[i].points
snake.length = int(snake.score/10) + 4
if snake.length - 1 > len(segments):
segment = Segment(snake.x,segment)
segments.append(segment)
snake.diameter = int(snake.score/50)*0.5 + 30
eaten.append([food[i].x,food[i].y,food[i].color,food[i].diameter,food[i].points])
#describe the snake
name = font.render(snake.name,0))
center = name.get_rect(center=(550,350))
win.blit(name,center)
send_snake()
#sort the snakes by score
snakes.sort(key=order,reverse=True)
#create the table
for i in range(0,1):
table = font.render(str(snakes[i]["name"]),0))
win.blit(table,(900,50 + i*30))
for i in range(0,1):
table = font.render(str(snakes[i]["score"]),(1050,1):
table = font.render(str(i + 1) + ".",(870,50 + i*30))
pygame.display.update()
clock.tick(140)
return_snakes()
#create food from head of dead snake
ds = [snake.x,15,5]
dead_snake.append(ds)
#create food from segments of dead snake
for i in range(0,1):
ds = [segments[i].x,segments[i].y,5]
dead_snake.append(ds)
send_snake()
dead_snake = []
clock.tick(140)
c.client.close()
while True:
#quit game
for event in pygame.event.get():
keys = pygame.key.get_pressed()
if (event.type == pygame.QUIT) or keys[pygame.K_ESCAPE] or keys[pygame.K_F4] and pygame.key.get_mods() & pygame.KMOD_ALT:
sys.exit()
if keys[pygame.K_SPACE]:
c = Client(ip,port)
c.server()
c.send_message(snake.name)
received = c.receive_message()
snake = Snake(received[0][0],received[0][7])
segments = []
segment = Segment(snake.x,snake)
segments.append(segment)
for i in range(0,1):
segment = Segment(snake.x,segment)
segments.append(segment)
break
win.fill((100,100))
screen()
#press space bar to continue
cont = big_font.render("Press space bar to continue",0))
center = cont.get_rect(center=(550,350))
win.blit(cont,center)
pygame.display.update()
clock.tick(140)
客户端类文件:
import socket
import pickle
ip = "10.0.0.3"
port = 5555
class Client:
def __init__(self,ip,port):
self.ip = ip
self.port = port
self.address = (self.ip,self.port)
self.client = socket.socket(socket.AF_INET,socket.soCK_STREAM)
def server(self):
try:
self.client.connect(self.address)
print(f"Successfully connected to {self.address}")
except:
pass
def send_message(self,message):
self.client.sendall(pickle.dumps(message))
def receive_message(self):
return pickle.loads(self.client.recv(8192*4))
这是我在这里使用的 pygame_textinput 库。
解决方法
您的第三个问题(截断的pickle 数据)是因为您使用的是TCP,并且您正在解开recv
返回的任何内容。您可能会想,每当您 send
某事时,接收方调用 recv
时,都会返回完全相同的内容,但实际上并非如此。 TCP 将您的数据拆分成数据包,因此接收方可能不会同时收到所有数据。
例如,如果您发送“abcdefgh”,然后单独发送“ijkl”,则允许第一次接收返回“abcd”,第二次接收返回“efghijkl”。或者第一个可以返回“ab”,第二个可以返回“cde”,第三个可以返回“fghijkl”,依此类推。
你必须设计一种方法让接收者知道什么时候停止接收。例如,如果您先发送“8abcdefgh”,然后发送“4ijkl”,则接收方可能会收到“8abcdefgh4ij”,然后它知道“8abcdefgh”是一个“发送”(因为它以 8 个字节开始,然后再发送 8 个字节)并且它知道“4ij”是下一个“发送”的开始,但不是全部(因为它以 4 开头,但没有 4 个字节)。
另一种方法是在每条消息后发送一个特殊字符,如换行符(回车键)。这可能不适用于泡菜,因为泡菜中可以有换行符。但是您可以选择泡菜没有的另一个字节,例如 0xFF。然后接收方知道继续接收,直到它看到字节 0xFF。