来自套接字的消息的Tkinter通知板 - 奇怪的行为

时间:2014-03-16 23:15:09

标签: python multithreading sockets 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 个答案:

答案 0 :(得分:0)

好吧,似乎我通过接受其他人的一些问题,建议和代码找到了解决方案。可能在外观上几乎没有差异。 在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()

客户端与问题中的相同。

我不确定我的代码是最优雅的代码(好吧,让它说出来......它不是!),但它似乎可以完成这项工作。不过,我想得到你的反馈意见,因为我确信我已经忽略了很多问题,并且事情本可以更容易地完成。 :)