「Goならわかるシステムプログラミング」の8章を読んだ。8章はUnixドメインソケットについて。
TCPとUDPによるソケット通信は、外部のネットワークにつながるインタフェースに接続します。 これに対し、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() }() } }
TCP、UDPの時と大きくは変わらない。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
で確認できる。
クライアンドサイド実装
これもTCP、UDPの時と大きくは変わらなそう。こちらも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