Skip to main content
 首页 » 编程设计

python之带有 Tkinter GUI 的 Twisted TCP 服务器

2024年08月28日28bluestorm

最近我一直在尝试使用 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 

运行客户端。

请您参考如下方法:

Twisted 有一些 specialised support用于 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()".