Unix domain sockets (UDS) are used for inter-process communication (IPC) between processes running on the same host system. The API for Unix domain sockets is similar to that used for communication between processes running on hosts connected over a network.
Table of Contents
1.0 Unix Domain Sockets
A socket is a communication endpoint at a host computer. The socket API provides calls for communication between processes. The socket system call is,
int socket (int domain, int type, int protocol);
The first parameter to the socket system call is the domain. The domain is actually the communication domain and it selects the protocol family to be used for communication. For Unix domain sockets, the domain is AF_UNIX and it is for local communication. A process can create a socket of domain AF_UNIX and communicate with other processes using it.
Why use Unix domain sockets? After all, we can use the domain as AF_INET or AF_INET6 for creating a socket of IPv4 or IPv6 Internet protocol families respectively and use the localhost for loopback address for local communication. And, that way we have the flexibility to put processes on the same host or different hosts on the network. The argument for using Unix domain sockets is that they are fast and easy to use. In fact, they are being use abundantly in your Linux system. You can check out the Unix domain sockets on your system by giving the command, “ss -f unix“. Also, since the communication is local, you don't have to worry about network byte order for numbers sent between processes.
2.0 Call sequence
Interprocess communication mechanisms like FIFOs are symmetrical because the server and client make the same calls. The socket API is asymmetrical bacause the server and client make different sequence of system calls. The server makes the following calls: socket, bind, listen and accept. Once a connection is accepted, the server does I/O using the read and write calls. The client first makes socket and connect calls. After the connection is established, the client communicates using the write and read calls.
3.0 socket
#include <sys/socket.h> #include <sys/un.h> unix_socket = socket (AF_UNIX, type, 0);
The first parameter in the socket call is the domain, AF_UNIX. The second parameter, socket type, can be SOCK_STREAM, for stream-oriented sockets, SOCK_DGRAM, for datagram sockets, or, SOCK_SEQPACKET, for sequenced packet socket. Datagram sockets for Unix domain are reliable and datagrams are not reordered. SOCK_SEQPACKET provides a connection oriented communication, preserving message boundaries and delivering messages in the order they were sent.
4.0 Socket address
The socket address is a very involved structure in network programming. However, it is a little simpler in the case of Unix domain sockets.
struct sockaddr_un { sa_family_t sun_family; /* AF_UNIX */ char sun_path [108]; /* pathname */ };
The first member, sun_family is always AF_UNIX. The second member, sun_path, is the pathname of the socket in the filesystem.
5.0 Example: A client-server system using Unix domain sockets
As an example, consider a maintenance system of an apartment complex. The system comprises of a Complaint Logging Server and clients. People staying in the apartment complex log their problems in the server. The maintenance staff take a complaint from the server, do necessary work and resolve the issue. Once, a complaint is resolved, it is deleted from the server. So, a client can do three things. First, it can create a new complaint. Second, it can ask the server for pending complaints. And, lastly, it can delete a complaint (after resolution).
5.1 Complaint Server
The server and clients communicate over Unix domain sockets. The server has a “public” socket, /tmp/complaint-server.socket. Clients connect to this socket. Once, a client's connect is accepted by the server, a new socket is created for data transmission between the server and the client.
The server code is as follows.
/* * complaint-server.c: Log and manage complaints * * Copyright (c) 2020 SoftPrayog.in * */ #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <stdbool.h> #include <sys/select.h> #include <sys/stat.h> #define COMPLAINT_SOCKET "/tmp/complaint-server.socket" #define BACKLOG 10 #define LOG_COMPLAINT 1 #define GIVE_ME_A_COMPLAINT 2 #define GIVE_COMPLAINT4APT 3 #define RESOLVE_COMPLAINT 4 #define QUIT 0 #define NEXT_COMPLAINT 11 #define NO_MORE_COMPLAINTS 12 #define NO_COMPLAINT4THIS_APT 13 #define COMPLAINT_ADDED 14 #define COMPLAINT_DELETED 15 #define MAX_CLIENTS 200 struct message { int32_t message_id; char apartment_id [8]; char remarks [128]; }; struct element { char *apartment_id; char *remarks; bool accessed; struct element *next; }; struct element *tail; struct message recv_message, send_message; void add_to_complaint_q (char *source_apartment_id, char *source_remarks, char *dest_apartment_id); int give_next_complaint (char *apartment_id, char *remarks); int give_complaint (char *source_apartment_id, char *dest_apartment_id, char *remarks); int del_complaint (char *source_apartment_id, char *dest_apartment_id); void reset_accessed_all (void); void error (char *msg); int main (int argc, char **argv) { printf ("Complaint-server: Hello, World!\n"); // initialize the complaint queue tail = NULL; // create a unix domain socket, COMPLAINT_SOCKET // unlink, if already exists struct stat statbuf; if (stat (COMPLAINT_SOCKET, &statbuf) == 0) { if (unlink (COMPLAINT_SOCKET) == -1) error ("unlink"); } int listener; if ((listener = socket (AF_UNIX, SOCK_SEQPACKET, 0)) == -1) error ("socket"); struct sockaddr_un socket_address; memset (&socket_address, 0, sizeof (struct sockaddr_un)); socket_address.sun_family = AF_UNIX; strncpy (socket_address.sun_path, COMPLAINT_SOCKET, sizeof(socket_address.sun_path) - 1); if (bind (listener, (const struct sockaddr *) &socket_address, sizeof (struct sockaddr_un)) == -1) error ("bind"); // Mark socket for accepting incoming connections using accept if (listen (listener, BACKLOG) == -1) error ("listen"); fd_set fds, readfds; FD_ZERO (&fds); FD_SET (listener, &fds); int fdmax = listener; printf ("Complaint-server: Waiting for a message from a client.\n"); while (1) { readfds = fds; // monitor readfds for readiness for reading if (select (fdmax + 1, &readfds, NULL, NULL, NULL) == -1) error ("select"); // Some sockets are ready. Examine readfds for (int fd = 0; fd < (fdmax + 1); fd++) { if (FD_ISSET (fd, &readfds)) { // fd is ready for reading if (fd == listener) { // request for new connection int fd_new; if ((fd_new = accept (listener, NULL, NULL)) == -1) error ("accept"); FD_SET (fd_new, &fds); if (fd_new > fdmax) fdmax = fd_new; fprintf (stderr, "complaint-server: new client\n"); } else // data from an existing connection, receive it { memset (&recv_message, '\0', sizeof (struct message)); ssize_t numbytes = read (fd, &recv_message, sizeof (struct message)); if (numbytes == -1) error ("read"); else if (numbytes == 0) { // connection closed by client fprintf (stderr, "Socket %d closed by client\n", fd); if (close (fd) == -1) error ("close"); FD_CLR (fd, &fds); } else { // data from client memset (&send_message, '\0', sizeof (struct message)); switch (recv_message.message_id) { case LOG_COMPLAINT: add_to_complaint_q (recv_message.apartment_id, recv_message.remarks, send_message.apartment_id); send_message.message_id = COMPLAINT_ADDED; if (write (fd, &send_message, sizeof (struct message)) == -1) error ("write"); break; case GIVE_ME_A_COMPLAINT: if (give_next_complaint (send_message.apartment_id, send_message.remarks) == -1) { // error: No more complaints send_message.message_id = NO_MORE_COMPLAINTS; if (write (fd, &send_message, sizeof (struct message)) == -1) error ("write"); } else { send_message.message_id = NEXT_COMPLAINT; if (write (fd, &send_message, sizeof (struct message)) == -1) error ("write"); } break; case GIVE_COMPLAINT4APT: if (give_complaint (recv_message.apartment_id, send_message.apartment_id, send_message.remarks) == -1) { // no complaint for this apartment send_message.message_id = NO_COMPLAINT4THIS_APT; if (write (fd, &send_message, sizeof (struct message)) == -1) error ("write"); } else { send_message.message_id = NEXT_COMPLAINT; if (write (fd, &send_message, sizeof (struct message)) == -1) error ("write"); } break; case RESOLVE_COMPLAINT: if (del_complaint (recv_message.apartment_id, send_message.apartment_id) == -1) { // error: No complaint found for this apartment send_message.message_id = NO_COMPLAINT4THIS_APT; if (write (fd, &send_message, sizeof (struct message)) == -1) error ("write"); } else { // complaint deleted send_message.message_id = COMPLAINT_DELETED; if (write (fd, &send_message, sizeof (struct message)) == -1) error ("write"); } break; case QUIT: if (close (fd) == -1) error ("close"); FD_CLR (fd, &fds); break; default: fprintf (stderr, "Complaint-server: Unexpected message from client\n"); } } } } // if (fd == ... } // for } // while (1) exit (EXIT_SUCCESS); } // main // add a new complaint to the complaint queue void add_to_complaint_q (char *source_apartment_id, char *source_remarks, char *dest_apartment_id) { struct element *ptr; char *cp1, *cp2; if ((ptr = (struct element *) malloc (sizeof (struct element))) == NULL) error ("malloc"); if ((cp1 = (char *) malloc (strlen (source_apartment_id) + 1)) == NULL) error ("malloc"); strcpy (cp1, source_apartment_id); strcpy (dest_apartment_id, source_apartment_id); ptr -> apartment_id = cp1; if ((cp2 = (char *) malloc (strlen (source_remarks) + 1)) == NULL) error ("malloc"); strcpy (cp2, source_remarks); ptr -> remarks = cp2; ptr -> accessed = false; if (tail == NULL) { ptr -> next = ptr; } else { ptr -> next = tail -> next; tail -> next = ptr; } tail = ptr; } int del_complaint (char *source_apartment_id, char *dest_apartment_id) // returns -1 on error { struct element *ptr, *prev; char *cp; strcpy (dest_apartment_id, source_apartment_id); if (!tail) { fprintf (stderr, "del_complaint: Queue is empty\n"); return -1; } // get the head prev = tail; ptr = tail -> next; while (1) { if (strcmp (source_apartment_id, ptr -> apartment_id) == 0) break; if (ptr == tail) return -1; prev = ptr; ptr = ptr -> next; } free (ptr -> apartment_id); free (ptr -> remarks); if (ptr == tail) { if (ptr == prev) { tail = NULL; free (ptr); return 0; } else { tail = prev; } } prev -> next = ptr -> next; free (ptr); return 0; } int give_next_complaint (char *apartment_id, char *remarks) { struct element *ptr; if (!tail) { fprintf (stderr, "give_next_complaint: Queue is empty\n"); return -1; } // get the head ptr = tail -> next; while (1) { if (!ptr -> accessed) break; if (ptr == tail) { reset_accessed_all (); ptr = tail -> next; continue; } ptr = ptr -> next; } strcpy (apartment_id, ptr -> apartment_id); strcpy (remarks, ptr -> remarks); ptr -> accessed = true; return 0; } void reset_accessed_all (void) { struct element *ptr; if (!tail) { fprintf (stderr, "reset_accessed_all: Queue is empty\n"); return; } // get the head ptr = tail -> next; while (1) { ptr -> accessed = false; if (ptr == tail) return; ptr = ptr -> next; } } int give_complaint (char *source_apartment_id, char *dest_apartment_id, char *remarks) { struct element *ptr; char *cp; strcpy (dest_apartment_id, source_apartment_id); if (!tail) { fprintf (stderr, "give_complaint: Queue is empty\n"); return -1; } // get the head ptr = tail -> next; while (1) { if (strcmp (source_apartment_id, ptr -> apartment_id) == 0) break; if (ptr == tail) return -1; ptr = ptr -> next; } strcpy (remarks, ptr -> remarks); return 0; } void error (char *msg) { perror (msg); exit (1); }
5.2 Clients
The client has commands to record a new complaint, get next complaint from the queue, get complaint for a specific apartment and clear a complaint. The client code is as follows.
/* * complaint-client.c: client for logging and managing complaints * * Copyright (c) 2020 SoftPrayog.in * */ #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <stdbool.h> #include <sys/select.h> #include <sys/stat.h> #define COMPLAINT_SOCKET "/tmp/complaint-server.socket" #define MAX_MESSAGE_SIZE 256 #define LOG_COMPLAINT 1 #define GIVE_ME_A_COMPLAINT 2 #define GIVE_COMPLAINT4APT 3 #define RESOLVE_COMPLAINT 4 #define QUIT 0 #define NEXT_COMPLAINT 11 #define NO_MORE_COMPLAINTS 12 #define NO_COMPLAINT4THIS_APT 13 #define COMPLAINT_ADDED 14 #define COMPLAINT_DELETED 15 struct message { int32_t message_id; char apartment_id [8]; char remarks [128]; }; struct message message; int sock_fd; int get_input (void); void error (char *msg); int main (int argc, char **argv) { if ((sock_fd = socket (AF_UNIX, SOCK_SEQPACKET, 0)) == -1) error ("socket"); struct sockaddr_un socket_address; memset (&socket_address, 0, sizeof (struct sockaddr_un)); socket_address.sun_family = AF_UNIX; strncpy (socket_address.sun_path, COMPLAINT_SOCKET, sizeof (socket_address.sun_path) - 1); if (connect (sock_fd, (const struct sockaddr *) &socket_address, sizeof (struct sockaddr_un)) == -1) error ("connect"); int option; bool over = false; while (!over) { option = get_input (); // send request to server if (write (sock_fd, &message, sizeof (struct message)) == -1) error ("write"); memset (&message, '\0', sizeof (struct message)); // receive response from server if (read (sock_fd, &message, sizeof (struct message)) == -1) error ("read"); // process server response switch (message.message_id) { case COMPLAINT_ADDED: printf ("\nComplaint for Apartment id: %s has been added.\n\n", message.apartment_id); break; case NEXT_COMPLAINT: printf ("\nCOMPLAINT\n\tApartment id: %s\n\tRemarks: %s\n\n", message.apartment_id, message.remarks); break; case NO_MORE_COMPLAINTS: printf ("\nNo more complaints\n\n"); break; case NO_COMPLAINT4THIS_APT: printf ("\nThere is no existing Complaint for Apartment id: %s.\n\n", message.apartment_id); break; case COMPLAINT_DELETED: printf ("\nComplaint for Apartment id: %s has been deleted.\n\n", message.apartment_id); break; case QUIT: over = true; break; default: printf ("\nUnrecongnized message from server\n\n"); } } exit (EXIT_SUCCESS); } char inbuf [512]; int get_input (void) { int option; while (1) { printf ("COMPLAINTS\n\n"); printf ("\tRecord a new complaint\t1\n"); printf ("\tGet next complaint\t2\n"); printf ("\tGet complaint\t3\n"); printf ("\tClear complaint\t4\n"); printf ("\tQuit\t\t0\n\n"); printf ("Your option: "); if (fgets (inbuf, sizeof (inbuf), stdin) == NULL) error ("fgets"); sscanf (inbuf, "%d", &option); int len; switch (option) { case 1: message.message_id = LOG_COMPLAINT; printf ("Apartment id: "); if (fgets (inbuf, sizeof (inbuf), stdin) == NULL) error ("fgets"); len = strlen (inbuf); if (inbuf [len - 1] == '\n') inbuf [len - 1] = '\0'; strcpy (message.apartment_id, inbuf); printf ("Description: "); if (fgets (inbuf, sizeof (inbuf), stdin) == NULL) error ("fgets"); len = strlen (inbuf); if (inbuf [len - 1] == '\n') inbuf [len - 1] = '\0'; strcpy (message.remarks, inbuf); break; case 2: message.message_id = GIVE_ME_A_COMPLAINT; break; case 3: message.message_id = GIVE_COMPLAINT4APT; printf ("Apartment id: "); if (fgets (inbuf, sizeof (inbuf), stdin) == NULL) error ("fgets"); len = strlen (inbuf); if (inbuf [len - 1] == '\n') inbuf [len - 1] = '\0'; strcpy (message.apartment_id, inbuf); break; case 4: message.message_id = RESOLVE_COMPLAINT; printf ("Apartment id: "); if (fgets (inbuf, sizeof (inbuf), stdin) == NULL) error ("fgets"); len = strlen (inbuf); if (inbuf [len - 1] == '\n') inbuf [len - 1] = '\0'; strcpy (message.apartment_id, inbuf); break; case 0: message.message_id = QUIT; break; default: printf ("Illegal option, try again\n\n"); continue; } return option; } } void error (char *msg) { perror (msg); exit (1); }
We can compile and run the server and client programs.
$ gcc complaint-server.c -o complaint-server $ gcc complaint-client.c -o complaint-client $ ./complaint-server & $ Complaint-server: Hello, World! Complaint-server: Waiting for a message from a client. $ ./complaint-client COMPLAINTS Record a new complaint 1 Get next complaint 2 Get complaint 3 Clear complaint 4 Quit 0 Your option:
A screenshot of server running along with three clients is given below.