“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)
- 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.
- 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.
- Listen:
res = listen(sock, MAX_CLIENTS);
“MAX_CLIENTS” is the length of the clients queue – We’re handling one client at a time.
- Creating the socket:
- 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
- Creating a new client socket is exactly the same as for the server
- 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.