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:
- Resol creates a socket by calling
socket()
.int listen_sock = socket(AF_UNIX, SOCK_STREAM, 0);
- 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));
- 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);
- Resol calls
accept()
and start to wait. It blocks until Reep’s connection comes.data_socket = accept(listen_socket, NULL, NULL);
- Once
accept()
returns, it returns a file descriptordata_socket
here. We can send and receive bytes usingsend()
andrecv()
.recv(data_socket, buf, buf_size, 0); send(data_socket, buf, buf_size, 0);
- 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:
-
Reep creates a UNIX socket by calling
socket()
.data_socket = socket(AF_UNIX, SOCK_STREAM, 0);
- 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));
- If
connect()
returns without errors, Reep now can start tosend()
andrecv()
data.recv(data_socket, buf, buf_size, 0); send(data_socket, buf, buf_size, 0);
- 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);
}