Introduction
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
- Sockets
- Threads or wx.Timer
- New event types
Process Overview
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.
Special Concerns
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.
Code Sample
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()
Comments
If you have any questions, please feel free to contact the author, whose information is available in his profile profile.