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)
    }
}

código fonte

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)
    }
}

código fonte

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.

Cesar Gimenes

Última modificação