eRPC is an efficient, extensible and easy-to-use RPC framework.
Suitable for RPC, Microservice, Peer-to-Peer, IM, Game and other fields.

go vesion ≥ 1.18
install
GO111MODULE=on go get -u -v -insecure github.com/andeya/erpc/v7
import "github.com/andeya/erpc/v7"
Header and the BodyHeader contains metadata in the same format as HTTP headerBody supports for custom codec of Content Type-Like, already implemented:rawproto - Default high performance binary protocoljsonproto - JSON message protocolpbproto - Ptotobuf message protocolthriftproto - Thrift message protocolhttproto - HTTP message protocoltcptcp4tcp6unixunixpacketkcpquicSelf Test
Sent total 1000000 messages
erpc
| client concurrency | mean(ms) | median(ms) | max(ms) | min(ms) | throughput(TPS) |
|---|---|---|---|---|---|
| 100 | 1 | 0 | 16 | 0 | 75505 |
| 500 | 9 | 11 | 97 | 0 | 52192 |
| 1000 | 19 | 24 | 187 | 0 | 50040 |
| 2000 | 39 | 54 | 409 | 0 | 42551 |
| 5000 | 96 | 128 | 1148 | 0 | 46367 |
| client concurrency | mean(ms) | median(ms) | max(ms) | min(ms) | throughput(TPS) |
|---|---|---|---|---|---|
| 100 | 0 | 0 | 14 | 0 | 225682 |
| 500 | 2 | 1 | 24 | 0 | 212630 |
| 1000 | 4 | 3 | 51 | 0 | 180733 |
| 2000 | 8 | 6 | 64 | 0 | 183351 |
| 5000 | 21 | 18 | 651 | 0 | 133886 |
Comparison Test
| Environment | Throughputs | Mean Latency | P99 Latency |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |


package main
import (
"fmt"
"time"
"github.com/andeya/erpc/v7"
)
func main() {
defer erpc.FlushLogger()
// graceful
go erpc.GraceSignal()
// server peer
srv := erpc.NewPeer(erpc.PeerConfig{
CountTime: true,
ListenPort: 9090,
PrintDetail: true,
})
// srv.SetTLSConfig(erpc.GenerateTLSConfigForServer())
// router
srv.RouteCall(new(Math))
// broadcast per 5s
go func() {
for {
time.Sleep(time.Second * 5)
srv.RangeSession(func(sess erpc.Session) bool {
sess.Push(
"/push/status",
fmt.Sprintf("this is a broadcast, server time: %v", time.Now()),
)
return true
})
}
}()
// listen and serve
srv.ListenAndServe()
}
// Math handler
type Math struct {
erpc.CallCtx
}
// Add handles addition request
func (m *Math) Add(arg *[]int) (int, *erpc.Status) {
// test meta
erpc.Infof("author: %s", m.PeekMeta("author"))
// add
var r int
for _, a := range *arg {
r += a
}
// response
return r, nil
}
package main
import (
"time"
"github.com/andeya/erpc/v7"
)
func main() {
defer erpc.SetLoggerLevel("ERROR")()
cli := erpc.NewPeer(erpc.PeerConfig{})
defer cli.Close()
// cli.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
cli.RoutePush(new(Push))
sess, stat := cli.Dial(":9090")
if !stat.OK() {
erpc.Fatalf("%v", stat)
}
var result int
stat = sess.Call("/math/add",
[]int{1, 2, 3, 4, 5},
&result,
erpc.WithAddMeta("author", "andeya"),
).Status()
if !stat.OK() {
erpc.Fatalf("%v", stat)
}
erpc.Printf("result: %d", result)
erpc.Printf("Wait 10 seconds to receive the push...")
time.Sleep(time.Second * 10)
}
// Push push handler
type Push struct {
erpc.PushCtx
}
// Push handles '/push/status' message
func (p *Push) Status(arg *string) *erpc.Status {
erpc.Printf("%s", *arg)
return nil
}
NOTE:
SetReadLimit// Start a server
var peer1 = erpc.NewPeer(erpc.PeerConfig{
ListenPort: 9090, // for server role
})
peer1.Listen()
...
// Start a client
var peer2 = erpc.NewPeer(erpc.PeerConfig{})
var sess, err = peer2.Dial("127.0.0.1:8080")
type Aaa struct {
erpc.CallCtx
}
func (x *Aaa) XxZz(arg *<T>) (<T>, *erpc.Status) {
...
return r, nil
}
// register the call route
// HTTP mapping: /aaa/xx_zz
// RPC mapping: Aaa.XxZz
peer.RouteCall(new(Aaa))
// or register the call route
// HTTP mapping: /xx_zz
// RPC mapping: XxZz
peer.RouteCallFunc((*Aaa).XxZz)
The default mapping(HTTPServiceMethodMapper) of struct(func) name to service methods:
AaBb -> /aa_bbABcXYz -> /abc_xyzAa__Bb -> /aa_bbaa__bb -> /aa_bbABC__XYZ -> /abc_xyzAa_Bb -> /aa/bbaa_bb -> /aa/bbABC_XYZ -> /abc/xyz
go
erpc.SetServiceMethodMapper(erpc.HTTPServiceMethodMapper)The mapping(RPCServiceMethodMapper) of struct(func) name to service methods:
AaBb -> AaBbABcXYz -> ABcXYzAa__Bb -> Aa_Bbaa__bb -> aa_bbABC__XYZ -> ABC_XYZAa_Bb -> Aa.Bbaa_bb -> aa.bbABC_XYZ -> ABC.XYZ
go
erpc.SetServiceMethodMapper(erpc.RPCServiceMethodMapper)func XxZz(ctx erpc.CallCtx, arg *<T>) (<T>, *erpc.Status) {
...
return r, nil
}
// register the call route
// HTTP mapping: /xx_zz
// RPC mapping: XxZz
peer.RouteCallFunc(XxZz)
type Bbb struct {
erpc.PushCtx
}
func (b *Bbb) YyZz(arg *<T>) *erpc.Status {
...
return nil
}
// register the push handler
// HTTP mapping: /bbb/yy_zz
// RPC mapping: Bbb.YyZz
peer.RoutePush(new(Bbb))
// or register the push handler
// HTTP mapping: /yy_zz
// RPC mapping: YyZz
peer.RoutePushFunc((*Bbb).YyZz)
// YyZz register the handler
func YyZz(ctx erpc.PushCtx, arg *<T>) *erpc.Status {
...
return nil
}
// register the push handler
// HTTP mapping: /yy_zz
// RPC mapping: YyZz
peer.RoutePushFunc(YyZz)
func XxxUnknownCall (ctx erpc.UnknownCallCtx) (interface{}, *erpc.Status) {
...
return r, nil
}
// register the unknown call route: /*
peer.SetUnknownCall(XxxUnknownCall)
func XxxUnknownPush(ctx erpc.UnknownPushCtx) *erpc.Status {
...
return nil
}
// register the unknown push route: /*
peer.SetUnknownPush(XxxUnknownPush)
// NewIgnoreCase Returns a ignoreCase plugin.
func NewIgnoreCase() *ignoreCase {
return &ignoreCase{}
}
type ignoreCase struct{}
var (
_ erpc.PostReadCallHeaderPlugin = new(ignoreCase)
_ erpc.PostReadPushHeaderPlugin = new(ignoreCase)
)
func (i *ignoreCase) Name() string {
return "ignoreCase"
}
func (i *ignoreCase) PostReadCallHeader(ctx erpc.ReadCtx) *erpc.Status {
// Dynamic transformation path is lowercase
ctx.UriObject().Path = strings.ToLower(ctx.UriObject().Path)
return nil
}
func (i *ignoreCase) PostReadPushHeader(ctx erpc.ReadCtx) *erpc.Status {
// Dynamic transformation path is lowercase
ctx.UriObject().Path = strings.ToLower(ctx.UriObject().Path)
return nil
}
// add router group
group := peer.SubRoute("test")
// register to test group
group.RouteCall(new(Aaa), NewIgnoreCase())
peer.RouteCallFunc(XxZz, NewIgnoreCase())
group.RoutePush(new(Bbb))
peer.RoutePushFunc(YyZz)
peer.SetUnknownCall(XxxUnknownCall)
peer.SetUnknownPush(XxxUnknownPush)
type PeerConfig struct {
Network string `yaml:"network" ini:"network" comment:"Network; tcp, tcp4, tcp6, unix, unixpacket, kcp or quic"`
LocalIP string `yaml:"local_ip" ini:"local_ip" comment:"Local IP"`
ListenPort uint16 `yaml:"listen_port" ini:"listen_port" comment:"Listen port; for server role"`
DialTimeout time.Duration `yaml:"dial_timeout" ini:"dial_timeout" comment:"Default maximum duration for dialing; for client role; ns,µs,ms,s,m,h"`
RedialTimes int32 `yaml:"redial_times" ini:"redial_times" comment:"The maximum times of attempts to redial, after the connection has been unexpectedly broken; Unlimited when <0; for client role"`
RedialInterval time.Duration `yaml:"redial_interval" ini:"redial_interval" comment:"Interval of redialing each time, default 100ms; for client role; ns,µs,ms,s,m,h"`
DefaultBodyCodec string `yaml:"default_body_codec" ini:"default_body_codec" comment:"Default body codec type id"`
DefaultSessionAge time.Duration `yaml:"default_session_age" ini:"default_session_age" comment:"Default session max age, if less than or equal to 0, no time limit; ns,µs,ms,s,m,h"`
DefaultContextAge time.Duration `yaml:"default_context_age" ini:"default_context_age" comment:"Default CALL or PUSH context max age, if less than or equal to 0, no time limit; ns,µs,ms,s,m,h"`
SlowCometDuration time.Duration `yaml:"slow_comet_duration" ini:"slow_comet_duration" comment:"Slow operation alarm threshold; ns,µs,ms,s ..."`
PrintDetail bool `yaml:"print_detail" ini:"print_detail" comment:"Is print body and metadata or not"`
CountTime bool `yaml:"count_time" ini:"count_time" comment:"Is count cost time or not"`
}
SetMessageSizeLimit sets max message size. If maxSize<=0, set it to max uint32.
go
func SetMessageSizeLimit(maxMessageSize uint32)
SetSocketKeepAlive sets whether the operating system should send keepalive messages on the connection.
go
func SetSocketKeepAlive(keepalive bool)
SetSocketKeepAlivePerio