いろいろ備忘録日記

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

Goメモ-554 (net/rpc)(RPC通信の処理を作りやすくするライブラリ)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。

Goの net はとても便利ですが、 たまに、パパっと通信処理を書いてしまいたいときがあります。

通信メッセージ設計とかも面倒臭い、つまり、RPC通信でパパっと呼び出したいってときです。gRPCを使えば良いってなるのですが、いちいちインストールしたりして用意するのも面倒。

どうせ、Go同士の通信なので言語非依存性とかも要らない。

とかいうときに、net/rpc さんが便利です。内部のエンコードには encoding/gob が利用されているため、Goアプリ専用のRPC通信となりますが、サクッと作れます。

サンプル

main.go

サンプルなので、サーバとクライアント兼用です。

package main

import (
    "flag"
    "fmt"
    "net"
    "net/rpc"
)

type (
    Args struct {
        IsServer bool
        X        int64
        Y        int64
    }
    RpcArgs struct {
        X int64
        Y int64
    }
    RpcReply struct {
        Result int64
    }
    Service struct{}
)

func (me *Service) Multiply(req *RpcArgs, res *RpcReply) error {
    if req == nil {
        return fmt.Errorf("req is nil")
    }

    result := req.X * req.Y
    res.Result = result

    return nil
}

var (
    args Args
)

func init() {
    flag.BoolVar(&args.IsServer, "server", false, "server mode")
    flag.Int64Var(&args.X, "x", 0, "X")
    flag.Int64Var(&args.Y, "y", 0, "Y")
}

func main() {
    flag.Parse()

    if err := run(); err != nil {
        panic(err)
    }
}

func run() error {
    var err error
    switch args.IsServer {
    case true:
        err = runServer()
    default:
        err = runClient()
    }

    if err != nil {
        return err
    }

    return nil
}

func runServer() error {
    //
    // RPCとして公開するサービス登録
    //
    service := new(Service)
    rpc.Register(service)

    l, err := net.Listen("tcp", ":8888")
    if err != nil {
        return err
    }
    defer l.Close()

    rpc.Accept(l)
    // rpc.Accept(net.Listener) は以下と同じ。
    //
    // for {
    //     conn, err := l.Accept()
    //     if err != nil {
    //         return err
    //     }
    //     go rpc.ServeConn(conn)
    // }

    return nil
}

func runClient() error {
    client, err := rpc.Dial("tcp", ":8888")
    if err != nil {
        return err
    }
    defer client.Close()

    //
    // RPC 呼び出し
    //   サービスメソッドの引数は arg, reply ともにポインタで渡すこと
    //
    const (
        serviceMethod = "Service.Multiply"
    )
    var (
        a = RpcArgs{X: args.X, Y: args.Y}
        r RpcReply
    )
    err = client.Call(serviceMethod, &a, &r)
    if err != nil {
        return err
    }

    fmt.Printf("%d * %d = %d\n", args.X, args.Y, r.Result)

    return nil
}

Taskfile.yml

# https://taskfile.dev

version: "3"

tasks:
  default:
    cmds:
      - task: build
      - task: run
  build:
    cmds:
      - go build -o app .
  run:
    cmds:
      - ./app -server &
      - sleep 1
      - ./app -x 2071723 -y 5363222357
      - sleep 1
      - pkill app
  watch:
    cmds:
      - cmd: sudo tcpdump -i lo -n 'tcp port 8888' -S -X
    interactive: true

shell

実行すると以下のように出力されます。

$ task
task: [run] ./app -server &
task: [run] sleep 1
task: [run] ./app -x 2071723 -y 5363222357
2071723 * 5363222357 = 11111111111111111
task: [run] sleep 1
task: [run] pkill app

参考情報

https://github.com/devlights/try-golang-extlib/tree/main/examples/socket/tcp_rpcgithub.com

Goのおすすめ書籍


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

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