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

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.

AsynchronousSockets (last edited 2008-03-11 10:50:23 by localhost)