Over time, there have been various questions regarding getting sockets up and running with wxPython, either as a server or as a client. This recipe intends to show how to create a simple line-echo server. It uses the asyncore library to handle much of the socket stuff, and can either use a wx.Timer() object in the main GUI thread to poll the socket, or can use a secondary thread to handle the standard asyncore loop.
What Objects are Involved
- Threads or wx.Timer
- New event types
We use a fairly standard asyncore.dispatcher subclass as the server, with 4 specialized arguments; host, port, mainwindow, and factory. host and port should be fairly obvious. mainwindow is where we send our logging messages to, via mainwindow.LogString(). In this example, LogString has been written to be aware of when there are multiple threads being run, and automatically uses wx.PostEvent as a thread-safe mechanism to process the resulting logging event. The factory argument specifies what the actual connection objects should be created from, and should take both the connection and the mainwindow
The DispatcherConnection is a specialized base class that derives from asyncore.dispatcher_with_send to allow for simple handling of outgoing buffers, and includes an overriding of the base asyncore.log and asyncore.log_info to use the maindindow.LogString() method.
The LineEchoConnection implements a simple line-based echo server.
Finally, the MainWindow implements a LogString method that handles the writing of the log messages, starting up either the polling timer or the threaded poller, etc.
For handling both incoming and outgoing data, we could have also used the standard library asynchat.async_chat, which similarly handles both incoming and outgoing buffers, and supports both a simple line-ending detection and a "read x bytes" reading mechanism. We could have also used Twisted, which is a very capable client/server architecture, that has functionality to tie in with wxPython.
Users shold also be aware that using threads correctly can be difficult, I (Josiah Carlson) include a threaded option for the poller strictly as a sample, but suggest that whenever possible users stick to non-threaded methods.
1 import wx 2 import wx.lib.newevent 3 import socket 4 import asyncore 5 import threading 6 import Queue 7 8 to_network = Queue.Queue() 9 LogEvent, EVT_LOG_EVENT = wx.lib.newevent.NewEvent() 10 11 #for high bandwidth applications, you are going to want to handle the 12 #out_buffer via a different mechanism, perhaps similar to asynchat or 13 #otherwise 14 class DispatcherConnection(asyncore.dispatcher_with_send): 15 def __init__(self, connection, mainwindow): 16 self.mainwindow = mainwindow 17 asyncore.dispatcher_with_send.__init__(self, connection) 18 def writable(self): 19 return bool(self.out_buffer) 20 def handle_write(self): 21 self.initiate_send() 22 def log(self, message): 23 self.mainwindow.LogString(message, sock=self) 24 def log_info(self, message, type='info'): 25 if type != 'info': 26 self.log(message) 27 def handle_close(self): 28 self.log("Connection dropped: %s"%(self.addr,)) 29 self.close() 30 31 #implement your client logic as a subclass 32 33 class LineEchoConnection(DispatcherConnection): 34 inc_buffer = '' 35 def handle_read(self): 36 self.inc_buffer += self.recv(512) 37 while '\n' in self.inc_buffer: 38 snd, self.inc_buffer = self.inc_buffer.split('\n', 1) 39 snd += '\n' 40 self.log("Line from %s: %r"%(self.addr, snd)) 41 self.send(snd) 42 43 class DispatcherServer(asyncore.dispatcher): 44 def __init__(self, host, port, mainwindow, factory=LineEchoConnection): 45 self.mainwindow = mainwindow 46 self.factory = factory 47 asyncore.dispatcher.__init__(self) 48 self.create_socket(socket.AF_INET, socket.SOCK_STREAM) 49 self.bind((host, port)) 50 self.listen(5) 51 def handle_accept(self): 52 connection, info = self.accept() 53 self.mainwindow.LogString("Got connection: %s"%(info,), sock=self) 54 self.factory(connection, self.mainwindow) 55 56 def loop(): 57 while 1: 58 while not to_network.empty(): 59 message = to_network.get() 60 #process and handle message 61 # 62 #remember to validate any possible 63 #socket objects recieved from the GUI, 64 #they could already be closed 65 asyncore.poll(timeout=.01) 66 67 class MainWindow(wx.Frame): 68 def __init__(self, host, port, threaded=0): 69 wx.Frame.__init__(self, None, title="Sample echo server") 70 71 #add any other GUI objects here 72 73 sz = wx.BoxSizer(wx.VERTICAL) 74 self.log = wx.TextCtrl(self, -1, '', style=wx.TE_MULTILINE|wx.TE_RICH2) 75 sz.Add(self.log, 1, wx.EXPAND|wx.ALL, 3) 76 77 self.Bind(EVT_LOG_EVENT, self.LogEvent) 78 DispatcherServer(host, port, self) 79 if not threaded: 80 self.poller = wx.Timer(self, wx.NewId()) 81 self.Bind(wx.EVT_TIMER, self.OnPoll) 82 #poll 50 times/second, should be enough for low-bandwidth apps 83 self.poller.Start(20, wx.TIMER_CONTINUOUS) 84 else: 85 t = threading.Thread(target=loop) 86 t.setDaemon(1) 87 t.start() 88 89 def LogString(self, message, **kwargs): 90 event = LogEvent(msg=message, **kwargs) 91 if threading.activeCount() == 1: 92 self.LogEvent(event) 93 else: 94 wx.PostEvent(self, event) 95 96 def LogEvent(self, evt): 97 self.log.AppendText(evt.msg) 98 if not evt.msg.endswith('\n'): 99 self.log.AppendText('\n') 100 101 def OnPoll(self, evt): 102 asyncore.poll(timeout=0) 103 #add other methods as necessary 104 105 if __name__ == '__main__': 106 a = wx.App(0) 107 b = MainWindow('localhost', 1199, 0) 108 b.Show(1) 109 a.MainLoop()
If you have any questions, please feel free to contact the author, whose information is available in his profile profile.