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:
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
- The socket library’s official docs
- The select library’s official docs
- wxPython and threads
- wxPython’s wiki entry on threads
- wxPython and PubSub: A Simple Tutorial
Download the Source
Note: This code was tested using Python 2.6.6, wxPython 2.8.12.1 on Windows 7
Pingback: Mike Driscoll: wxPython: How to Communicate with Your GUI via sockets | The Black Velvet Room
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.
Pingback: A basic socket client server example | Python Adventures
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
If you are using a wxPython version newer than 2.8, then the API for Publisher has changed to conform to the new API. See the following article: https://www.blog.pythonlibrary.org/2013/09/05/wxpython-2-9-and-the-newer-pubsub-api-a-simple-tutorial/
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
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