Только запуск кода отображения Pygame только после того, как данные получены из соединения с сокетом Python - PullRequest
2 голосов
/ 14 марта 2019

Я хочу написать программу Pygame, в которой я получаю данные от Raspberry Pi через сокетное соединение.Однако я хочу, чтобы код Pygame запускался только после поступления строки.Это включает в себя код, ожидающий некоторое время, затем, когда строка получена от Raspberry Pi, pygame запускает код для обновления дисплея.В настоящее время моя Pygame зависает и отключается при ожидании поступления данных.Есть идеи.Спасибо.

Ответы [ 2 ]

0 голосов
/ 15 марта 2019

Здесь есть несколько проблем. Как отвечает @Josep Valls, код должен осуществлять связь с сокетами в подпотоке. Кроме того, основной поток PyGame должен постоянно опрашивать цикл событий (и, возможно, перерисовывать экран). Очевидно, что если код ничего не делает в каждом цикле, возможно, нет необходимости каждый раз перекрашивать весь экран, как в примере ниже.

Итак, что нужно для обработки соединений через сокет с PyGame? Сначала вам нужно прослушивать сокет, чтобы принимать соединения. В этом примере класс ConnectionHandlerThread делает именно это. Он прослушивает входящие клиентские соединения и, когда это происходит, создает ConversationHandlerThread для чтения / записи для каждого клиента. (Если вы делаете много сокетов, приходящих и уходящих, возможно, вам понадобится пул потоков для этого, но я также стараюсь сделать пример простым.)

Итак, чтобы объяснить пример кода - он рисует окно с парой AlienSprite скачущих вокруг. Клиенты могут подключаться к нему через порт 5555 (скажем, с telnet) и выдавать команды. Отправка цветного слова (красный / зеленый / синий) порождает нового пришельца в этом цвете. Команда quit отключается.

Для связи между потоками обработки сокетов и основным потоком PyGame потоки сокетов используют PyGame event.post() для отправки событий. Эти пользовательские события были реализованы в enum с именем NetworkEvents, чтобы отслеживать их. Прелесть этого в том, что вас не смущает, когда две темы публикуют pygame.USEREVENT + 27 для разных типов событий.

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

# Window size
WINDOW_WIDTH=400
WINDOW_HEIGHT=400

DARK    = (  50, 50, 50 )
WHITE   = ( 255,255,255 )
RED     = ( 255, 55, 55 )
GREEN   = (   5,255, 55 )
BLUE    = (   5, 55,255 )

LISTEN_ADDRESS = '127.0.0.1'
LISTEN_PORT    = 5555


class NetworkEvents( enum.IntEnum ):
    CLIENT_CONNECTED = pygame.USEREVENT + 1
    CLIENT_HANGUP    = pygame.USEREVENT + 2
    CLIENT_MESSAGE   = pygame.USEREVENT + 3


class AlienSprite( pygame.sprite.Sprite ):
    """ A tiny little alien, which wanders around the screen """
    def __init__( self, colour ):
        pygame.sprite.Sprite.__init__(self)
        self.image = self.render( colour )
        self.rect  = self.image.get_rect()
        self.rect.center = ( random.randrange( 0, WINDOW_WIDTH ), random.randrange( 0, WINDOW_HEIGHT ) )

    def render( self, colour ):
        new_image = pygame.Surface( ( 7, 6 ), pygame.SRCALPHA )
        new_image.fill( (0,0,0,0) )
        pixels = [                     (3,1),               \
                                (2,2), (3,2), (4,2),        \
                            (1,3),     (3,3),      (5,3),   \
                                (2,4), (3,4), (4,4) ]
        for p in pixels:
            new_image.set_at( p, colour )
        return new_image

    def update( self ):
        self.rect.x += random.randrange( -2, 3 )
        self.rect.y += random.randrange( -2, 3 )
        # remove if we wander off-screen
        if ( self.rect.x < 0 or self.rect.x >= WINDOW_WIDTH or \
             self.rect.y < 0 or self.rect.x >= WINDOW_HEIGHT ):
            self.kill() 




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

    def stop( self ):
        self.done = True

    def run( self ):
        """ Loops until the client hangs-up """
        read_events_on   = [ self.client_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.client_socket.recv( 8192 )
                if ( len(incoming) == 0):
                    # Socket has closed
                    new_event = pygame.event.Event( NetworkEvents.CLIENT_HANGUP, { "address" : self.client_address } )
                    pygame.event.post( new_event )
                    self.client_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.CLIENT_HANGUP, { "address" : self.client_address } )
                                pygame.event.post( new_event )
                                self.client_socket.close()
                                self.done = True

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






class ConnectionHandlerThread( threading.Thread ):
    """ Opens a socket, listening for incoming connections.
        If a client connects, notifies the PyGame main thread with a message,
        and starts a conversation handler thread to accept commands """
    def __init__( self ):
        threading.Thread.__init__(self)
        self.daemon = True # exit with parent
        self.done   = False

    def stop( self ):
        self.done = True

    def run( self ):
        # Establish a socket-listener
        rx_sock = socket.socket() 
        rx_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        rx_sock.bind( ( LISTEN_ADDRESS, LISTEN_PORT ) )
        while ( not self.done ):
            rx_sock.listen( 3 ) # small queue
            client_socket, remote_addr = rx_sock.accept()
            print("Connection from %s" % ( str( remote_addr ) ) )
            # Tell the main thread someone connected via an event
            new_event_args = { "socket"  : client_socket, "address" : remote_addr  }
            new_event = pygame.event.Event( NetworkEvents.CLIENT_CONNECTED, new_event_args )
            pygame.event.post( new_event )
            # Start a thread to handle the socket-conversation with the client
            new_thread = ConversationHandlerThread( client_socket, remote_addr )
            new_thread.start()




###
### MAIN
###

# Create the window
pygame.init()
pygame.display.set_caption("Socket Messages")
WINDOW  = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE )

# Create some sprites
SPRITES = pygame.sprite.Group()
for i in range( 3 ):
    new_sprite = AlienSprite( WHITE )
    SPRITES.add( new_sprite )

# Start the connection-listener thread
thread1 = ConnectionHandlerThread()
thread1.start()

# Main paint / update / event loop
done = False
clock = pygame.time.Clock()
while ( not done ):
    SPRITES.update()

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

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

        elif ( event.type == NetworkEvents.CLIENT_CONNECTED ):
            print(" NEW CLIENT FROM %s " % ( str(event.address) ) )

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

        elif ( event.type == pygame.VIDEORESIZE ):
            WINDOW_WIDTH  = event.w
            WINDOW_HEIGHT = event.h
            WINDOW  = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE )

    WINDOW.fill( DARK )
    SPRITES.draw( WINDOW )
    pygame.display.flip()

    clock.tick_busy_loop(60)

thread1.stop()
pygame.quit()

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

0 голосов
/ 14 марта 2019

Pygame "зависает", скорее всего, из-за блокировки сетевого вызова.Подумайте о том, чтобы развернуть новый поток для обработки передачи данных, пока Pygame захватывает основной поток и обновляет отображение некоторым «ожидающим» сообщением.После получения данных второй поток должен передать их основному потоку, который их отобразит.

...