最近、TCP チャット サーバー/クライアントを作成するために Twisted (Python ライブラリ) を試しています。サーバーは問題なく稼働していましたが、Tkinter ベースの GUI をサーバーに追加しようとすると、状況がおかしくなりました。ユーザーがサーバーに接続するとすぐに、メッセージが GUI に送信されます。ただし、途中で何かがうまくいかず、長々としたエラーが発生します。その要点は、無限ループのために Tkinter がスタック領域を使い果たしたことです。私は自分のコードを以下に置きました。私が問題を抱えている関数は、App.write(text) と User.connectionMade(*args) と、テキストを GUI に出力しようとする User クラスのその他の関数です。
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
クライアントを実行します。