title: Twisted TCP server with a Tkinter GUI
body: <p>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.</p>
  
  <pre><code>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) &gt; 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 = '&lt;%s (you)&gt; %s\r\n' % (self.name, data)
          to_else = '&lt;%s&gt; %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('&lt;server&gt; %s\r\n' % (msg))
          self.write('[CHAT] - &lt;server&gt; %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('&lt;Return&gt;',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()
  </code></pre>
  
  <p>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 </p>
  
  <pre><code>telnet localhost 4444
  </code></pre>
  
  <p>to run the client. </p>