yikegaya’s blog

yikegayaのブログ

「Goならわかるシステムプログラミング」の8章を読んだ(Unixドメインソケット)

「Goならわかるシステムプログラミング」の8章を読んだ。8章はUnixドメインソケットについて。

TCPUDPによるソケット通信は、外部のネットワークにつながるインタフェースに接続します。 これに対し、Unixドメインソケットでは外部インタフェースへの接続は行いません。 その代わり、カーネル内部で完結する高速なネットワークインタフェースを作成します。 Unixドメインソケットを使うことで、ウェブサーバーとNGINXなどのリバースプロキシとの間、あるいはウェブサーバーとデータベースとの間の接続を高速にできる場合があります。

railsでpumaとか使うときにソケットファイルが必要だったりしたけどこれのことか。。(知らなかった)

最低限のコード

TCPとほとんど同じ

サーバサイド

conn, err := net.Dial("unix", "socketfile")
if err != nil {
  panic(err)
}

クライアントサイド

listener, err := net.Listen("unix", "socketfile")
if err != nil {
  panic(err)
}
defer listener.Close()
conn, err := listener.Accept()
if err != nil {
  // エラー処理
}

Unixドメインソケット版のHTTPサーバを作る

サーバサイド実装

package main

import (
    "bufio"
    "fmt"
    "io/ioutil"
    "net"
    "net/http"
    "net/http/httputil"
    "os"
    "path/filepath"
    "strings"
)

func main() {
    path := filepath.Join(os.TempDir(), "unixdomainsocket-sample")
    os.Remove(path)
    listener, err := net.Listen("unix", path)
    if err != nil {
        panic(err)
    }
    defer listener.Close()
    fmt.Println("Server is running at " + path)
    for {
        conn, err := listener.Accept()
        if err != nil {
            panic(err)
        }
        go func() {
            fmt.Printf("Accept %v\n", conn.RemoteAddr())
            request, err := http.ReadRequest(bufio.NewReader(conn))
            if err != nil {
                panic(err)
            }
            dump, err := httputil.DumpRequest(request, true)
            if err != nil {
                panic(err)
            }
            fmt.Println(string(dump))
            response := http.Response{
                StatusCode: 200,
                ProtoMajor: 1,
                ProtoMinor: 0,
                Body: ioutil.NopCloser(strings.NewReader("Hello World\n")),
            }
            response.Write(conn)
            conn.Close()
        }()
    }
}

TCPUDPの時と大きくは変わらない。net.Listenにunix指定するとUnixドメインソケットが使われる。 unixdomainsocket-sampleパスにソケットが作られる。

go run main.go
Server is running at /var/folders/9d/kwmd2hfx44q7_f3s_t8l5vtr0000gn/T/unixdomainsocket-sample

ls -la /var/folders/9d/kwmd2hfx44q7_f3s_t8l5vtr0000gn/T/unixdomainsocket-sample
srwxr-xr-x  1 ikegayayuuki  staff  0  1 13 17:26 /var/folders/9d/kwmd2hfx44q7_f3s_t8l5vtr0000gn/T/unixdomainsocket-sample

lsしたときに先頭にsがついていればソケットファイル。

ローカルのソケットファイルの状態はnetstat -uで確認できる。

クライアンドサイド実装

これもTCPUDPの時と大きくは変わらなそう。こちらもnet.Dialでunixを指定。

package main

import (
    "bufio"
    "fmt"
    "net"
    "net/http"
    "net/http/httputil"
    "os"
    "path/filepath"
)

func main() {
    conn, err := net.Dial("unix", filepath.Join(os.TempDir(), "unixdomainsocket-sample"))
    if err != nil {
        panic(err)
    }
    request, err := http.NewRequest("get", "http://localhost:8888", nil)
    if err != nil {
        panic(err)
    }
    request.Write(conn)
    response, err := http.ReadResponse(bufio.NewReader(conn), request)
    if err != nil {
        panic(err)
    }
    dump, err := httputil.DumpResponse(response, true)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(dump))
}

UDP相当の使い方ができるデータグラム型のUnixドメインソケットの実装。

net.ListenPacket()でプロトコルとして"udp"ではなく"unixgram"を指定

package main

import (
    "log"
    "net"
    "os"
    "path/filepath"
)

func main() {
    clientPath := filepath.Join(os.TempDir(), "unixdomainsocket-client")
    os.Remove(clientPath)
    conn, err := net.ListenPacket("unixgram", clientPath)
    if err != nil {
        panic(err)
    }

    unixServerAddr, err := net.ResolveUnixAddr("unixgram", filepath.Join(os.TempDir(), "unixdomainsocket-server"))

    var serverAddr net.Addr = unixServerAddr
    if err != nil {
        panic(err)
    }
    defer conn.Close()
    log.Println("Sending to server")
    _, err = conn.WriteTo([]byte("Hello from Client"), serverAddr)
    if err != nil {
        panic(err)
    }
    log.Println("Receiving from server")
    buffer := make([]byte, 1500)
    length, _, err := conn.ReadFrom(buffer)
    if err != nil {
        panic(err)
    }
    log.Printf("Received: %s\n", string(buffer[:length]))
}

ベンチマーク

go test -bench . ⏎
testing: warning: no tests to run
BenchmarkTCPServer-8 1000 7989037 ns/op
BenchmarkUDSStreamServer-8 20000 91136 ns/op

何度か実行すると、多少の変動はありますが、Unixドメインソケットのほうが80倍から90倍高速なことがわかります。