代码之家  ›  专栏  ›  技术社区  ›  Justin

python 3:tkinter gui中的udp包发送/接收

  •  1
  • Justin  · 技术社区  · 6 年前

    所以,我可以很容易地编写一个小脚本来监听某个IP/端口上的UDP数据包,但我正在努力将其实现到Tkinter图形用户界面中。

    每当我尝试使用由按钮触发的无限while true:循环时,gui应用程序就会崩溃。我做了进一步的研究,读了一些关于使用延迟的文章,但是我无法让它正常工作。我试过将while循环放入调用startreceiveing函数的代理函数中,但它也会使gui崩溃。下面的代码将启动一个gui并运行我当前的问题。

    最终的问题是:如何让按钮触发事件开始发送数据包,同时仍然能够接受按钮事件来开始和停止接收数据包?

    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 回复  |  直到 6 年前
        1
  •  0
  •   scotty3785    6 年前

    继续我对你问题的评论。 下面是一个简单的例子,说明我如何处理这些问题(我通常会做更多的oop,但这是一个简单的例子)。

    我将使用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 功能。

    注意,send或receive函数都没有while循环。

        2
  •  0
  •   Gil Hamilton    6 年前

    这里有几个问题:

    1. 这个 after 函数未正确调用。一个例子: self.after(self.DELAY, startreceiving(False), self.DELAY) . 首先——它在召唤 startreceiving 立即 这不是你想要的。第二,第三和随后的论点 之后 作为回调函数的参数提供。所以你要派 self.DELAY 作为一个论点 星际接收 但是这个参数应该是一个布尔值,因为你已经编码了它。

    2. 之后 函数不应该进入无限循环,因为它会从Tkinter窃取控制权。相反(正如@scotty3785所指出的),您应该为该操作创建一个新线程,或者使 之后 短时间回拨,然后让它“重新安排”自己。

    作为一个有趣的学习练习,我用一个线程为发送者和接收者重新编写了您的代码。在注释中包括一些注释。

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