Blazingly Fast Interprocess communication in Go using UNIX Sockets!!
Tags: go, linux, backend, nginx
Published: 2024-03-17 13:20:36

## What are Unix Sockets?

![Unix Sockets](https://achintya-x7.vercel.app/blog/unix-go/communication.png)

A UNIX socket, AKA Unix Domain Socket, is an inter-process communication mechanism that allows bidirectional data exchange between processes. It is similar to TCP/IP sockets, but it does not use the network stack, and it is used for communication between processes on the same machine. 

## Why use Unix Sockets?
Unix sockets have various advantages as mentioned below:
1. 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.
2. They are also more secure because they are not exposed to the network.
3. They are also more reliable to use because they are not dependent on the network.

## How to use Unix Sockets in Go?
We will be using the `Gin` web framework which allows us to route unix sockets pretty similarly like APIs. This makes the code more easy and familiar to work with.

### Lets create a Gin server which listens on a Unix Socket and returns a the current ram usage of the process.


```go

package main

import (
	"fmt"
	"net"
	"net/http"
	"runtime"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()

	router.GET("/ram", func(c *gin.Context) {
		var m runtime.MemStats
		runtime.ReadMemStats(&m)

		// Convert bytes to megabytes
		mb := m.Alloc / 1024 / 1024

		c.String(http.StatusOK, fmt.Sprintf("Current RAM usage: %v MB", mb))
	})

	listener, err := net.Listen("unix", "/tmp/demo.sock")
	if err != nil {
		panic(err)
	}

	http.Serve(listener, router)
}
```
### Code Explanation
1. We are using the `net.Listen` function to listen on a Unix socket. 
2. The first argument is the network type, which is `unix` in our case. The second argument is the `path` to the Unix socket file. 
3. It is preferred to use the `/tmp` directory for the Unix socket file, as it is a common location for temporary files.
4. We are using the `http.Serve` function to serve the requests on the Unix socket.
5. As per the router, we have a single route `/ram` which returns the current RAM usage of the process.

### Now, lets create the client which connects to the Unix socket and fetches the RAM usage.

```go

package main

import (
	"context"
	"fmt"
	"io"
	"net"
	"net/http"
)

func main() {
	// Dial the Unix socket
	conn, err := net.Dial("unix", "/tmp/demo.sock")
	if err != nil {
		panic(err)
	}

	// Create an HTTP client with the Unix socket connection
	client := http.Client{
		Transport: &http.Transport{
			DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
				return conn, nil
			},
		},
	}

	// Send a GET request to the server
	resp, err := client.Get("http://unix/ram")
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	// Read the response body
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}

	// Print the response body
	fmt.Println(string(body))
}
```
### Code explanation
1. We are using the `net.Dial` function to dial the Unix socket.
2. The first argument is the network type, which is `unix` in our case. The second argument is the `path` to the Unix socket file.
3. We are creating an HTTP client with the Unix socket connection.
4. We are sending a GET request to the server using the Unix socket.
5. We are reading the response body and printing it.

## Now let's run the server and the client and see the output.
```bash
cd server
go run main.go
```
```bash
go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /ram                      --> main.main.func1 (3 handlers)
[GIN] 2024/03/17 - 20:49:55 | 200 |     346.791µs |                 | GET      "/ram"
```

You can actually disconnect the internet and run the client to see that it still works. For reference, I am running a ping command to google.com and then running the client to show that it still works.

```bash
cd client
ping -c 4 google.com
go run main.go
```
```bash
ping -c 4 google.com
go run main.go

ping: google.com: Temporary failure in name resolution
Current RAM usage: 2 MB
```
## Can I use this as a normal TCP/IP server?
The simple answer is **YES!**

You can use `NGINX` to `proxy pass` your unix socket and use it as a simple `HTTP` server. Here is a `NGINX` config which can be imported into the `nginx.conf` file.
```nginx
upstream demo {
    server unix:/tmp/demo.sock;
}
 
# the nginx server instance
server {
    listen 8081;
    server_name go.dev *.go.dev;
 
    location / {
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_set_header X-NginX-Proxy true;
      proxy_http_version 1.1; # for keep-alive
      proxy_pass http://demo/;
      proxy_redirect off;
    }
}
```
*NOTE: If you want to know more about `NGINX` and how to use it, you can refer to one of other blogs: [Deploy on Linux with Systemctl and Nginx ](https://achintya-x7.vercel.app/blog/linux-nginx/).*

You might also need to change the permissions of the socket file to allow `NGINX` to access it. You can do that by running the following command.
```bash
sudo chmod a+rw /tmp/demo.sock
```
Finally, you can run the `NGINX` server and access the `HTTP` server using the `NGINX` server.
```bash
sudo nginx
curl localhost:8081/ram
```

## Use Cases
1. **Microservices**: You can use Unix sockets to communicate between microservices running on the same machine.
2. **GUI Applications**: You can use Unix sockets to communicate between a GUI application and a backend server. Docker Desktop and Docker CLI uses Unix sockets to communicate with the Docker Daemon.
3. **Security**: Unix sockets are more secure than TCP/IP sockets because they are not exposed to the network.