Dustin Ingram

WritingSpeakingGithub

Introduction

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()