[英]Python 3: UDP Packet send/receive in a Tkinter gui
因此,我能夠輕松編寫一個小腳本來偵聽某個IP /端口上的UDP數據包,但是我正在努力將其實現到Tkinter GUI中。
每當我嘗試使用由按鈕觸發的True while:無限循環時,gui應用程序就會崩潰。 我做了一些進一步的研究,並閱讀了一些有關使用延遲的信息,但是我無法使其正常工作。 我嘗試過將while循環放到調用startreceiving函數的代理函數中,但是它也會使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()
按照我對您問題的評論進行操作。 這是我如何解決問題的簡單示例(我通常會做更多的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毫秒運行。 然后它將繼續自行調用,直到stop_sending
函數將sending_enabled
標志設置為false stop_sending
。
注意,發送或接收函數都沒有while循環。
您在這里遇到幾個問題:
after
函數未正確調用。 一個示例: self.after(self.DELAY, startreceiving(False), self.DELAY)
。 首先-它立即調用startreceiving
,這不是您想要的。 第二, after
的第三個和后續參數作為回調函數的參數提供。 因此,您要發送self.DELAY
作為startreceiving
的參數,但是該參數應為編碼后的布爾值。
after
函數不應進入無限循環,因為這會從tkinter竊取控制權。 而是(如@ scotty3785所指出的),您應該為該操作創建一個新線程,或者使after
回調變短,然后讓它自己“重新計划”。
作為我自己的一項有趣的學習練習,我使用了一個分別針對發送者和接收者的線程對您的代碼進行了重新設計。 注釋中包含一些注釋。
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.