Pygame и сокет в многопользовательской игре: OverflowError - PullRequest
2 голосов
/ 27 марта 2020

Я пытаюсь создать простую многопользовательскую игру в Python с использованием модулей pygame и socket. Он состоит из двух кругов, которые управляются клавишами W, A, S, D с двух разных компьютеров.

Сначала я создал клиента с recv() в середине пигмеи l * 1041. *. Он работал хорошо, но recv() заблокировал l oop, поэтому движение кругов было не плавным, и мне пришлось увеличить скорость до 6 (до того, как она была установлена ​​на 0,6), чтобы иметь нормальную скорость. Вот код (я его немного суммировал):

Клиент 1-й версии

#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() в поток, чтобы прекратить блокировать l oop:

Клиент 2-й версии

#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 слишком велик для преобразования в C long

Я добавил for i in range(30):, потому что думал, что сервер падает, потому что было слишком много сообщений отправляется в то же время. Вывод такой же: через 3 секунды более или менее происходит сбой программы, выдавая OverflowError.

. Я добавил оператор print() в обеих версиях сразу после recv(), чтобы увидеть, какие значения x и я получил. В 1-й версии все они были в диапазоне ширины и высоты. Однако во второй версии 1/5 полученных сообщений было большим числом, таким как 124381473265. Если это число еще больше, оно дает OverflowError. Я не понимаю, почему это происходит: я кодирую и декодирую сообщения одинаково в обеих версиях, но одна работает, а другая нет.

Я не включаю код сервера потому что я не думаю, что это необходимо. Он просто передает сообщения между обоими клиентами без их изменения. Сервер не является ошибкой, так как в 1-й версии клиента сообщения отправляются и принимаются правильно.

Любая помощь приветствуется. Если вам нужна дополнительная информация по любому вопросу, просто скажите мне.

Заранее спасибо!

1 Ответ

0 голосов
/ 27 марта 2020

Поместите свой код сокета в поток или используйте select.select() для неблокирующего чтения сокетов. Затем отправляйте пользовательские сообщения о событиях обратно на главный l oop, когда с сервера поступает грамм данных.

import pygame
import enum

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

Ваш код чтения сокетов должен обрабатывать частичные пакеты, зависания и задержки. , Я думаю, что ваша ошибка выше вызвана попыткой распаковать частичный (или каким-то образом ненужный) пакет. Обычно люди используют модуль pickle для инкапсуляции этих данных, но для начала я бы сделал предварительное тестирование с простыми строковыми данными. Проще отладить. Затем, после того, как код передачи полностью уложен и протестирован, измените его на двоичный - при необходимости.

Мне нравится использовать select() в моем коде сокета, несмотря ни на что. Это дает детальный контроль над тем, что происходит с сокетом.

Вот простой многопоточный обработчик диалога с сокетом. Он действительно поддерживает только зависание сокета и входящее сообщение. Но его можно легко расширить для обработки других типов сообщений.

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()

В вашем основном l oop обработайте события, как и все остальные:

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 )
...