Dustin Ingram
Writing — Speaking — GitHub — SocialThreaded Messaging via Unix Domain Sockets in Python
September 09 2010Introduction #
As part of a recent project, I experimented for the first time with Unix Domain Sockets. If you’re unfamiliar, it’s probably because there’s relatively little documentation or examples available online. I won’t go into detail, but two good sources to read are Wikipedia’s “Unix Domain Socket” page as well as Thomas Stover’s “Demystifying Unix Domain Sockets” article. Although the latter’s examples are in C (and that’s not what you’re here for!) he does a great job of, well, demystifying Unix Domain Sockets.
My goal was to create a basic Threaded example class which could be used as a server or client.
A Brief Example: The ‘piper’ Class #
Imports #
We’ll be using the threading
module as well as the socket
module (where the
Unix Domain Sockets reside):
import socket
from threading import Thread
class piper :
Initialization #
Here’s the __init__
def for the class:
def __init__(self, name, isDaemon) :
self._name = name
self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
self._socket.bind("/tmp/piper-%s" % name)
self._thread = Thread(target=self._listener)
self._thread.setDaemon(isDaemon)
self._thread.start()
As the above articles mention, you can think of Domain Sockets as local pipes, or even as files on the local domain. Therefore, for each client that is created, I give it a unique name, create and bind to a Unix socket, and set up and start the Thread.
Note the setDaemon(isDaemon)
method call there. It’s important, when we go to
create a bunch of individual nodes, that one of them is not daemonized, for
reasons specified by the daemon
documentation:
The entire Python program exits when no alive non-daemon threads are left.
This will allow us to run n-1
threads in the background, and only get output
from one, as well as being able to kill the non-daemonized thread to stop the
entire application.
Receiving Data - The Thread Target Method #
As specified above, the thread for the class has a target method, _listener
.
For the purposes of the example, this thread will contain a loop which waits
for data on it’s own socket, prints to the command line on receipt, and then
sends a response back to the original sender (in this case, it sends it’s own
name). Here’s the method:
def _listener(self) :
while True:
data, _ = self._socket.recvfrom(1024)
print "%s received: '%s'" % (self._name, data)
self._send(data, self._name)
This is pretty self-explanatory. The value 1024
is the message buffer size,
etc.
Sending Data #
The _send
method simply sends on a specified socket. In this demo, it’s used
to push data on to the socket of another class, so it looks like this:
def _send(self, recp, data) :
_s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
_s.sendto(data, "/tmp/piper-%s" % recp)
Using The ‘piper’ Class #
Here’s the main()
method I used. Remembering some of the baby steps I first
took when learning about Threads, I wanted to make two clients named
‘ping’ and ‘pong’ which simply chats back and forth, ad
infinitum. Note that one is non-daemonic, and that the last line simply exists
to set off the chain reaction:
def main() :
ping = piper(name='ping', isDaemon=True)
pong = piper(name='pong', isDaemon=False)
ping._send(recp=pong._name, data=ping._name)
Cleaning Up #
You probably won’t be able to kill the threaded process by ^C
-ing at the
command line, instead:
$ pkill python
Furthermore, this example doesn’t clean up after itself at all. If you try to run it twice in a row, you’ll get the error:
socket.error: [Errno 98] Address already in use
This means that the Domain Sockets we created in /tmp
(/tmp/piper-ping
and
/tmp/piper-pong
) still exist! Get rid of them with:
$ rm -rf /tmp/piper-*
Putting It All Together #
Here’s the entire, continuous example class:
#!/usr/bin/python
import socket
from threading import Thread
class piper:
def __init__(self, name, isDaemon) :
self._name = name
self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
self._socket.bind("/tmp/piper-%s" % name)
self._thread = Thread(target=self._listener)
self._thread.setDaemon(isDaemon)
self._thread.start()
def _listener(self) :
while True:
data, _ = self._socket.recvfrom(1024)
print "%s received: '%s'" % (self._name, data)
self._send(data, self._name)
def _send(self, recp, data) :
_s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
_s.sendto(data, "/tmp/piper-%s" % recp)
def main() :
ping = piper(name='ping', isDaemon=True)
pong = piper(name='pong', isDaemon=False)
ping._send(recp=pong._name, data=ping._name)
if __name__ == "__main__" :
main()