Unix Domain Sockets

“Unix domain sockets” is a powerful socket-based IPC (inter-process communication) mechanism, it uses the same-old sockets API, and is simple to use… well, it has many features, so getting deep in it might be a bit complicated, but the basic use of Unix domain sockets it quite simple.

The man page is excellent, and it has a very good code example, almost as good as mine 🙂

All the code for this post can be found in github.

Unix-domain server

Basically, we create a “server” by opening a socket (either stream or datagram) and linking it to an address, which is actually a filename in our filesystem*. Then we listen for clients and wait for incoming data. Exactly as we do with network sockets.

* There are other binding options, but I’m going to review the filesystem one here.

In my example code, my listener waits for a client (using “accept”), and when a client connect, it handles the connection until the client closes (not optimal, because a client can starve other clients, but this is only an example).
In order to terminate the listener, it handles “Ctrl-C” – when this signal is received, it exits gracefully.

So, this is what we need to do:

  • Step 1: Create a socket and bind it to an address (a filename)
    1. Creating the socket:
       int sock = socket(AF_UNIX, SOCK_STREAM, 0);

      We are using the “AF_UNIX” family, which is the Unix-domain family, with SOCK_STREAM. We can also use SOCK_DGRAM if we want a datagram socket.

    2. Binding the socket:
       res = bind_address(sock, path);

      Where “sock” is the result from the call to “socket()”, and “path” is the filename that represents the socket. This filename mustn’t exist, therefore, we call “unlink(path)” before calling bind, to be sure that the filename is cleared.

    3. Listen:
       res = listen(sock, MAX_CLIENTS);

      “MAX_CLIENTS” is the length of the clients queue – We’re handling one client at a time.

  • Step 2: In a loop, wait for clients
     while (!quit) {
         int client_socket = accept(sock, NULL, NULL);
         if (client_socket < 0) {          if (errno != EINTR)              continue;          LOGE("Failed accepting client (%d: %s)", errno, strerror(errno));          break;      }      quit = handle_client(client_socket) > 0 ? 1 : 0;
         if (quit) 
             LOGI("Got termination command, exiting");
    
         close(client_socket);
    }
    

    Notes:
    Our socket is blocking, as we didn’t define it otherwise, so “accept()” will block until a client tries to connect.
    We exit only if accept fails on EINTR because we don’t want just any client connection error to kill our service.
    handle_client handles the input/output from/to the client. I defined it, arbitrarily, so that if it returns any positive value, then the listener exits.

    Regarding listener ending, I think it is important to define a SIGTERM handler, so that if the program needs to exit, it will do so gracefully (closing sockets and unlinking the address).

  • Step 3: When a client connects, receive data from it and send back result.
    This is really trivial, and it happens in “handle_client()”. We read the input data like so:

     len = read(sock, buff, MAX_BUFFER_LEN-1);

    Where “sock” is the client socket’s file descriptor that we received from “accept()”.
    Now we do all our processing on the input, and when we want to send data back to the client, we use “write”:

     res = write(sock, out_buff, outbuff_len);

    We can repeat reading/writing as long as the client is connected, and when we decide to terminate the connection, we close the client socket:

     close(client_socket);

Easy enough.

Unix-domain client

Well, this is even easier than creating a server.

  • Step 1: Create a new Unix-domain socket and connect to the server
    1. Creating a new client socket is exactly the same as for the server
    2. Connecting:
      struct sockaddr_un address;
      
      memset(&address, 0x00, sizeof(address));
      address.sun_family = AF_UNIX;
      strncpy(address.sun_path, socket_name, SOCKET_NAME_MAX_LEN);
      
      res = connect(sock, (struct sockaddr *)&address, sizeof(address));
  • Step 2: Read/Write data
    Again, exactly the same as explained above
  • Step 3: Disconnect
    Dito – Use close.

Execution example

Listener

$ ./listener /tmp/sock1
[LISTENER] INFO  Starting "Unix Domain Sockets" demo listener
[LISTENER] INFO  * Starting communication with a client *
[LISTENER] INFO  Incoming message: Test this string
[LISTENER] INFO  Client socket was closed by peer
[LISTENER] INFO  * Starting communication with a client *
[LISTENER] INFO  Incoming message: Take another one
[LISTENER] INFO  Client socket was closed by peer
^C[LISTENER] INFO  Terminating service

Client

$ ./test_client /tmp/sock1 "Test this string"
INFO  Starting "Unix Domain Sockets" demo client
INFO  Got back: Got your message
$ ./test_client /tmp/sock1 "Take another one"
INFO  Starting "Unix Domain Sockets" demo client
INFO  Got back: Got your message

Just what we wanted!
Until next time,
Amnon.

One thought on “Unix Domain Sockets

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s