Unix Domain Socket com Golang
- Unix Domain Sockets ou IPC Socket
Unix Domain Sockets é uma forma prática e segura de trocar informações entre processos. Essa forma de IPC (Inter-process communication) usa um arquivo como endereço em vez de um IP e uma porta, como na comunicação via rede.
É importante lembrar que o servidor gerencia o arquivo. Se o arquivo não existir, o servidor o cria automaticamente. Se o arquivo já existir, você receberá um erro como “bind: address already in use”. Isso significa que o servidor não pode reutilizar o arquivo existente. Portanto, é correto desligar o servidor de forma elegante, fechar e apagar o arquivo antes de finalizar o servidor. Dependendo do sistema, pode ser interessante verificar se o arquivo existe e apagá-lo antes de iniciar o servidor.
Apesar da facilidade, usar um arquivo como endereço impede a troca de informações entre máquinas diferentes. O kernel mantém a comunicação. O arquivo é apenas um name space. Nenhum byte é realmente escrito no arquivo, que ocupa zero espaço no disco. Toda a comunicação acontece na RAM e é gerenciada pelo kernel.
Servidor
A conexão entre cliente e servidor é similar à conexão via TCP/IP. O servidor ouve o namespace
indicado pelo arquivo e espera por conexões, como se estivesse ouvindo uma porta TCP. Quando uma conexão chega, usamos a função Accept
do pacote net e passamos para uma goroutine
com o handler da conexão.
package main
import (
"fmt"
"io"
"net"
)
func main() {
l, err := net.Listen("unix", "/tmp/echo.sock")
if err != nil {
panic(err)
}
for {
f, err := l.Accept()
if err != nil {
panic(err)
}
go func(c io.ReadWriter) {
for {
buf := make([]byte, 512)
n, err := c.Read(buf)
if err != nil {
return
}
fmt.Printf("echo: %s\r\n", buf[:n])
_, err = c.Write(buf[:n])
if err != nil {
panic(err)
}
}
}(f)
}
}
Cliente
No exemplo de cliente, criamos duas linhas de processamento. Uma é a goroutine que lê tudo que chega pela conexão. A outra é a função main, que envia dados para o servidor em loop. Não precisa ser assim. Dependendo do funcionamento do sistema, você pode enviar uma mensagem para o servidor e iniciar uma goroutine para tratar apenas do timeout. O único cuidado é que essas funções bloqueiam o processamento.
package main
import (
"fmt"
"io"
"net"
"time"
)
func main() {
f, err := net.Dial("unix", "/tmp/echo.sock")
if err != nil {
panic(err)
}
defer f.Close()
go func(r io.Reader) {
buf := make([]byte, 1024)
for {
n, errf := r.Read(buf)
if errf != nil {
panic(err)
}
fmt.Printf("recebido: %s\r\n", buf[:n])
}
}(f)
for {
data := []byte("olá mundo")
fmt.Printf("enviando: %s\r\n", data)
_, err = f.Write([]byte("olá mundo"))
if err != nil {
panic(err)
}
time.Sleep(time.Duration(400) * time.Millisecond)
}
}
Servidor usando netcat
Para testes, podemos usar o netcat.
nc -lU /tmp/echo.sock && rm /tmp/echo.sock
Cliente usando netcat
nc -U /tmp/echo.sock
Conclusão
Unix Domain Sockets é bem interessante, a comunicação é feita na RAM, sem ocupar espaço em disco não existe a necessidade de configurar um IP e uma porta e não expõe a comunicação para a rede e aceita permisões de arquivos tornando o processo mais seguro. A única desvantagem é que a comunicação é limitada a processos na mesma máquina.