UNIX Domain Sockets (Datagram)

Hello friends!

I am here in Internetland to talk to you about my adventures with UNIX domain sockets. I’m designing a collection of separate programs that all need to communicate. The communication has to be multiclient, single server, and easy to implement. In this case, a datagram is the preferred mode due to it’s tiny connectionless nature. UDP seems like a good option at first, but it has problems with reliability. Dropping packets in stateful IPC can cause issues with synchronization, etc. TCP could work, but why use the networking stack when there are APIs dedicated to IPC?

Sounds like a job for UNIX domain sockets. These wonderful little things use the file system for their endpoint identifiers, but the actual communication happens within the kernel. You can configure them as a pipe, or use datagrams. With datagrams, it’s in a single packet context, rather than a stream. There’s no established connection to speak of (but you do call connect()). A bit confusing. You create a file handle for your client and connect to the server’s file handle. If there’s already a file on the filesystem with the same name, the socket’s bind will fail, so cleaning the handles up when done (or before you init) is important too.

If you’re just getting into IPC, then I highly suggest reading Beej’s Guide To Unix Interprocess Communication. It’s great. I should say, there are many different types of IPC, and domain sockets are just one. I prefer domain sockets to shared memory, for the sake of simplicity. For your implementation, look at the many methods available, and see what works best for you.

Without further ado, here’s the code! This first part is the server.

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

char * server_filename = "/tmp/socket-server";

int main(void)
{
    int s;
    struct sockaddr_un srv_un = {0};

    if ((s = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) {
        perror("socket server");
        exit(1);
    }

    srv_un.sun_family = AF_UNIX;
    strncpy(srv_un.sun_path, server_filename, sizeof(srv_un.sun_path));
    /*If you leave the file behind when you're finished, or perhaps crash after binding, the next bind will fail
    / with "address in use". Which just means, the file is already there.*/
    unlink(srv_un.sun_path);

    if (bind(s, (struct sockaddr *)&srv_un, sizeof(srv_un)) == -1) {
        perror("bind server");
        exit(1);
    }

    for(;;) {

        char buf[1024] = {0};
        read(s, buf, sizeof(buf));
        printf("RECEIVED: %s", buf);

    }

    close(s);

    return 0;
}

And, here’s the client.

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

char * server_filename = "/tmp/socket-server";
char * client_filename = "/tmp/socket-client";

int main(void)
{

    int s;
    char obuf[100];
    struct sockaddr_un srv_un, cli_un = { 0 };
    
    srv_un.sun_family = AF_UNIX;
    strncpy(srv_un.sun_path, server_filename, sizeof(srv_un.sun_path));

    cli_un.sun_family = AF_UNIX;
    strncpy(cli_un.sun_path, client_filename, sizeof(cli_un.sun_path));
    unlink(cli_un.sun_path);

    if ((s = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) {
        perror("socket server");
        exit(1);
    }

    /* (http://stackoverflow.com/questions/3324619/unix-domain-socket-using-datagram-communication-between-one-server-process-and)
    Here, we bind to our client node, and connect to the server node. As Unix domain sockets need to have endpoints on either end
    of the connection. For more info, visit the URL.*/
    if (bind(s, (struct sockaddr *)&cli_un, sizeof(cli_un)) == -1) {
        perror("bind client");
        exit(1);
    }

    if (connect(s, (struct sockaddr *) &srv_un, sizeof(srv_un)) == -1) {
        perror("connect client");
        exit(1);
    }

    //printf("Connected.\n");

    while(printf("> "), fgets(obuf, 100, stdin), !feof(stdin)) {
        if (send(s, obuf, strlen(obuf), 0) == -1) {
            perror("send");
            exit(1);
        }
        break;
    }

    //printf("Sent successfully.\n");

    close(s);

    return 0;
}