When I wrote about Inter Process Communication, shared memory and signals I promised I’ll suggest an easier way of sending messages between process in a way that one of them gets notified. Shared memory and signals is great, but it wasn’t the right choice for my example. Today I’ll explain so-called Unix sockets and as always I’ll give you an example.
What is a network socket
You might already be familiar with the concept of a socket, but let’s revise it. A socket is something that let’s revise this knowledge. A socket is an endpoint used for communication – usually network one. So when you open your browser, it uses sockets to read a web page, same applies for servers. In Unix world everything is a file, or to be a bit more precise, OS represents every device as a file. When you run a server, its sockets are “files”. Let’s spawn a listening server with netcat by running nc -l 127.0.0.1 8080
and list its files with lsof
:
➜ ~ lsof -c nc COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME nc 13437 gonczor cwd DIR 1,6 864 20758717 /Users/gonczor nc 13437 gonczor txt REG 1,6 203632 1152921500312810925 /usr/bin/nc nc 13437 gonczor txt REG 1,6 2160672 1152921500312811906 /usr/lib/dyld nc 13437 gonczor 0u CHR 16,0 0t22384 1159 /dev/ttys000 nc 13437 gonczor 1u CHR 16,0 0t22384 1159 /dev/ttys000 nc 13437 gonczor 2u CHR 16,0 0t22384 1159 /dev/ttys000 nc 13437 gonczor 3u IPv4 0x19bdae2b0a7886e3 0t0 TCP localhost:http-alt (LISTEN)
In the last line the file is of IPv4 (notice that directories are also represented as files), with its node and a few other things that are not relevant now. How does writing to such a socket look like? Let’s analyze a simple program in C I’ve mostly copied from here.
#include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <arpa/inet.h> int main(int argc, char *argv[]) { int sockfd = 0, n = 0; const char* message = "Hello, world!\n"; struct sockaddr_in serv_addr; if(argc != 3) { printf("\n Usage: %s <ip of server> <port>\n",argv[0]); return 1; } if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("\n Error : Could not create socket \n"); return 1; } memset(&serv_addr, '0', sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(atoi(argv[2])); if(inet_pton(AF_INET, argv[1], &serv_addr.sin_addr)<=0) { printf("\n inet_pton error occured\n"); return 1; } if( connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { printf("\n Error : Connect Failed \n"); return 1; } write(sockfd, message, strlen(message)); close(sockfd); return 0; }
At the end of the post you’ll find a link to repository with all the source code. Anyway, what is important here is the write()
call at the bottom. We can use other functions like send
, but I wanted to show that you can use the same function for writing to files and to the socket. The server should react in the following way receiving the data and displaying it:
➜ IPC git:(master) ✗ nc -l 127.0.0.1 8080 Hello, world!
But what if you wanted to communicate 2 processes running in your memory on the same machine? Can it be done better?
What are Unix sockets
I’m just going to shamelessly copy an answer from serverfault giving a good overall idea of what Unix sockets are:
A UNIX socket, AKA Unix Domain Socket, is an inter-process communication mechanism that allows bidirectional data exchange between processes running on the same machine.
IP sockets (especially TCP/IP sockets) are a mechanism allowing communication between processes over the network. In some cases, you can use TCP/IP sockets to talk with processes running on the same computer (by using the loopback interface).
UNIX domain sockets know that they’re executing on the same system, so they can avoid some checks and operations (like routing); which makes them faster and lighter than IP sockets. So if you plan to communicate with processes on the same host, this is a better option than IP sockets.
An example use case? I remember working for a company that had on premise servers that would run web applications written in Python. For a few reasons we wanted to use Nginx server in front of them – better scaling when spawning new processes, better static files handling and so on. What we did was to spawn Nginx as one process and make it a reverse proxy pointing to sockets. We would then make our python application listen on the same sockets. We can configure server to do this by adding the following line:
listen unix:/var/run/nginx.sock;
Implementation
Let’s take a look at the client implementation:
#include <netinet/in.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <sys/un.h> int main(int argc, char *argv[]) { int sockfd = 0, len = 0, socket_path_len = 0; const char* message = "Hello, world!\n"; const char* socket_path = "local.sock"; struct sockaddr_un serv_addr; if((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { perror("\nCould not create socket \n"); return 1; } serv_addr.sun_family = AF_UNIX; socket_path_len = strlen(socket_path); strncpy(serv_addr.sun_path, socket_path, socket_path_len+1); len = socket_path_len + 1 + sizeof(serv_addr.sun_family); printf("%s: %d\n", socket_path, socket_path_len); if (connect(sockfd, (struct sockaddr *)&serv_addr, len) == -1) { perror("\nConnection failed \n"); return 1; } if (write(sockfd, message, strlen(message)) == -1) { perror("\nWriting data unsuccessful\n"); return 1; } close(sockfd); return 0; }
There are a few differences worth discussing. First we used AF_UNIX
instead of AF_INET
to create socket: socket(AF_UNIX, SOCK_STREAM, 0)
. Second the binding process is a bit different as we don’t need to choose both address and port, only “address” in form of a disk space. Moreover, there is no need to translate the address string into address structure:
The inet_pton() function converts a presentation format address (that is, printable form as held in a character string) to network format (usually a struct in_addr or some other
internal binary representation, in network byte order).
After the initialization part is done, sending data with write()
function is exactly the same. One catch I came across is correctly counting the buffer sizes – hence the +1
in strncpy()
and len
counting.
I’ve also written a simple python server that displays data received over the network:
#!/usr/bin/env python3 import socket import sys import os ADDRESS = "./local.sock" if os.path.exists(ADDRESS): os.unlink(ADDRESS) print("# Creating server...") server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: server.bind(ADDRESS) server.listen(1) print("# Server created") while True: connection, client = server.accept() if data := connection.recv(0x1_000): print(f"Received: {data.decode('utf-8')}") else: print("Error") break finally: server.close()
The process here is even more simple. Again I needed to use AF_UNIX
instead of AF_INET
and point to a file instead of address and port tuple. We can now run the server and the client:

Summary
That’s all for today. As always you can find working examples on my GitLab (remember that those will only work on Unix systems – macOS/Linux). You may also want to check out the latest posts: