wxPython: How to Communicate with Your GUI via sockets

I sometimes run into situations where it would be nice to have one of my Python scripts communicate with another of my Python scripts. For example, I might want to send a message from a command-line script that runs in the background to my wxPython GUI that's running on the same machine. I had heard of a solution involving Python's socket module a couple of years ago, but didn't investigate it until today when one of my friends was asking me how this was done. It turns out Cody Precord has a recipe in his wxPython Cookbook that covers this topic fairly well. I've taken his example and done my own thing with it for this article.

wxPython, threads and sockets, oh my!

Yes, we're going to dive into threads in this article. They can be pretty confusing, but in this case it's really very simple. As is the case with every GUI library, we need to be aware of how we communicate with wxPython from a thread. Why? Because if you use an unsafe wxPython method, the result is undefined. Sometimes it'll work, sometimes it won't. You'll have weird issues that are hard to track down, so we need to be sure we're communicating with wxPython in a thread-safe manner. To do so, we can use one of the following three methods:

  • wx.CallAfter (my favorite)
  • wx.CallLater (a derivative of the above)
  • wx.PostEvent (something I almost never use)

Now you have the knowledge of how to talk to wxPython from a thread. Let's actually write some code! We'll start with the thread code itself:

UPDATE 2014/02/21: In wxPython 2.9+, you will need to use the new pubsub API detailed in this article

# wx_ipc.py

import select
import socket
import wx

from threading import Thread
from wx.lib.pubsub import Publisher

########################################################################
class IPCThread(Thread):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Initialize"""
        Thread.__init__(self)
        
        
        self.socket = socket.socket(socket.AF_INET,
                                    socket.SOCK_STREAM)
        # Setup TCP socket
        self.socket.bind(('127.0.0.1', 8080))
        self.socket.listen(5)
        self.setDaemon(True)
        self.start()
        
    #----------------------------------------------------------------------
    def run(self):
        """
        Run the socket "server"
        """
        while True:
            try:
                client, addr = self.socket.accept()
        
                ready = select.select([client,],[], [],2)
                if ready[0]:
                    recieved = client.recv(4096)
                    print recieved
                    wx.CallAfter(Publisher().sendMessage,
                                 "update", recieved)
                    
            except socket.error, msg:
                print "Socket error! %s" % msg
                break
            
        # shutdown the socket
        try:
            self.socket.shutdown(socket.SHUT_RDWR)
        except:
            pass
    
        self.socket.close()

I went ahead and copied Cody's name for this class, although I ended up simplifying my version quite a bit. IPC stands for inter-process communication and since that's what we're doing here, I thought I'd leave the name alone. In this call, we set up a socket that's bound to 127.0.0.1 (AKA the localhost) and is listening on port 8080. If you know that port is already in use, be sure to change that port number to something that isn't in use. Next we daemonize the thread so it will run indefinitely in the background and then we start it. Now it's basically running in an infinite loop waiting for someone to send it a message. Read the code a few times until you understand how it works. When you're ready, you can move on to the wxPython code below.

Note that the threading code above and the wxPython code below go into one file.

# wx_ipc.py

########################################################################
class MyPanel(wx.Panel):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent)
        
        btn = wx.Button(self, label="Send Message")
        btn.Bind(wx.EVT_BUTTON, self.onSendMsg)
        
        self.textDisplay = wx.TextCtrl(self, value="", style=wx.TE_MULTILINE)
        
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add(self.textDisplay, 1, wx.EXPAND|wx.ALL, 5)
        mainSizer.Add(btn, 0, wx.CENTER|wx.ALL, 5)
        self.SetSizer(mainSizer)
        
        Publisher().subscribe(self.updateDisplay, "update")
        
    #----------------------------------------------------------------------
    def onSendMsg(self, event):
        """
        Send a message from within wxPython
        """
        message = "Test from wxPython!"
        try:
            client = socket.socket(socket.AF_INET,
                                   socket.SOCK_STREAM)
            client.connect(('127.0.0.1', 8080))
            client.send(message)
            client.shutdown(socket.SHUT_RDWR)
            client.close()
        except Exception, msg:
            print msg
            
    #----------------------------------------------------------------------
    def updateDisplay(self, msg):
        """
        Display what was sent via the socket server
        """
        self.textDisplay.AppendText( str(msg.data) + "\n" )
    
########################################################################
class MyFrame(wx.Frame):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, parent=None, title="Communication Demo")
        panel = MyPanel(self)
        
        # start the IPC server
        self.ipc = IPCThread()
        
        self.Show()
        
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyFrame()
    app.MainLoop()

Here we set up our user interface using a simple frame and a panel with two widgets: a text control for displaying the messages that the GUI receives from the socket and a button. We use the button for testing the socket server thread. When you press the button, it will send a message to the socket listener which will then send a message back to the GUI to update the display. Kind of silly, but it makes for a good demo that everything is working the way you expect. You'll notice we're using pubsub to help in sending the message from the thread to the UI. Now we need to see if we can communicate with the socket server from a separate script.

So make sure you leave your GUI running while you open a new editor and write some code like this:

# sendMessage.py
import socket

#----------------------------------------------------------------------
def sendSocketMessage(message):
    """
    Send a message to a socket
    """
    try:
        client = socket.socket(socket.AF_INET,
                               socket.SOCK_STREAM)
        client.connect(('127.0.0.1', 8080))
        client.send(message)
        client.shutdown(socket.SHUT_RDWR)
        client.close()
    except Exception, msg:
        print msg
        
if __name__ == "__main__":
    sendSocketMessage("Python rocks!")

Now if you execute this second script in a second terminal, you should see the string "Python rocks!" appear in the text control in your GUI. It should look something like the following if you've pressed the button once before running the script above:

wxipc

Wrapping Up

This sort of thing would also work in a non-GUI script too. You could theoretically have a master script running with a listener thread. When the listener receives a message, it tells the master script to continue processing. You could also use a completely different programming language to send socket messages to your GUI as well. Just use your imagination and you'll find you can do all kinds of cool things with Python!

Additional Reading

Download the Source

Note: This code was tested using Python 2.6.6, wxPython 2.8.12.1 on Windows 7

Copyright © 2024 Mouse Vs Python | Powered by Pythonlibrary