Python 3: UDP Пакет отправлять / получать в интерфейсе Tkinter - PullRequest
0 голосов
/ 13 декабря 2018

Итак, я могу легко написать небольшой скрипт для прослушивания пакетов UDP на определенном IP / порте, но я изо всех сил пытаюсь внедрить его в графический интерфейс Tkinter.

Всякий раз, когда я пытаюсь использовать бесконечныйв то время как True: цикл запускается кнопкой, приложение GUI падает.Я провел дополнительное исследование и немного прочитал об использовании задержек, но не могу заставить его работать должным образом.Я попытался поместить цикл while в прокси-функцию, которая вызывает функцию startreceiving, но это также приводит к сбою графического интерфейса.Приведенный ниже код позволяет настроить и запустить мои текущие проблемы.

Последний вопрос: как я могу получить кнопку для запуска события, чтобы начать отправку пакетов, и в то же время иметь возможность принимать события кнопки для запуска ипрекратить прием пакетов?

import socket
import tkinter as tk
import tkinter.font as tkFont

UDP_IP = "127.0.0.1"
UDP_PORT = 5005
MESSAGE = b"Hello, world"

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

sock.bind((UDP_IP, UDP_PORT))

def startsending(run=True):
    while run is True:
        print("Sending Message.")
        sock.sendto(MESSAGE, (UDP_IP, UDP_PORT))

def startreceiving(run=True):
    while run is True:
        try:
            data, addr = sock.recvfrom(1024)
            print("received message:", data)
            print("from: ", addr)
        except OSError:
            break

class App(tk.Frame):
    STRIDE = 8
    DELAY = 100

    variables = []
    for i in range(10):
        variables.append(i)

    sensors = []
    for i in range(3):
        sensors.append(i)

    fields = []
    for i in range(len(sensors) * len(variables)):
        fields.append(i)

    def __init__(self, master=None):
        tk.Frame.__init__(self,master)
        self.grid()
        self.create_widgets()
        self.after(self.DELAY, self.update, self.DELAY)

    #---- Create the GUI Layout ----
    def create_widgets(self):
        self.btn_font = tkFont.Font(family="Helvetica", size=12, weight='bold')
        self.gui_buttons = []
        self.send_button = tk.Button(self,
                                     text = format("Begin Sending."),
                                     font = self.btn_font,
                                     relief = tk.RIDGE,
                                     pady = 4,
                                     command = self.send_message)
        self.send_button.grid(column=2, row=11)
        self.start_button = tk.Button(self,
                                 text = format("Begin Receiving."),
                                 font = self.btn_font,
                                 relief = tk.RIDGE,
                                 pady = 4,
                                 command = self.start_receiving)
        self.start_button.grid(column=3, row=11)
        self.stop_button = tk.Button(self,
                                     text = format("Stop Receiving."),
                                     font = self.btn_font,
                                     relief = tk.RIDGE,
                                     pady = 4,
                                     padx = 6,
                                     state='disabled',
                                     command = self.stop_receiving)

        self.stop_button.grid(column=3, row=12)
        x = 0
        y = 1
        for i, label in enumerate(self.variables):
            label = tk.Label(self,
                                text = format("Variable " + str(i)),
                                font = self.btn_font,
                                padx = 10)
            label.grid(column=x, row=y)
            y += 1

        x = 1
        y = 0
        for i, label in enumerate(self.sensors):
            sensor = tk.Label(self,
                                text = format("Sensor " + str(i)),
                                font = self.btn_font,
                                padx = 20,
                                relief = tk.RIDGE)
            sensor.grid(column=x, row=y)
            x += 1

        x = 1
        y = 1
        for i, field in enumerate(self.fields):
            field = tk.Entry(self,
                             width=10,
                             text=format("field val " + str(i)),
                             font=self.btn_font,
                             state='disabled')
            field.grid(column=x, row=y)
            y += 1
            if y > len(self.variables):
                y = 1
                x += 1

    #----Proxy to call the start receiving method using a delay and set the corresponding buttons to normal/disabled.
    def start_receiving(self):
        self.start_button.config(state='disabled')
        self.stop_button.config(state='normal')

        self.after(self.DELAY, startreceiving, self.DELAY)

    #----Proxy to call the stop receiving method using a delay and set the corresponding buttons to normal/disabled.
    def stop_receiving(self):
        self.stop_button.config(state='disabled')
        self.start_button.config(state='normal')

        self.after(self.DELAY, startreceiving(False), self.DELAY)
        self.after(self.DELAY, startsending(False), self.DELAY)

    #----Proxy to call the start sending method using a delay.
    def send_message(self):
        self.after(self.DELAY, startsending, self.DELAY)

app = App()
app.master.title('ESDR')
app.master.geometry('640x480')
app.mainloop()

Ответы [ 2 ]

0 голосов
/ 13 декабря 2018

У вас есть несколько проблем:

  1. Функция after вызывается неправильно.Один пример: self.after(self.DELAY, startreceiving(False), self.DELAY).Прежде всего - он звонит startreceiving немедленно , что не то, что вы хотите.Во-вторых, третий и последующие аргументы after предоставляются в качестве аргументов функции обратного вызова.Таким образом, вы отправляете self.DELAY в качестве аргумента startreceiving, но этот аргумент должен быть логическим, поскольку вы его закодировали.

  2. Функция after не должна входить в бесконечный цикл, поскольку он крадет управление у tkinter.Вместо этого (как указывал @ scotty3785), вы должны либо создать новый поток для операции, либо сделать короткий обратный вызов after и затем «перепланировать» себя.

AsДля меня было забавное учебное упражнение, я переработал ваш код с потоком для отправителя и получателя.Включены некоторые комментарии в комментариях.

from threading import Thread
import time
import socket
import select
import tkinter as tk
import tkinter.font as tkFont

UDP_IP = "127.0.0.1"
UDP_PORT = 5005

class Sender(Thread):
    MESSAGE = b"Hello, world"
    def __init__(self, sock):
        # Call Thread constructor
        super().__init__()
        self.sock = sock
        self.keep_running = True

    def stop(self):
        # Call this from another thread to stop the sender
        self.keep_running = False

    def run(self):
        # This will run when you call .start method
        while self.keep_running:
            print("Sending Message.")
            try:
                self.sock.sendto(self.MESSAGE, (UDP_IP, UDP_PORT))
                time.sleep(0.5) # REMOVE ME: Just to slow things down a bit for debugging
            except socket.error as err:
                print("Error from sending socket {}".format(err))
                break


class Receiver(Thread):
    def __init__(self, sock):
        # Call Thread constructor
        super().__init__()
        self.sock = sock
        self.keep_running = True

    def stop(self):
        # Call this from another thread to stop the receiver
        self.keep_running = False

    def run(self):
        # This will run when you call .start method
        while self.keep_running:
            # We use select here so that we are not *hung* forever in recvfrom.
            # We'll wake up every .5 seconds to check whether we should keep running
            rfds, _wfds, _xfds = select.select([self.sock], [], [], 0.5)
            if self.sock in rfds:
                try:
                    data, addr = self.sock.recvfrom(1024)
                    print("received message:", data)
                    print("from: ", addr)
                except socket.error as err:
                    print("Error from receiving socket {}".format(err))
                    break


class App(tk.Frame):
    STRIDE = 8
    DELAY = 100

    # pythonic list comprehensions equivalent to your previous loops
    variables = [i for i in range(10)]
    sensors = [i for i in range(3)]
    fields = [i for i in range(len(sensors) * len(variables))]

    def __init__(self, sock, master=None):
        # Call superclass constructor
        super().__init__(master)
        self.sock = sock
        self.sender = None
        self.receiver = None
        self.grid()
        self.create_widgets()
        self.update()

    #---- Create the GUI Layout ----
    def create_widgets(self):
        self.btn_font = tkFont.Font(family="Helvetica", size=12, weight='bold')
        self.gui_buttons = []
        # Buttons renamed for orthogonality
        self.sstart_button = tk.Button(self,
                                     text = format("Begin Sending."),
                                     font = self.btn_font,
                                     relief = tk.RIDGE,
                                     pady = 4,
                                     command = self.start_sending)
        self.sstart_button.grid(column=2, row=11)

        # Adding a stop button for the sender too
        self.sstop_button = tk.Button(self,
                                     text = format("Stop Sending."),
                                     font = self.btn_font,
                                     relief = tk.RIDGE,
                                     pady = 4,
                                     padx = 6,
                                     state='disabled',
                                     command = self.stop_sending)

        self.sstop_button.grid(column=2, row=12)

        self.rstart_button = tk.Button(self,
                                 text = format("Begin Receiving."),
                                 font = self.btn_font,
                                 relief = tk.RIDGE,
                                 pady = 4,
                                 command = self.start_receiving)
        self.rstart_button.grid(column=3, row=11)
        self.rstop_button = tk.Button(self,
                                     text = format("Stop Receiving."),
                                     font = self.btn_font,
                                     relief = tk.RIDGE,
                                     pady = 4,
                                     padx = 6,
                                     state='disabled',
                                     command = self.stop_receiving)

        self.rstop_button.grid(column=3, row=12)
        x = 0
        y = 1
        for i, label in enumerate(self.variables):
            label = tk.Label(self,
                                text = format("Variable " + str(i)),
                                font = self.btn_font,
                                padx = 10)
            label.grid(column=x, row=y)
            y += 1

        x = 1
        y = 0
        for i, label in enumerate(self.sensors):
            sensor = tk.Label(self,
                                text = format("Sensor " + str(i)),
                                font = self.btn_font,
                                padx = 20,
                                relief = tk.RIDGE)
            sensor.grid(column=x, row=y)
            x += 1

        x = 1
        y = 1
        for i, field in enumerate(self.fields):
            field = tk.Entry(self,
                             width=10,
                             text=format("field val " + str(i)),
                             font=self.btn_font,
                             state='disabled')
            field.grid(column=x, row=y)
            y += 1
            if y > len(self.variables):
                y = 1
                x += 1

    def mainloop(self, *args):
        # Overriding mainloop so that we can do cleanup of our threads
        # *If* any arguments were provided, we would pass them on to Tk.frame
        super().mainloop(*args)

        # When main loop finishes, shutdown sender and/or receiver if necessary
        if self.sender:
            self.sender.stop()
        if self.receiver:
            self.receiver.stop()


    #----Start the receiver thread
    def start_receiving(self):
        self.rstart_button.config(state='disabled')
        self.rstop_button.config(state='normal')
        # Create and start receiver thread
        self.receiver = Receiver(self.sock)
        self.receiver.start()

    #----Stop the receiver
    def stop_receiving(self):
        self.rstop_button.config(state='disabled')
        self.rstart_button.config(state='normal')
        self.receiver.stop()
        self.receiver.join()
        self.receiver = None

    #----Start the sender thread
    def start_sending(self):
        self.sstart_button.config(state='disabled')
        self.sstop_button.config(state='normal')
        self.sender = Sender(self.sock)
        self.sender.start()

    #----Stop the sender
    def stop_sending(self):
        self.sstop_button.config(state='disabled')
        self.sstart_button.config(state='normal')
        self.sender.stop()
        self.sender.join()
        self.sender = None

def main():
    # Got rid of sock as global variable
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind((UDP_IP, UDP_PORT))
    app = App(sock)
    app.master.title('ESDR')
    app.master.geometry('640x480')
    app.mainloop()

if __name__ == '__main__':
    main()
0 голосов
/ 13 декабря 2018

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

Я бы использовал метод tkinter .after для планирования моих функций отправки / получениядля периодического запуска.

import tkinter as tk

sending_enabled = False

def send_message():
    if sending_enabled:
        print("Sending Message")
        root.after(500,send_message)

def receive_messages():
    print("Getting Messages")
    root.after(1000,recieve_messages)


def start_sending():
    global sending_enabled
    if not sending_enabled:
        root.after(500,send_message)
        sending_enabled = True

def stop_sending():
    global sending_enabled
    sending_enabled = False


root = tk.Tk()

startButton = tk.Button(root,text="Start",command=start_sending)
startButton.grid()
stopButton = tk.Button(root,text="Stop",command=stop_sending)
stopButton.grid()
root.after(1000,receive_messages)

root.mainloop()

Функция receive_message запланирована на первый запуск через 1000 мс после запуска программы, а затем будет вызываться каждые 1000 мс

Функция send_message является первойзапланировано запустить 1000 мс после нажатия кнопки запуска.Затем он будет продолжать вызывать себя, пока флаг sending_enabled не будет установлен в значение false с помощью функции stop_sending.

Обратите внимание, что ни у функций отправки, ни у функции нет циклов while.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...