What are Unix Sockets?
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:
- 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.
- They are also more secure because they are not exposed to the network.
- 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.
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
- We are using the
net.Listen
function to listen on a Unix socket. - The first argument is the network type, which is
unix
in our case. The second argument is thepath
to the Unix socket file. - It is preferred to use the
/tmp
directory for the Unix socket file, as it is a common location for temporary files. - We are using the
http.Serve
function to serve the requests on the Unix socket. - 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.
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
- We are using the
net.Dial
function to dial the Unix socket. - The first argument is the network type, which is
unix
in our case. The second argument is thepath
to the Unix socket file. - We are creating an HTTP client with the Unix socket connection.
- We are sending a GET request to the server using the Unix socket.
- We are reading the response body and printing it.
Now let’s run the server and the client and see the output.
cd server
go run main.go
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.
cd client
ping -c 4 google.com
go run main.go
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.
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 .
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.
sudo chmod a+rw /tmp/demo.sock
Finally, you can run the NGINX
server and access the HTTP
server using the NGINX
server.
sudo nginx
curl localhost:8081/ram
Use Cases
- Microservices: You can use Unix sockets to communicate between microservices running on the same machine.
- 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.
- Security: Unix sockets are more secure than TCP/IP sockets because they are not exposed to the network.