python - 多人游戏中的Pygame和socket : OverflowError

标签 python python-3.x multithreading sockets pygame

我正在尝试使用 pygamesocket 模块在 Python 中创建一个简单的多人游戏。它仅由两个圆圈组成,由两台不同计算机的 W、A、S、D 键控制。

首先,我在 pygame 循环中间使用 recv() 创建了一个客户端。它运行良好,但 recv() 阻塞了循环,因此圆圈的运动不平滑,我必须将速度增加到 6(在设置为 0.6 之前),以便正常速度。这是代码(我总结了一下):

客户端第一个版本

#import modules

def main(sock):
    pygame.init()
    
    #Display screen and set initial me_x and me_y

    vel = 0.6
   
    while True:
        keys_pressed = pygame.key.get_pressed()

        #Change me_x and me_y (with A, D, W, S keys)

        #Make sure me_x and me_y don't get off the screen

        screen.fill(color)
        
        if other_x and other_y:
            pygame.draw.circle(screen, colorMe, (other_x, other_y), radi)

        pygame.draw.circle(screen, colorOther, (int(me_x), int(me_y)), radi)

        pygame.display.flip()

        sock.send(int(me_x).to_bytes(3, byteorder = 'big') + int(me_y).to_bytes(3, byteorder = 'big'))

        otherPos = sock.recv(BUFSIZ)

        other_x = int.from_bytes(otherPos[:3], byteorder = 'big')
        other_y = int.from_bytes(otherPos[3:], byteorder = 'big')
        print(other_x, other_y)

#CONNECT TO TCP SOCKET

other_x = None
other_y = None

main(client_socket)

然后,我尝试将 recv() 放入线程中以停止阻塞循环:

客户端第二版

#import modules

def main(sock):
    pygame.init()
    
    #Display screen and set initial me_x and me_y

    vel = 0.6
   
    while True:
        for i in range(30):
            keys_pressed = pygame.key.get_pressed()

            #Change me_x and me_y (with A, D, W, S keys)

            #Make sure me_x and me_y don't get off the screen

            screen.fill(color)
            
            if other_x and other_y:
                pygame.draw.circle(screen, colorOther, (other_x, other_y), 15)

            pygame.draw.circle(screen, colorMe, (int(me_x), int(me_y)), 15)

            pygame.display.flip()

        msg = int(me_x).to_bytes(3, byteorder = 'big') + int(me_y).to_bytes(3, byteorder = 'big')
        sock.send(msg)

def recv_pos(sock):
    while True:
        other_pos = sock.recv(BUFSIZ)
        other_x = int.from_bytes(other_pos[:3], byteorder = 'big')
        other_y = int.from_bytes(other_pos[3:], byteorder = 'big')

        print(other_x, other_y)

#CONNECT TO TCP SOCKET

other_x = None
other_y = None

receive_thread = threading.Thread(target = recv_pos, args = (client_socket,))
receive_thread.daemon = True
receive_thread.start()

main(client_socket)

但是,当我在两台不同的计算机中启动客户端 2 的 2 个实例时,它会给出一个 OverflowError:

OverflowError: Python int too large to convert to C long

我添加了for i in range(30):,因为我认为服务器正在崩溃,因为同时发送的消息太多。输出是相同的:大约 3 秒后,程序崩溃并给出 OverflowError

我在两个版本中的 recv() 之后添加了一个 print() 语句,以查看我收到的 x 和 y 值。在第一个版本中,它们都在宽度和高度范围内。然而,在第二个版本中,收到的消息中有 1/5 是一个很大的数字,例如 124381473265。如果这个数字更大,则会出现溢出错误。我不明白为什么会发生这种情况:我在两个版本中都以相同的方式对消息进行编码和解码,但一个有效,另一个无效。

我没有包含服务器代码,因为我认为没有必要。它只是在两个客户端之间传输消息而不修改它们。服务器不是错误,因为在第一个客户端版本中,消息可以正确发送和接收。

任何帮助将不胜感激。如果您需要有关任何点的更多信息,请告诉我。

提前致谢!

最佳答案

将套接字代码放入线程中,或使用 select.select() 进行非阻塞套接字读取。然后,当数据报从服务器到达时,将自定义事件消息发送回主循环。

import pygame
import enum

class NetworkEvents( enum.IntEnum ):
    EVENT_HANGUP    = pygame.USEREVENT + 1
    EVENT_MESSAGE   = pygame.USEREVENT + 2

您的套接字读取代码也应该处理部分数据包、挂起和延迟。我认为您的上述错误是由于尝试解压部分(或某种垃圾)数据包而引起的。通常人们使用 pickle 模块来封装这些数据,但首先,我会使用简单的字符串数据进行预测试。调试起来更容易。然后,一旦传输代码全部固定并经过测试,更改为二进制 - 如有必要。

无论如何,我喜欢在我的套接字代码中使用select()。它可以对套接字发生的情况进行细粒度控制。

这是一个简单的线程套接字 session 处理程序。它实际上只支持套接字挂起和传入消息。但它可以轻松扩展以处理更多消息类型。

import threading
import pygame
import random
import enum
import socket
import select
import time

class ConversationHandlerThread( threading.Thread ):
    """ A thread that handles a conversation with a single remote server.
        Accepts commands of 'close', 'red', 'green' or 'blue', and posts messages
        to the main PyGame thread for processing """
    def __init__( self, server_address, server_port ):
        threading.Thread.__init__(self)
        self.server_address = server_address
        self.server_port    = server_port
        self.server_socket  = None
        self.data_buffer    = ''
        self.daemon         = True # exit with parent
        self.done           = False

    def stop( self ):
        self.done = True

    def connect( self ):
        self.server_socket = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
        while True:
            try:
                self.server_socket.connect( ( self.server_address, self.server_port ) )
                print( "Connected to %s:%d" % ( self.server_address, self.server_port ) )
                break;
            except:
                print( "Failed to connect %s:%d" % ( self.server_address, self.server_port ) )
                time.sleep( 12 )
                print( "Retrying..." )

    def run( self ):
        """ Connects to Server, then Loops until the server hangs-up """
        self.connect()

        # Now we're connected, start reading commands
        read_events_on   = [ self.server_socket ]
        while ( not self.done ):
            # Wait for incoming data, or errors, or 0.3 seconds
            (read_list, write_list, except_list) = select.select( read_events_on, [], [], 0.5 )

            if ( len( read_list ) > 0 ):
                # New data arrived, read it
                incoming = self.server_socket.recv( 8192 )
                if ( len(incoming) == 0):
                    # Socket has closed
                    new_event = pygame.event.Event( NetworkEvents.EVENT_HANGUP, { "address" : self.server_address } )
                    pygame.event.post( new_event )
                    self.server_socket.close()
                    self.done = True
                else:
                    # Data has arrived
                    try:
                        new_str = incoming.decode('utf-8')
                        self.data_buffer += new_str
                    except:
                        pass # don't understand buffer

                    # Parse incoming message (trivial parser, not high quality) 
                    # commands are '\n' separated
                    if (self.data_buffer.find('\n') != -1 ):
                        for line in self.data_buffer.split('\n'):
                            line = line.strip()
                            # client disconnect command
                            if ( line == 'close' ):
                                new_event = pygame.event.Event( NetworkEvents.EVENT_HANGUP, { "address" : self.server_address } )
                                pygame.event.post( new_event )
                                self.server_socket.close()
                                self.done = True

                            # only make events for valid commands
                            elif ( line in ( 'red', 'green', 'blue' ) ):
                                new_event = pygame.event.Event( NetworkEvents.EVENT_MESSAGE, { "address" : self.server_address, "message" : line  } )
                                pygame.event.post( new_event )
                        self.data_buffer = ''  # all used-up

将所有这些复杂性放入一个线程中的美妙之处在于,一旦它启动,您就可以忘记它。

# Start the network-handler thread
thread1 = ConversationHandlerThread( '127.0.0.1', 5555 )
thread1.start()

在主循环中,像处理其他事件一样处理事件:

for event in pygame.event.get():
    if ( event.type == pygame.QUIT ):
        done = True

    elif ( event.type == NetworkEvents.EVENT_HANGUP ):
        print(" CLIENT DISCONNECTED %s " % ( str(event.address) ) )

    elif ( event.type == NetworkEvents.EVENT_MESSAGE ):
        print(" CLIENT MESSAGE FROM %s - %s " % ( str(event.address), event.message ) )
        if ( event.message == 'red' ):
            new_sprite = AlienSprite( RED )
            SPRITES.add( new_sprite )

关于python - 多人游戏中的Pygame和socket : OverflowError,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60884259/

相关文章:

python函数修改调用范围内的变量

python - 创建一个类级别的装饰器来自动添加一个属性

multithreading - 如何防止卡住MainForm并等待子线程的返回值

Java 多线程和异步 Http 请求处理

python - 如何检测装饰器是否已应用于方法或函数?

python - 将列表转换为索引和范围 0-1.0

python - 根据其他列值/Pandas -Python 在数据框中创建 ID 列

java - while 循环的奇怪行为

python - Matplotlib 全屏无法工作

python - 在网页上抓取 jpg 文件,然后使用 python 保存它