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

7 thoughts on “wxPython: How to Communicate with Your GUI via sockets”

  1. Pingback: Mike Driscoll: wxPython: How to Communicate with Your GUI via sockets | The Black Velvet Room

  2. Steve Williams

    This works well in a Data Warehouse environment where you have to coordinate activities (copies, loads, updates, notifications) between different platforms. Send a UDP message to a central listener (e.g., “daily backup complete on machine X”). The central listener looks the message up in a dataset and gets rows with DNS names, ports and messages for waiting listeners. The waiting listeners start processes, send e-mails, forward messages, . . . The python to do all this amounts to less than 100 lines of code.

  3. Pingback: A basic socket client server example | Python Adventures

  4. I am having problems with this, getting the following, I have tried importing args1/kwargs specifically but it says they should not be imported directly. I am running this on Python 2.7 64bit, wx v3.0(for 2.7 64bit)

    Thanks

    Traceback (most recent call last):

    File “C:UsersAdminWorkspacesSUTESUTEsrcwx_ipc.py”, line 133, in

    frame = MyFrame()

    File “C:UsersAdminWorkspacesSUTESUTEsrcwx_ipc.py”, line 124, in __init__

    panel = MyPanel(self)

    File “C:UsersAdminWorkspacesSUTESUTEsrcwx_ipc.py”, line 91, in __init__

    Publisher().subscribe(self.updateDisplay, “update”)

    NameError: global name ‘Publisher’ is not defined

  5. Hi,
    I am newbee on Python and Socket.
    Your tuto is very interesting. But I have one question maybe idiot :
    we must install Python and WxPython on the Client also or the server gives all, the program and also wxPython ?
    Regards Bruno

  6. You would need to install them both on the client. The server won’t have wxPython installed as most servers are headless (i.e. do not have a monitor). Or you could package the script up with something like PyInstaller so you would just install your program instead of Python/wx

Comments are closed.