So, it’s time for some light-weight demo code.

You can find this Tiny RPc Go project at mizumoto-cn/TRPG

TRPcG

License No Nazism Allowed

Build Go Report Card CodeFactor codecov License


What is TRPcG ✈

TRPcG is short for “Tiny Remote Procedure-call in Go”, a fast, stable, light-weight and high-performance RPC framework for Go.

It’s also a example net/rpc-based RPC framework which can help people better understand RPC.

  • TCP based
  • Support for multiple compression formats : gzip, snappy, zlib, etc.
  • Implemented protocol buffer. May be cross-platform in future.
  • protoc-gen-trpcg plug-in allows you define your own service.
  • Support for custom serialization. See Customize

Install & Quick Start

go install github.com/golang/protobuf/protoc-gen-go
go install github.com/mizumoto-cn/TRPcG/proto-gen

Then you’ll need to create a arith.proto file to define the rpc services.

Use protoc to generate code:

protoc --trpcg_out=. arith.proto --go_out=. arith.proto

Two files will be generated in the directory message: arith.pb.go and arith.svr.go

Then you need a new main.go like main.go.bak

After that you can implement your rpc client.

...
conn, err := net.Dial("tcp", ":8082")
if err != nil {
    log.Fatal(err)
}
defer conn.Close()
client := TRPcG.NewClient(conn)
req := &message.ArithRequest{ A: 1, B: 2 }
res := &message.ArithResponse{}
err = client.Call("Arith.Add", &req, &res)
log.Printf("%d + %d = %d, %v", req.A, req.B, res.C, err)
...

You may also use AsyncCall to get a asynchronous return in the form of *rpc.Call


result := client.AsyncCall("ArithService.Add", &resq, &resp)
select {
case call := <-result:
	log.Printf("Arith.Add(%v, %v): %v ,Error: %v", resq.A, resq.B, resp.C, call.Error)
case <-time.After(100 * time.Microsecond):
	log.Fatal("time out")
}

Customize

Compressor

You can choose to use ‘gzip’, ‘snappy’, ‘zlib’, or ‘raw’ to compress the data.

Like:

import "github.com/mizumoto-cn/TRPcG/compressor"

...
client := tinyrpc.NewClient(conn, tinyrpc.WithCompress(compressor.Gzip))

Serializer

You’ll need to implement Serializer interface to use customized serialization.

type Serializer interface {
    Serialize(interface{}) ([]byte, error)
    Deserialize([]byte, interface{}) error
}

Here is a simple implementation of Serializer in json format:


type JsonSerializer struct {}

func (s *JsonSerializer) Serialize(v interface{}) ([]byte, error) {
    return json.Marshal(v)
}

func (s *JsonSerializer) Deserialize(b []byte, v interface{}) error {
    return json.Unmarshal(b, v)
}

And we can use it to define a service like:

type JsonRequest struct {
	Req string `json:"req"`
}

type JsonResponce struct {
	Res string `json:"res"`
}

type RepeatService struct{}

func (_ *JsonService) Repeater(args *JsonRequest, reply *JsonResponse) error {
	reply.Res = args.Req
	return nil
}

Also, we’ll have to assign json serializer to server:

listener, err := net.Listen("tcp", ":8082")
if err != nil {
    log.Fatal(err)
}
server := TRPcG.NewServer(listener, TRPcG.WithSerializer(&JsonSerializer{}))
server.Register(&JsonService{})
server.Serve(listener)

Remember your client also need to assign json serializer when you create it:

client := TRPcG.NewClient(conn, TRPcG.WithSerializer(&JsonSerializer{}))

Architecture

TRPcG Client will send request messages, and which will be three parts: an unsigned-int Header Info, a Header, and a Body based on Protocol Buffers (Google Developers)

Here is a picture of the Request Stream:

Request Stream

Response Stream

uvarint is just like a variable-length unsigned-integer. It’s an encoding of 64-bit unsigned integers into between 1 and 9 bytes.

The Header is based on a customized protocol.

ID is like a serial code of the rpc calls, with which in concurrent cases, clients can determine whether it’s a successful call based on the ID serial number of the response.

for more architecture info, goto wiki

License

This project is governed by Mizumoto General Public License v1.2. Basically a Mozilla 2.0 public license, but with extra restrictions:

By using any part of this project, you are deemed to have fully understanding and acceptance of the following terms:

  1. You must conspicuously display, without modification, this License and the notice on each redistributed or derivative copy of the License Covered Work.
  2. Any non-independent developers companies/groups/legal entities or other organizations should ensure that employees are not oppressed or exploited, and that employees can always receive a reasonable salary for their legal working hours.
  3. Any independent or non-independent developers/companies/groups/legal entities or other organizations, shall ensure that it has a clear conscience, including and not limited to opposition to any form of Nazi or Neo-Nazism organization(s).

Otherwise these Individuals / Companies / Groups / Legal-entities will not have the right to copy / modify / redistribute any code / file / algorithm governed by MGPL v1.2.