简体   繁体   English

Python 3:在Tkinter gui中发送/接收UDP数据包

[英]Python 3: UDP Packet send/receive in a Tkinter gui

So, I am able to easily write a small script to listen for UDP packets at a certain IP/Port, but I'm struggling implementing it into a Tkinter GUI. 因此,我能够轻松编写一个小脚本来侦听某个IP /端口上的UDP数据包,但是我正在努力将其实现到Tkinter GUI中。

Whenever I try using an infinite while True: loop triggered by a button, the gui application crashes. 每当我尝试使用由按钮触发的True while:无限循环时,gui应用程序就会崩溃。 I did some further research and read a little about using delays, but I'm unable to get it to work properly. 我做了一些进一步的研究,并阅读了一些有关使用延迟的信息,但是我无法使其正常工作。 I've tried putting the while loop in the proxy function which calls the startreceiving function, but it too crashes the gui. 我尝试过将while循环放到调用startreceiving函数的代理函数中,但是它也会使gui崩溃。 The below code gets a gui up and running with my current issues. 下面的代码获取了一个gui并运行了我当前的问题。

The ultimate question: How can I get a button to trigger an event to begin sending packets, while still being able to accept button events to start and stop receiving packets? 最终的问题:如何获得一个按钮来触发事件以开始发送数据包,同时仍然能够接受按钮事件来开始和停止接收数据包?

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

Follow on from my comments on your question. 按照我对您问题的评论进行操作。 Here is a quick example of how I might approach the problems (I'd usually do more OOP but this is a quick example). 这是我如何解决问题的简单示例(我通常会做更多的OOP,但这是一个简单的示例)。

I'd use the tkinter .after method to schedule my send/receive functions to be run periodically. 我将使用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()

The receive_message function is scheduled to first be run 1000ms after the program starts and then will call itself every 1000ms 按计划, receive_message函数首先在程序启动后1000毫秒运行,然后每1000毫秒调用一次

The send_message function is first scheduled to run 1000ms after the start button is pressed. 首先将send_message函数安排为在按下开始按钮后1000毫秒运行。 It will then continue to call itself until the sending_enabled flag is set to false by the stop_sending function. 然后它将继续自行调用,直到stop_sending函数将sending_enabled标志设置为false stop_sending

Note that neither the send or receive functions have while loops in them. 注意,发送或接收函数都没有while循环。

You have several problems here: 您在这里遇到几个问题:

  1. The after function isn't being called correctly. after函数未正确调用。 One example: self.after(self.DELAY, startreceiving(False), self.DELAY) . 一个示例: self.after(self.DELAY, startreceiving(False), self.DELAY) First of all -- it's calling startreceiving immediately which is not what you want. 首先-它立即调用startreceiving 这不是您想要的。 Second, the 3rd and subsequent arguments to after are provided as arguments to the callback function. 第二, after的第三个和后续参数作为回调函数的参数提供。 So you're sending self.DELAY as an argument to startreceiving but that argument should be a boolean as you have it coded. 因此,您要发送self.DELAY作为startreceiving的参数,但是该参数应为编码后的布尔值。

  2. An after function shouldn't enter an infinite loop as that steals control from tkinter. after函数不应进入无限循环,因为这会从tkinter窃取控制权。 Instead (as @scotty3785 pointed out), you should either create a new thread for the operation, or make the after callback short and have it "reschedule" itself afterward. 而是(如@ scotty3785所指出的),您应该为该操作创建一个新线程,或者使after回调变短,然后让它自己“重新计划”。

As a fun learning exercise for myself, I reworked your code with a thread each for sender and receiver. 作为我自己的一项有趣的学习练习,我使用了一个分别针对发送者和接收者的线程对您的代码进行了重新设计。 Included some annotations in the comments. 注释中包含一些注释。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM