Tkinter 通知板与套接字消息 - 奇怪的行为
我在这个论坛上已经使用了一段时间,但这是我第一次提问,因为我一直找不到解决我遇到的问题的好办法,而且我希望这个问题对其他人也有帮助。
我正在实现一个简单的通知板,也就是一个可以显示来自套接字连接的消息的窗口。这个通知板会把最新收到的消息用红色显示,旧的消息用蓝色显示,最多显示十条。当客户端发送的消息是'Q'时,连接会结束,通知板也会被销毁。
我使用了Tkinter、线程和套接字,但运行起来不太流畅(通知板刷新需要一些时间)。我想到了一些可能的问题:处理连接的线程没有关闭;更新窗口是通过销毁并重新创建顶层窗口来完成的。不幸的是,我不太明白这些问题是否是导致问题的根源。
这是客户端的代码,非常简单:
#!/usr/bin/env python
import socket
HOST = '' # Symbolic name meaning the local host
PORT = 24073 # Arbitrary non-privileged port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST,PORT))
while True:
message = raw_input('Enter your command (Q=quit): ')
s.send(message)
reply = s.recv(1024)
if reply=='Q':
print 'Request to disconnect was received.'
break
else :
print reply
s.close()
这是服务器的代码。服务器实现了一个类来处理通知板的特性,还有一个线程用于套接字连接,最后是包含mainloop()
的主要部分。
#!/usr/bin/env python
import socket
import threading
from Tkinter import *
from datetime import datetime
### Class definition
class NoticationsBoard() :
def __init__(self, title):
self.messages = []
self.toplevel = None
self.title = title
self.backgroundColor = 'black'
self.textColor = 'blue'
self.textColorFirst = 'red'
self.boardSize = '200x250+0+0'
self.listsize = 10
def createBoard(self):
self.toplevel = Toplevel()
self.toplevel.title(self.title)
self.toplevel.configure(background='black')
self.toplevel.geometry(self.boardSize)
def publish(self, message):
self.addToList(message)
self.displayList()
def addToList(self, msg):
if len(self.messages) == self.listsize:
self.messages.pop(0)
timestamp = datetime.utcnow().strftime('%H:%M:%S')
newMessage = (msg, timestamp)
self.messages.append(newMessage)
def displayList(self):
# Destroy and create the window (is it really necessary?)
if self.toplevel is not None :
self.toplevel.destroy()
self.createBoard()
# create labels for all the messages in the list
index = 1
for m, t in self.messages :
color = self.textColor
if index == len(self.messages) :
color = self.textColorFirst
label = Label(self.toplevel, text=m, height=0, width=100, fg=color, anchor=W)
label.grid(row=0,column=1)
label.configure(background=self.backgroundColor)
label.pack(side='bottom')
index = index +1
####### Run
def receiveMessages(newsboard) :
print '===== Inside receiveMessages ======'
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
try:
s.bind((HOST, PORT))
except socket.error , msg:
print 'Bind failed. Error code: ' + str(msg[0]) + 'Error message: ' + msg[1]
sys.exit()
print 'Socket bind complete'
s.listen(1)
print 'Socket now listening on port', PORT
# Accept the connection once
(conn, addr) = s.accept()
print 'Connected with ' + addr[0] + ':' + str(addr[1])
stored_data = ''
while True:
# RECEIVE DATA
data = conn.recv(1024)
# PROCESS DATA
if data == 'Q' :
print 'Client wants to disconnect.'
reply = 'Q'
conn.send(reply)
break
else :
print data
newsboard.publish(data)
reply = 'Message received:' + data
conn.send(reply)
print 'Close connection.'
conn.close()
board.destroy()
HOST = '' # Symbolic name meaning the local host
PORT = 24073 # Arbitrary non-privileged port
app = Tk()
app.title("GUI main")
board = NoticationsBoard('Notifications')
t = threading.Thread(target=receiveMessages, args = (board,))
t.start()
app.update() # Not sure what it does and if it is necessary
app.mainloop()
我使用的是Python 2.7.5。
最后,虽然这只是小事,我试图在每条消息左边显示时间戳,并用不同的颜色显示。看起来在同一个标签上显示不同颜色的文字是不可能的,所以我在for
循环中创建了其他标签来显示时间戳。我尝试使用.grid(column=0)
和.grid(column=1)
把时间戳和消息标签并排显示,但它们并不是并排显示,而是上下排列,我还没弄明白为什么。
如你所见,我不是一个熟练的程序员,绝对是Python的新手……
感谢提前给我建议的人,希望这个问题对很多人都有帮助。
1 个回答
好的,我好像找到了解决办法,主要是参考了其他人的问题、建议和代码。外观上可能有一些小差别。
在图形界面(GUI)部分,最明显的变化是我预先加载了所有的标签,然后只修改文本。
在多线程的部分,这个完全变了。请看下面的代码。
#!/usr/local/bin/python
try:
import Tkinter
except ImportError:
import tkinter as Tkinter
import time
import threading
import random
import Queue
import socket
import sys
from datetime import datetime
class GuiPart:
def __init__(self, master, queue):
self.queue = queue
# GUI stuff
self.labelArray = []
self.messages = []
# Set up the GUI
self.master = master
self.backgroundColor = 'black'
self.listsize = 10
master.config(bg=self.backgroundColor)
self.board = Tkinter.LabelFrame(self.master, text='Notification Board', bg='Black', fg='Yellow', labelanchor='n', width=170)
self.initLabels()
self.board.pack()
def initLabels(self) :
self.textColorTime = 'cyan'
self.textColorMessage = 'orange'
colorTime = 'blue'
colorMessage = 'red'
for i in range(0,self.listsize):
la = Tkinter.Label(self.board, height=0, width=10, bg=self.backgroundColor, fg=colorTime, anchor=Tkinter.W)
lb = Tkinter.Label(self.board, height=0, width=160, bg=self.backgroundColor, fg=colorMessage)
la.grid(row=i,column=0, sticky=Tkinter.W)
lb.grid(row=i,column=1, sticky=Tkinter.W)
self.labelArray.append((la, lb))
colorTime = self.textColorTime
colorMessage = self.textColorMessage
self.initList()
self.displayList()
def initList(self):
for i in range(0, self.listsize):
t = ''
m = ''
self.messages.append((t,m))
def processIncoming(self):
while self.queue.qsize():
try:
msg = self.queue.get(0)
self.processMessage(msg)
except Queue.Empty:
pass
def processMessage(self, message):
timestamp = datetime.utcnow().strftime('%H:%M:%S')
self.publish(timestamp, message)
def publish(self, msg1, msg2):
self.addToList(msg1, msg2)
self.displayList()
def addToList(self, msg1, msg2):
if len(self.messages) == self.listsize:
self.messages.pop(0)
if (msg1 == None):
msg1 = datetime.utcnow().strftime('%H:%M:%S')
newMessage = (msg1, msg2)
self.messages.append(newMessage)
def displayList(self):
index = self.listsize -1
for t, m in self.messages :
la, lb = self.labelArray[index]
la.config(text=t)
lb.config(text=m)
index = index -1
def destroy(self):
self.master.destroy()
class ThreadedClient:
def __init__(self, master):
self.master = master
# Create the queue
self.queue = Queue.Queue()
# Define connection parameters
self.conn = None
self.bindError = False
# Set up the GUI part
self.gui = GuiPart(master, self.queue)
# Set up the thread to do asynchronous I/O
self.running = True
self.commThread = threading.Thread(target=self.workerThreadReceive)
self.commThread.daemon = True
self.commThread.start()
# Start the periodic call in the GUI to check if the queue contains anything
self.periodicCall()
def periodicCall(self):
if not self.running:
self.killApplication()
else :
self.gui.processIncoming()
self.master.after(100, self.periodicCall)
def workerThreadReceive(self):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try :
s.bind((HOST, PORT))
except socket.error as msg :
print 'Bind failed. Error code: ' + str(msg[0]) + ' Error message: ' + str(msg[1])
self.running = False
self.bindError = True
return
s.listen(1)
(self.conn, self.addr) = s.accept()
while self.running :
data = self.conn.recv(1024)
if data == 'Q' :
self.conn.sendall('Q')
self.running = False
else :
self.queue.put(data)
reply = 'ACK'
self.conn.sendall(reply)
if self.conn is not None:
self.conn.close()
def killApplication(self):
self.running = False
if (self.conn is None) and (not self.bindError) :
sfake = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sfake.connect((HOST,PORT))
sfake.sendall('Q')
sfake.close()
self.gui.destroy()
sys.exit()
HOST = '' # Symbolic name meaning the local host
PORT = 24073 # Arbitrary non-privileged port
root = Tkinter.Tk()
client = ThreadedClient(root)
root.protocol("WM_DELETE_WINDOW", client.killApplication)
root.mainloop()
客户端的部分和问题中提到的是一样的。
我不确定我的代码是否是最优雅的(好吧,老实说,肯定不是!),但看起来是能完成任务的。不过,我还是想听听你们的反馈,因为我知道我可能忽略了很多问题,还有很多地方可以做得更简单。:)