いろいろ備忘録日記

主に .NET とか Go とか Python絡みのメモを公開しています。

Goメモ-105 (gRPCでUnixドメインソケットのメモ)

概要

少し前に Unix ドメインソケット のメモを書いたのですが

devlights.hatenablog.com

ついでなので、gRPC で Unix ドメインソケット のサンプルも作ってみました。

サンプルについて

今回もサンプルはついでなので github にアップしてあります。良かったらご参照ください。

github.com

gRPC を動作させる上で必要なものをインストール

gRPC を Go で動作させるには

  • protocol buffers の コンパイラ protoc
  • grpc の Go ライブラリ

が必要になります。インストールについては以下の記事にとてもわかり易く書いてくださってる方がいます。感謝 m( )m

qiita.com

上の記事を見ながらインストールしてもいいのですが、gRPCでサンプル書くたびに打つの面倒なので

gistに makefile アップしときました。

gRPC と Go のプログラム作るときに使えるmakefile

この makefile 落として

make install-requirements

ってやるとprotocとかgRPCのライブラリを落としてきます。

ご利用される場合は、makefile内の protoc のURLとかprotocの生成物の置き場所とかを適時ご自身の環境に調整ください。

私は Crostini (Chromebook の Linux) で作業していますので、ARM64版 (aarch64) のprotoc を使っています。

protoファイルを作成

セットアップが終わったら、次は protoファイルの作成です。

以下のようにしました。

syntax = "proto3";

package echo;
option go_package = "internal/pb";

service Echo {
    rpc Echo (EchoMessage) returns (EchoResponse) {}
}

message EchoMessage {
    string data = 1;
}

message EchoResponse {
    string data = 1;
}

protoファイルからGoのコードを生成

次に protoファイル からGoのコードを生成します。

上で載せている makefile には、すでにタスクを定義してあります。

make protoc

で生成されます。

サービスを実装

以下のように実装しました。リクエスト受けたデータを大文字にして返すだけです。

package service

import (
    "context"
    "strings"

    "github.com/devlights/go-grpc-uds-example/internal/pb"
)

type EchoServiceImpl struct {
}

var _ pb.EchoServer = (*EchoServiceImpl)(nil)

func NewEchoService() pb.EchoServer {
    return new(EchoServiceImpl)
}

func (e *EchoServiceImpl) Echo(ctx context.Context, message *pb.EchoMessage) (*pb.EchoResponse, error) {
    s := strings.ToUpper(message.Data)
    r := &pb.EchoResponse{
        Data: s,
    }

    return r, nil
}

サーバとクライアントを実装

後は、それを呼び出すコードをサーバとクライアント分書けばいいだけです。楽ですねー gRPC。

サーバ側

gRPC で Unix ドメインソケットを利用する場合、サーバ側は特別違いはありません。

普通にリスナーを作って、それを gRPC にホストしてもらうように設定すれば終わりです。

package main

import (
    "log"
    "net"
    "os"

    "github.com/devlights/go-grpc-uds-example/internal/pb"
    "github.com/devlights/go-grpc-uds-example/internal/service"
    "google.golang.org/grpc"
)

const (
    protocol = "unix"
    sockAddr = "/tmp/echo.sock"
)

func main() {
    // - https://qiita.com/marnie_ms4/items/4582a1a0db363fe246f3
    // - http://yamahiro0518.hatenablog.com/entry/2016/02/01/215908

    cleanup := func() {
        if _, err := os.Stat(sockAddr); err == nil {
            if err := os.RemoveAll(sockAddr); err != nil {
                log.Fatal(err)
            }
        }
    }

    cleanup()

    listener, err := net.Listen(protocol, sockAddr)
    if err != nil {
        log.Fatal(err)
    }

    server := grpc.NewServer()
    echo := service.NewEchoService()

    pb.RegisterEchoServer(server, echo)

    server.Serve(listener)
}

クライアント側

クライアント側は、いつもの gRPC の Dial 呼び出しと少し変わります。

そのままだと、TCPになってしまうので、オプションで Dialer を渡すようにします。

それ以降は、いつもどおりです。

package main

import (
    "context"
    "fmt"
    "log"
    "net"
    "time"

    "github.com/devlights/go-grpc-uds-example/internal/pb"
    "google.golang.org/grpc"
)

const (
    protocol = "unix"
    sockAddr = "/tmp/echo.sock"
)

func main() {
    // - https://qiita.com/marnie_ms4/items/4582a1a0db363fe246f3
    // - http://yamahiro0518.hatenablog.com/entry/2016/02/01/215908

    dialer := func(addr string, t time.Duration) (net.Conn, error) {
        return net.Dial(protocol, addr)
    }

    conn, err := grpc.Dial(sockAddr, grpc.WithInsecure(), grpc.WithDialer(dialer))
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    var (
        client  = pb.NewEchoClient(conn)
        rootCtx = context.Background()
        timeout = 1 * time.Second
        values  = []string{
            "hello world",
            "golang",
            "goroutine",
            "this program runs on crostini",
        }
    )

    for _, v := range values {
        func() {
            ctx, cancel := context.WithTimeout(rootCtx, timeout)
            defer cancel()

            message := pb.EchoMessage{Data: v}

            res, err := client.Echo(ctx, &message)
            if err != nil {
                log.Println(err)
                return
            }

            fmt.Println(res)
        }()
    }
}

実行してみる

実行すると以下のようになります。

$ make run
go run cmd/server/server.go &
sleep 1
go run cmd/client/client.go
data:"HELLO WORLD"
data:"GOLANG"
data:"GOROUTINE"
data:"THIS PROGRAM RUNS ON CROSTINI"
sleep 1
pkill server
true
signal: terminated

ちゃんと大文字になって返ってきていますね。通信の送受信部分を一切自分で書いていないのに動いてくれるって、とても便利。

通信内容を覗いてみる

どんなデータが流れているのでしょうか。Unixドメインソケットの通信データを覗く方法は上にリンク貼ってる前々回の記事を参照ください。

私の環境では以下のようになっていました。

> 2020/08/26 12:55:25.623421  length=33 from=0 to=32
 50 52 49 20 2a 20 48 54 54 50 2f 32 2e 30 0d 0a  PRI * HTTP/2.0..
 0d 0a                                            ..
 53 4d 0d 0a                                      SM..
 0d 0a                                            ..
 00 00 00 04 00 00 00 00 00                       .........
--
< 2020/08/26 12:55:25.625373  length=15 from=0 to=14
 00 00 06 04 00 00 00 00 00 00 05 00 00 40 00     .............@.
--
< 2020/08/26 12:55:25.625875  length=9 from=15 to=23
 00 00 00 04 01 00 00 00 00                       .........
--
> 2020/08/26 12:55:25.626531  length=9 from=33 to=41
 00 00 00 04 01 00 00 00 00                       .........
--
> 2020/08/26 12:55:25.627203  length=116 from=42 to=157
 00 00 50 01 04 00 00 00 01 83 86 45 8b 60 a4 9c  ..P........E.`..
 eb e0 24 e7 63 01 27 3f 41 8a 61 34 d6 c1 49 39  ..$.c.'?A.a4..I9
 d7 41 c9 d7 5f 8b 1d 75 d0 62 0d 26 3d 4c 4d 65  .A.._..u.b.&=LMe
 64 7a 8a 9a ca c8 b4 c7 60 2b b2 15 c3 40 02 74  dz......`+...@.t
 65 86 4d 83 35 05 b1 1f 40 89 9a ca c8 b2 4d 49  e.M.5...@.....MI
 4f 6a 7f 85 7d f6 84 27 6d 00 00 12 00 01 00 00  Oj..}..'m.......
 00 01 00 00 00 00 0d 0a                          ........
 0b 68 65 6c 6c 6f 20 77 6f 72 6c 64              .hello world
--
< 2020/08/26 12:55:25.630630  length=30 from=24 to=53
 00 00 04 08 00 00 00 00 00 00 00 00 12 00 00 08  ................
 06 00 00 00 00 00 02 04 10 10 09 0e 07 07        ..............
--
< 2020/08/26 12:55:25.631389  length=83 from=54 to=136
 00 00 0e 01 04 00 00 00 01 88 5f 8b 1d 75 d0 62  .........._..u.b
 0d 26 3d 4c 4d 65 64 00 00 12 00 00 00 00 00 01  .&=LMed.........
 00 00 00 00 0d 0a                                ......
 0b 48 45 4c 4c 4f 20 57 4f 52 4c 44 00 00 18 01  .HELLO WORLD....
 05 00 00 00 01 40 88 9a ca c8 b2 12 34 da 8f 01  .....@......4...
 30 40 89 9a ca c8 b5 25 42 07 31 7f 00           0@.....%B.1..
--
> 2020/08/26 12:55:25.633103  length=17 from=158 to=174
 00 00 08 06 01 00 00 00 00 02 04 10 10 09 0e 07  ................
 07                                               .
--
> 2020/08/26 12:55:25.635127  length=76 from=175 to=250
 00 00 04 08 00 00 00 00 00 00 00 00 12 00 00 08  ................
 06 00 00 00 00 00 02 04 10 10 09 0e 07 07 00 00  ................
 0f 01 04 00 00 00 03 83 86 c3 c2 c1 c0 bf 7e 86  ..............~.
 7d f7 df 69 9b 7f 00 00 0d 00 01 00 00 00 03 00  }..i............
 00 00 00 08 0a                                   .....
 06 67 6f 6c 61 6e 67                             .golang
--
< 2020/08/26 12:55:25.642211  length=91 from=137 to=227
 00 00 08 06 01 00 00 00 00 02 04 10 10 09 0e 07  ................
 07 00 00 04 08 00 00 00 00 00 00 00 00 0d 00 00  ................
 08 06 00 00 00 00 00 02 04 10 10 09 0e 07 07 00  ................
 00 02 01 04 00 00 00 03 88 c0 00 00 0d 00 00 00  ................
 00 00 03 00 00 00 00 08 0a                       .........
 06 47 4f 4c 41 4e 47 00 00 02 01 05 00 00 00 03  .GOLANG.........
 bf be                                            ..
--
> 2020/08/26 12:55:25.651475  length=47 from=251 to=297
 00 00 08 06 01 00 00 00 00 02 04 10 10 09 0e 07  ................
 07 00 00 04 08 00 00 00 00 00 00 00 00 0d 00 00  ................
 08 06 00 00 00 00 00 02 04 10 10 09 0e 07 07     ...............
--
> 2020/08/26 12:55:25.654727  length=49 from=298 to=346
 00 00 0f 01 04 00 00 00 05 83 86 c4 c3 c2 c1 c0  ................
 7e 86 7d f7 de 79 ab 7f 00 00 10 00 01 00 00 00  ~.}..y..........
 05 00 00 00 00 0b 0a                             .......
 09 67 6f 72 6f 75 74 69 6e 65                    .goroutine
--
< 2020/08/26 12:55:25.657198  length=17 from=228 to=244
 00 00 08 06 01 00 00 00 00 02 04 10 10 09 0e 07  ................
 07                                               .
--
< 2020/08/26 12:55:25.658443  length=77 from=245 to=321
 00 00 04 08 00 00 00 00 00 00 00 00 10 00 00 08  ................
 06 00 00 00 00 00 02 04 10 10 09 0e 07 07 00 00  ................
 02 01 04 00 00 00 05 88 c0 00 00 10 00 00 00 00  ................
 00 05 00 00 00 00 0b 0a                          ........
 09 47 4f 52 4f 55 54 49 4e 45 00 00 02 01 05 00  .GOROUTINE......
 00 00 05 bf be                                   .....
--
> 2020/08/26 12:55:25.664361  length=47 from=347 to=393
 00 00 08 06 01 00 00 00 00 02 04 10 10 09 0e 07  ................
 07 00 00 04 08 00 00 00 00 00 00 00 00 10 00 00  ................
 08 06 00 00 00 00 00 02 04 10 10 09 0e 07 07     ...............
--
> 2020/08/26 12:55:25.665495  length=69 from=394 to=462
 00 00 0f 01 04 00 00 00 07 83 86 c5 c4 c3 c2 c1  ................
 7e 86 7d f7 df 65 bb 7f 00 00 24 00 01 00 00 00  ~.}..e....$.....
 07 00 00 00 00 1f 0a                             .......
 1d 74 68 69 73 20 70 72 6f 67 72 61 6d 20 72 75  .this program ru
 6e 73 20 6f 6e 20 63 72 6f 73 74 69 6e 69        ns on crostini
--
< 2020/08/26 12:55:25.667152  length=17 from=322 to=338
 00 00 08 06 01 00 00 00 00 02 04 10 10 09 0e 07  ................
 07                                               .
--
< 2020/08/26 12:55:25.668900  length=97 from=339 to=435
 00 00 04 08 00 00 00 00 00 00 00 00 24 00 00 08  ............$...
 06 00 00 00 00 00 02 04 10 10 09 0e 07 07 00 00  ................
 02 01 04 00 00 00 07 88 c0 00 00 24 00 00 00 00  ...........$....
 00 07 00 00 00 00 1f 0a                          ........
 1d 54 48 49 53 20 50 52 4f 47 52 41 4d 20 52 55  .THIS PROGRAM RU
 4e 53 20 4f 4e 20 43 52 4f 53 54 49 4e 49 00 00  NS ON CROSTINI..
 02 01 05 00 00 00 07 bf be                       .........
--

ちゃんと最初にHTTP/2.0から始まっていますね。送っているデータはとても単純なデータなのですが内部でいろいろ付与されているので、それなりにバイト数が増えています。まあ、それでも少ないですが。

おすすめ書籍

自分が読んだGo関連の本で、いい本って感じたものです。

Go言語による並行処理

Go言語による並行処理

スターティングGo言語 (CodeZine BOOKS)

スターティングGo言語 (CodeZine BOOKS)

  • 作者:松尾 愛賀
  • 発売日: 2016/04/15
  • メディア: 単行本(ソフトカバー)

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)


過去の記事については、以下のページからご参照下さい。

  • いろいろ備忘録日記まとめ

devlights.github.io

サンプルコードは、以下の場所で公開しています。

  • いろいろ備忘録日記サンプルソース置き場

github.com

github.com

github.com