Reputation: 484
recently I've been experimenting with Twisted (python library) in an attempt to make a TCP chat server/client. I had the server running nicely but when I tried to add a Tkinter-based GUI to the server, things got weird. As soon as a user connects to the server, a message is sent to the GUI. However, somewhere along the way something goes wrong and a long-winded error comes up, the gist of which is that Tkinter ran out of stack space because of an infinite loop. I've put my code below. The function that I am having trouble with is App.write(text) and User.connectionMade(*args) as well as any other function in the User class that attempts to print text to the 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()
Everything else is running as expected, and when I comment out the App.write('') statements, the program runs as expected (sans GUI and server-side messages). I've been using Windows to test the program so I use
telnet localhost 4444
to run the client.
Upvotes: 2
Views: 3011
Reputation: 304175
Twisted has some specialised support for 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()".
Upvotes: 3