2

最近我一直在尝试使用 Twisted(python 库)来尝试制作 TCP 聊天服务器/客户端。我的服务器运行良好,但是当我尝试向服务器添加基于 Tkinter 的 GUI 时,事情变得很奇怪。一旦用户连接到服务器,就会向 GUI 发送一条消息。然而,在此过程中的某个地方出现了问题并出现了一个冗长的错误,其要点是 Tkinter 由于无限循环而耗尽了堆栈空间。我把我的代码放在下面。我遇到问题的函数是 App.write(text) 和 User.connectionMade(*args) 以及 User 类中尝试将文本打印到 GUI 的任何其他函数。

from twisted.internet.protocol import ServerFactory, Protocol
from twisted.internet import reactor
from os import path
import yaml
import threading
from Tkinter import *

__version__ = ''
__author__ = ''

class User(Protocol):
    def connectionMade(self,*args):
        self.gui.write('New connection from %s' % (self.addr.host))
        self.transport.write('Username: ')

    def connectionLost(self,reason):
        self.gui.write('Connection lost with %s' % (self.addr.host))
        if not self.name == None:
            msg = '%s has disconnected\r\n' % (self.name)
            self.gui.write(msg.rstrip())
            self.toAll(msg)
            del self.users[self.name]

    def dataReceived(self,data):
        if data == '\x08':
            if len(self.text) > 0:
                self.text = self.text[:-1]
            return 
        elif not data.endswith('\r\n'):
            self.text += data
            return
        if self.name == None:
            self.setName(self.text)
        else:
            self.handle(self.text)
        self.text = ''

    def handle(self,data):
        if not data.startswith('/'):
            self.chat(data)
        else:
            self.gui.write('%s executed command %s' % (self.name, data))
            if data in ['/help','/h']: self.cmdHelp()
            elif data in ['/list','/l']: self.userList()
            elif data in ['/motd','/m']: self.sendMotd()
            elif data in ['/ping','/p']: self.transport.write('Pong!\r\n')
            else: self.transport.write('Unrecognized command %s\r\n' % (data))

    def cmdHelp(self):
        x = ['\r\nCOMMANDS:',\
             '/motd,/m - Display the MOTD',\
             '/list,/l - Display a list of online users',\
             '/help,/h - Display this list\r\n']
        for item in x:
            self.transport.write(item+'\r\n')

    def sendMotd(self):
        self.transport.write('\r\nMOTD: %s\r\n\r\n' % (self.motd))

    def userList(self):
        self.transport.write('\r\nCURRENTLY ONLINE: server,%s\r\n\r\n' % (','.join(item for item in self.users)))

    def setName(self,name):
        if self.users.has_key(name) or name.lower() == 'server':
            self.transport.write('That username is in use!\r\nUsername: ')
        elif ' ' in name:
            self.transport.write('No spaces are allowed in usernames!\r\nUsername: ')
        elif name == '':
            self.transport.write('You must enter a username!\r\nUsername: ')
        else:
            self.users[name] = self
            self.name = name
            self.gui.write('New user registered as %s' % (name))
            self.toAll('%s has connected' % (self.name))
            self.transport.write('\nSuccessfully logged in as %s\r\n\r\n' % (name))
            self.sendMotd()

    def toAll(self,msg):
        for name,protocol in self.users.iteritems():
            if not protocol == self:
                protocol.transport.write(msg)

    def chat(self,data):
        to_self = '<%s (you)> %s\r\n' % (self.name, data)
        to_else = '<%s> %s\r\n' % (self.name, data)
        self.gui.write('[CHAT] - %s' % (to_else.rstrip()))
        self.transport.write(to_self)
        self.toAll(to_else)

    def __init__(self,addr=None,users=None,motd=None,master=None):
        self.name = None
        self.addr = addr
        self.users = users
        self.motd = motd
        self.text = ''
        self.factory = master
        self.gui = self.factory.app
        self.kicked = False

class App(Frame):
    def write(self,text):
        self.display.insert(END,text+'\n')

    def clear(self,event=None):
        self.display.delete(1.0,END)

    def userList(self):
        self.write('Currently online: server,%s' % (','.join(item for item in self.factory.users)))

    def handle(self,event=None):
        msg = self.entry.get()
        self.entry.delete(0,END)
        if not msg.startswith('/'): self.send(msg)
        elif msg in ['/cls','/clear','/clr','/c']: self.clear()
        elif msg in ['/list','/l']: self.userList()
        elif msg in ['/exit']: self.kill()
        else: self.write('Unrecognized command \'%s\'' % (msg))

    def send(self,msg,event=None):
        for item in self.factory.users: self.factory.users[item].transport.write('<server> %s\r\n' % (msg))
        self.write('[CHAT] - <server> %s' % (msg))

    def kill(self):
        self.write('Stopping server...')
        reactor.stop()
        self.write('GUI says guidbye! :(')
        self.quit()

    def __init__(self,master,factory):
        Frame.__init__(self,master)
        self.grid(row=0,sticky=N+E+S+W)
        self.columnconfigure(0,weight=1)
        self.rowconfigure(0,weight=1)

        self.display = Text(self)
        self.display.grid(row=0,sticky=N+E+S+W)
        self.yscroll = Scrollbar(self,command=self.display.yview)
        self.yscroll.grid(row=0,column=1,sticky=N+S)
        self.display.config(yscrollcommand=self.yscroll.set)
        self.entry = Entry(self)
        self.entry.grid(row=1,sticky=E+W)
        self.master = master

        self.master.wm_title('TCP Chat Server v%s' % (__version__))
        self.factory = factory

        self.motd = ''
        self.port = 0

        self.entry.bind('<Return>',self.handle)
        self.master.protocol('WM_DELETE_WINDOW',self.kill)

        self.write('TCP Chat Server v%s' % (__version__))
        self.write('by %s\n' % (__author__))
        self.write('Server currently running on port %s' % (self.factory.port))

class Main(ServerFactory):
    def buildProtocol(self,addr):
        return User(addr=addr,users=self.users,motd=self.motd,master=self)

    def start(self):
        self.root = Tk()
        self.root.columnconfigure(0,weight=1)
        self.root.rowconfigure(0,weight=1)

        self.app = App(self.root,self)
        self.app.mainloop()

    def __init__(self,motd,port):
        self.users = {}
        self.motd = motd
        self.port = port

        self.tk_thread = threading.Thread(target=self.start)
        self.tk_thread.start()


if not path.isfile('config.yml'):
    open('config.yml','w').write('port: 4444\nmotd: No motd set!')

with open('config.yml','r') as f:
    dump = yaml.load(f.read())
    motd = dump['motd']
    port = dump['port']

reactor.listenTCP(port,Main(motd,port))
reactor.run()

其他一切都按预期运行,当我注释掉 App.write('') 语句时,程序按预期运行(没有 GUI 和服务器端消息)。我一直在使用 Windows 来测试程序,所以我使用

telnet localhost 4444

运行客户端。

4

1 回答 1

3

Twisted 对 Tkinter 有一些专门的支持

from Tkinter import *
from twisted.internet import tksupport, reactor

root = Tk()

# Install the Reactor support
tksupport.install(root)

# at this point build Tk app as usual using the root object,
# and start the program with "reactor.run()", and stop it
# with "reactor.stop()".
于 2013-08-16T03:05:09.227 回答