A socket is an endpoint that allows bi-directional communication between two processes. With the help of sockets, processes can exchange information across a network. In the Unix system, we can create Unix sockets with the help of the C library sys/socket.h
.
Unix sockets are essentially used for inter-process communication for processes on the same host rather than a network. This means that the two processes must be running on the same local machine to use Unix sockets to exchange data with each other.
Now that we have understood the purpose of a Unix socket, let's look at the steps to create a Unix socket and use it to perform inter-process communication in C applications with the help of a flow diagram.
To understand the steps illustrated in the diagram, we will break the diagram into two categories, server and client, and explain them individually.
server_socket = socket(AF_UNIX, SOCK_STREAM, 0);
First, we will need to create a server-side socket which will be used to handle connections from the client. To create a socket, we will call the function socket()
that takes the type of socket to be created as an argument. Since we are using Unix sockets, we will use the socket family of AF_UNIX
, we will also use the standard socket type of SOCK_STREAM
. The function, upon execution will return a file descriptor to the new socket.
struct sockaddr_un server_addr;server_addr.sun_family = AF_UNIX;strcpy(server_addr.sun_path, "unix_socket");int slen = sizeof(server_addr);bind(server_socket, (struct sockaddr *) &server_addr, slen);
To bind a socket to a local address on the machine we use the bind()
function. In the function we pass the file descriptor of the socket returned by the socket()
function and the bind details that we specific in a structure of type sockaddr_un
named server_addr
in our application.
listen(server_socket, 5);
Now we use the listen()
function to listen for incoming connections. In the function, we pass the socket we want to listen to. We also pass the max number of connections to queue in the socket as the 2nd argument. In our code, we specified 5 max connection queues.
int clen = sizeof(client_addr);client_socket = accept(server_socket, (struct sockaddr *) &client_addr, &clen);
To accept an incoming connection from the client, we can use the accept()
function. The function will take the server socket as an argument and retrieve the first socket connection in the queue, create a new socket through which we can communicate with the client, and return the file descriptor for that socket.
read(client_socket, &ch, 1);
After a connection is established, we can then read data from the client socket using the read()
system call by passing the client socket file descriptor.
write(client_socket, &ch, 1);
The server can also write data to the client by using the write()
system call and passing the client socket's file descriptor.
close(client_socket);
Once the server is done sending and receiving data from the client, it can close the socket connection using the close()
function and passing the socket's file descriptor client_socket
in the function's argument.
server_socket = socket(AF_UNIX, SOCK_STREAM, 0);
The client also creates a similar socket as seen in the server configuration using the socket()
function with the socket family of AF_UNIX
and the socket type of SOCK_STREAM
.
Note: The type of socket for the server and the the client should be the same for the client to establish a connection with the server.
struct sockaddr_un server_addr;server_addr.sun_family = AF_UNIX;strcpy(server_addr.sun_path, "unix_socket");connection_result = connect(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));
Once a client has set up a socket, it will use the connect()
method to try to establish a connection to the server socket. It requires the file descriptor for the server socket and the server address we wish to connect to as function arguments.
The function will return the value 0
upon a successful connection to the server; otherwise, it returns -1
which can be used for error handling.
write(server_socket, &ch, 1);
If the client wants to send data to the server, it can use the write()
system call by passing the file descriptor for the client socket.
read(server_socket, &ch, 1);
To read data from the server, the client can use the read()
system call and pass the file descriptor for the client socket.
close(server_socket);
Once the client is done sending and receiving data from the server, it can close the socket connection using the close()
function and passing the socket's file descriptor server_socket
in the function's argument.
Below, we can see a C application that encloses all the steps that we have gone through above illustrating the process of how Unix sockets and created and user for inter-process communication.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> int main() { int server_socket; struct sockaddr_un server_addr; int connection_result; char ch='C'; server_socket = socket(AF_UNIX, SOCK_STREAM, 0); server_addr.sun_family = AF_UNIX; strcpy(server_addr.sun_path, "unix_socket"); connection_result = connect(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)); if (connection_result == -1) { perror("Error:"); exit(1); } write(server_socket, &ch, 1); read(server_socket, &ch, 1); printf("Client: I recieved %c from server!\n", ch); close(server_socket); exit(0); }
When we click the run button, the client passes the letter C to the server. The server then increments the ASCII value of the letter sent by the client and sends the incremented character back to the client.
Note: We can change the letter sent to the server by modifying the
char
variablech
in line 15 of the fileclient.c
and re-running the application.
IPC (inter-process communication) inside the same host is made strong and effective by Unix socket programming in C. Developers may build reliable and high-performance communication channels for processes operating on the same computer by using Unix domain sockets.
Free Resources