UNIX domain socket tutorial

Brief into

UNIX domain socket is a inter-process communication mechanism, enabling processes exchange their data. This page aims for usage of UNIX socket.

Suppose process Resol waits for process Reep to send data. To initiate a communication, Resol has to setup a listening socket and waits for Reep. Reep creates a connecting socket, then connects Resol’s socket file. Once Resol accepts the connection, they can start communications.

Setup a listening socket

On Resol’s side, the procedure goes as follows:

  1. Resol creates a socket by calling socket().
    int listen_sock = socket(AF_UNIX, SOCK_STREAM, 0);
    
  2. Resol assigns the socket a name. In case of UNIX socket, the name is exactly a filename. Resol bind() the socket with a filename.
    // We need extra steps to create the _addr_ variable. Here we omit it for simplicity.
    ret = bind(listen_socket, (const struct sockaddr *) &addr, sizeof(struct sockaddr_un));
    
    
  3. Resol make sures the socket is listening type. After this step, you can see a socket file in the filesystem.
    // The 20 here is called _backlog_
    ret = listen(listen_socket, 20);
    
  4. Resol calls accept() and start to wait. It blocks until Reep’s connection comes.
    data_socket = accept(listen_socket, NULL, NULL);
    
  5. Once accept() returns, it returns a file descriptor data_socket here. We can send and receive bytes using send() and recv().
    recv(data_socket, buf, buf_size, 0);
    send(data_socket, buf, buf_size, 0);
    
  6. Like other file descriptors, you have to close() it once it’s not refered anymore.

Note that Resol can accept() multiple times, such that it can communicate with more than one processes.

Setup a connecting socket

On Reep’s side, the procedure goes as follows:

  1. Reep creates a UNIX socket by calling socket().

    data_socket = socket(AF_UNIX, SOCK_STREAM, 0);
    
  2. It calls connect() to connect to Resol’s socket file. Of course, the address should be identical to the Resol’s socket filename.
    // We need extra steps to setup _addr_ beforehand.
    ret = connect(data_socket, (const struct sockaddr *) &addr, sizeof(struct sockaddr_un));
    
  3. If connect() returns without errors, Reep now can start to send() and recv() data.
    recv(data_socket, buf, buf_size, 0);
    send(data_socket, buf, buf_size, 0);
    
  4. Of course, you have to close() it eventually.

(update 3) Complete example

The example code is slightly modified from unix(7) manpage. The code is only for reference. Please fill missing details in this code.

Resol’s code (Server)

#define SOCKET_NAME "/tmp/resol.sock"
#define BUFFER_SIZE 12

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

int
main(int argc, char *argv[])
{
    struct sockaddr_un addr;
    int down_flag = 0;
    int ret;
    int listen_socket;
    int data_socket;
    int result;
    char buffer[BUFFER_SIZE];

    /*
     * In case the program exited inadvertently on the last run,
     * remove the socket.
     */

    unlink(SOCKET_NAME);

    /* Create local socket. */

    listen_socket = socket(AF_UNIX, SOCK_STREAM, 0);
    if (listen_socket == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    /*
     * For portability clear the whole structure, since some
     * implementations have additional (nonstandard) fields in
     * the structure.
     */

    memset(&addr, 0, sizeof(struct sockaddr_un));

    /* Bind socket to socket name. */

    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, SOCKET_NAME, sizeof(addr.sun_path) - 1);

    ret = bind(listen_socket, (const struct sockaddr *) &addr,
               sizeof(struct sockaddr_un));
    if (ret == -1) {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    /*
     * Prepare for accepting connections. The backlog size is set
     * to 20. So while one request is being processed other requests
     * can be waiting.
     */

    ret = listen(listen_socket, 20);
    if (ret == -1) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    /* This is the main loop for handling connections. */

    for (;;) {

        /* Wait for incoming connection. */

        data_socket = accept(listen_socket, NULL, NULL);
        if (data_socket == -1) {
            perror("accept");
            exit(EXIT_FAILURE);
        }

        result = 0;
        for(;;) {

            /* Wait for next data packet. */

            ret = read(data_socket, buffer, BUFFER_SIZE);
            if (ret == -1) {
                perror("read");
                exit(EXIT_FAILURE);
            }

            /* Ensure buffer is 0-terminated. */

            buffer[BUFFER_SIZE - 1] = 0;

            /* Handle commands. */

            if (!strncmp(buffer, "DOWN", BUFFER_SIZE)) {
                down_flag = 1;
                break;
            }

            if (!strncmp(buffer, "END", BUFFER_SIZE)) {
                break;
            }

            /* Add received summand. */

            result += atoi(buffer);
        }

        /* Send result. */

        sprintf(buffer, "%d", result);
        ret = write(data_socket, buffer, BUFFER_SIZE);

        if (ret == -1) {
            perror("write");
            exit(EXIT_FAILURE);
        }

        /* Close socket. */

        close(data_socket);

        /* Quit on DOWN command. */

        if (down_flag) {
            break;
        }
    }

    close(listen_socket);

    /* Unlink the socket. */

    unlink(SOCKET_NAME);

    exit(EXIT_SUCCESS);
}

Reep’s code (Client)

#define SOCKET_NAME "/tmp/resol.sock"
#define BUFFER_SIZE 12

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

int
main(int argc, char *argv[])
{
    struct sockaddr_un addr;
    int i;
    int ret;
    int data_socket;
    char buffer[BUFFER_SIZE];

    /* Create local socket. */

    data_socket = socket(AF_UNIX, SOCK_STREAM, 0);
    if (data_socket == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    /*
     * For portability clear the whole structure, since some
     * implementations have additional (nonstandard) fields in
     * the structure.
     */

    memset(&addr, 0, sizeof(struct sockaddr_un));

    /* Connect socket to socket address */

    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, SOCKET_NAME, sizeof(addr.sun_path) - 1);

    ret = connect (data_socket, (const struct sockaddr *) &addr,
                   sizeof(struct sockaddr_un));
    if (ret == -1) {
        fprintf(stderr, "The server is down.\n");
        exit(EXIT_FAILURE);
    }

    /* Send arguments. */

    for (i = 1; i < argc; ++i) {
        ret = write(data_socket, argv[i], strlen(argv[i]) + 1);
        if (ret == -1) {
            perror("write");
            break;
        }
    }

    /* Request result. */

    strcpy (buffer, "END");
    ret = write(data_socket, buffer, strlen(buffer) + 1);
    if (ret == -1) {
        perror("write");
        exit(EXIT_FAILURE);
    }

    /* Receive result. */

    ret = read(data_socket, buffer, BUFFER_SIZE);
    if (ret == -1) {
        perror("read");
        exit(EXIT_FAILURE);
    }

    /* Ensure buffer is 0-terminated. */

    buffer[BUFFER_SIZE - 1] = 0;

    printf("Result = %s\n", buffer);

    /* Close socket. */

    close(data_socket);

    exit(EXIT_SUCCESS);
}

(Update 4) Communicate with multiple clients

Since Resol can accept() more than once, it’s possible that Resol can exchange data with more than one processes.

We are aware that send() and recv() are blocking calls. For example, if Resol calls send() but Reep refuses to recv(), Resol will stuck at that place as well as other clients.

To cope with this issue, select() would be our friend. We can form a set of file descriptors and hand it to select(). Once one of the fds is ready to read or write, select() returns and we can find which fd is available.

The code is only for reference. Please fill missing details in this code.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

int
main(void)
{
    int ret;
    int listen_socket;

    /* Setup a listen socket, bind() and listen() on it */
    listen_socket = socket(AF_UNIX, SOCK_STREAM, 0);
    // ...skip
    ret = bind(listen_socket, (const struct sockaddr *) &addr, sizeof(struct sockaddr_un));
    // ...skip
    ret = listen(listen_socket, 20);

    fd_set rfds;

    /* Watch stdin and listen socket to see when it has input */

    FD_ZERO(&rfds);
    FD_SET(0, &rfds);
    FD_SET(listen_socket, &rfds);

    /* Wait up to five seconds. */

    for (;;)
    {
        /* We make a copy of original set because select() will mess it up */
        fd_set working_rfds;
        memcpy(&working_rfds, &rfds, sizeof(working_rfds));

        ret = select(1024, &working_rfds, NULL, NULL, NULL);

        if (ret == -1) {
            perror("select()");
            exit(EXIT_FAILURE);
        }

        if (ret == 0) {
            printf("No data is available, we skip this loop\n");
            continue;
        }

        /* After this line, input data is available */

        if (FD_ISSET(0, &working_rfds)) {
            /* read stdin */
        }

        if (FD_ISSET(listen_socket, &working_rfds)) {
            int data_socket = accept(listen_socket, NULL, NULL);
            /* communicate with client */
        }

    }

    exit(EXIT_SUCCESS);
}