Golang中rpc的几种实现

2019-03-17

什么是RPC

远程过程调用(Remote Procedure Call,缩写为 RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。如果涉及的软件采用面向对象编程,那么远程过程调用亦可称作远程调用或远程方法调用。

用通俗易懂的语言描述就是:RPC允许跨机器、跨语言调用计算机程序方法。

golang中如何实现RPC

在golang中实现RPC非常简单,有封装好的官方库和一些第三方库提供支持。Go RPC可以利用tcp或http来传递数据,可以对要传递的数据使用多种类型的编解码方式。golang官方的net/rpc库使用encoding/gob进行编解码,支持tcp或http数据传输方式,由于其他语言不支持gob编解码方式,所以使用net/rpc库实现的RPC方法没办法进行跨语言调用。

golang官方还提供了net/rpc/jsonrpc库实现RPC方法,JSON RPC采用JSON进行数据编解码,因而支持跨语言调用。但目前的jsonrpc库是基于tcp协议实现的,暂时不支持使用http进行数据传输。

除了golang官方提供的rpc库,还有许多第三方库为在golang中实现RPC提供支持,大部分第三方rpc库的实现都是使用protobuf进行数据编解码,根据protobuf声明文件自动生成rpc方法定义与服务注册代码,在golang中可以很方便的进行rpc服务调用。

net/rpc库

使用net/rpc库实现乘除法的rpc

rpc服务端:
$GOPATH/src/leitty/rpc/netrpc/server/rpc_server.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package main 

import (
"fmt"
"errors"
"net/rpc"
"net/http"
"net"
"os"
)

type Arith struct {}

type ArithRequest struct {
A int
B int
}

type ArithResponse struct {
Pro int
Quo int
Rem int
}

func (this *Arith) Multiply(req ArithRequest,res *ArithResponse) error {
res.Pro = req.A * req.B
return nil
}

func (this *Arith) Divide(req ArithRequest,res *ArithResponse) error {
if req.B == 0 {
return errors.New("divide by zero")
}
res.Quo = req.A / req.B
res.Rem = req.A % req.B
return nil
}

func main() {
rpc.Register(new(Arith))
rpc.HandleHTTP()

lis, err := net.Listen("tcp","127.0.0.1:8095")
if err != nil {
panic(err)
}

fmt.Fprintf(os.Stdout, "%s", "start connection")

http.Serve(lis,nil)
}

rpc客户端:

$GOPATH/src/leitty/rpc/netrpc/server/rpc_client.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import (
"fmt"
"net/rpc"
)

type ArithRequest struct {
A int
B int
}
type ArithResponse struct {
Pro int
Quo int
Rem int
}


func main() {
conn, err := rpc.DialHTTP("tcp","127.0.0.1:8095")
if err != nil {
panic(err)
}

req := ArithRequest{9,2}
var res ArithResponse

err = conn.Call("Arith.Multiply",req, &res)
if err != nil {
panic(err)
}
fmt.Printf("%d * %d = %d\n", req.A, req.B, res.Pro)

}

net/rpc/jsonrpc库

使用net/rpc/jsonrpc库实现乘除法的rpc

$GOPATH/src/leitty/rpc/jsonrpc/server/rpc_server.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package main

import (
"errors"
"fmt"
"log"
"net"
"net/rpc"
"net/rpc/jsonrpc"
"os"
)

// 算数运算结构体
type Arith struct {
}

// 算数运算请求结构体
type ArithRequest struct {
A int
B int
}

// 算数运算响应结构体
type ArithResponse struct {
Pro int // 乘积
Quo int // 商
Rem int // 余数
}

// 乘法运算方法
func (this *Arith) Multiply(req ArithRequest, res *ArithResponse) error {
res.Pro = req.A * req.B
return nil
}

// 除法运算方法
func (this *Arith) Divide(req ArithRequest, res *ArithResponse) error {
if req.B == 0 {
return errors.New("divide by zero")
}
res.Quo = req.A / req.B
res.Rem = req.A % req.B
return nil
}

func main() {
rpc.Register(new(Arith)) // 注册rpc服务

lis, err := net.Listen("tcp", "127.0.0.1:8096")
if err != nil {
log.Fatalln("fatal error: ", err)
}

fmt.Fprintf(os.Stdout, "%s", "start connection")

for {
conn, err := lis.Accept() // 接收客户端连接请求
if err != nil {
continue
}

go func(conn net.Conn) { // 并发处理客户端请求
fmt.Fprintf(os.Stdout, "%s", "new client in coming\n")
jsonrpc.ServeConn(conn)
}(conn)
}
}

$GOPATH/src/leitty/rpc/jsonrpc/server/rpc_client.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package main

import (
"fmt"
"log"
"net/rpc/jsonrpc"
)

// 算数运算请求结构体
type ArithRequest struct {
A int
B int
}

// 算数运算响应结构体
type ArithResponse struct {
Pro int // 乘积
Quo int // 商
Rem int // 余数
}

func main() {
conn, err := jsonrpc.Dial("tcp", "127.0.0.1:8096")
if err != nil {
log.Fatalln("dailing error: ", err)
}

req := ArithRequest{9, 2}
var res ArithResponse

err = conn.Call("Arith.Multiply", req, &res) // 乘法运算
if err != nil {
log.Fatalln("arith error: ", err)
}
fmt.Printf("%d * %d = %d\n", req.A, req.B, res.Pro)

err = conn.Call("Arith.Divide", req, &res)
if err != nil {
log.Fatalln("arith error: ", err)
}
fmt.Printf("%d / %d, quo is %d, rem is %d\n", req.A, req.B, res.Quo, res.Rem)
}

jsonrpc能实现跨语言的rpc通信,但是效率较低。一些第三方的rpc库都选择采用protobuf进行数据编码。

proto rpc库

使用protorpc库实现乘除法的rpc

protobuf的介绍,请移步这里

$GOPATH/src/leitty/rpc/protobuf/server/pb/arith.proto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
syntax = "proto3";
package pb;

// 算术运算请求结构
message ArithRequest {
int32 a = 1;
int32 b = 2;
}

// 算术运算响应结构
message ArithResponse {
int32 pro = 1; // 乘积
int32 quo = 2; // 商
int32 rem = 3; // 余数
}

// rpc方法
service ArithService {
rpc multiply (ArithRequest) returns (ArithResponse); // 乘法运算方法
rpc divide (ArithRequest) returns (ArithResponse); // 除法运算方法
}

$GOPATH/src/leitty/rpc/protobuf/server/rpc_server.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"errors"
"test/rpc/pb"
)

// 算术运算结构体
type Arith struct {
}

// 乘法运算方法
func (this *Arith) Multiply(req *pb.ArithRequest, res *pb.ArithResponse) error {
res.Pro = req.GetA() * req.GetB()
return nil
}

// 除法运算方法
func (this *Arith) Divide(req *pb.ArithRequest, res *pb.ArithResponse) error {
if req.GetB() == 0 {
return errors.New("divide by zero")
}
res.Quo = req.GetA() / req.GetB()
res.Rem = req.GetA() % req.GetB()
return nil
}

func main() {
pb.ListenAndServeArithService("tcp", "127.0.0.1:8097", new(Arith))
}

$GOPATH/src/leitty/rpc/protobuf/server/rpc_client.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
"fmt"
"log"
"test/rpc/pb"
)

func main() {
conn, err := pb.DialArithService("tcp", "127.0.0.1:8097")
if err != nil {
log.Fatalln("dailing error: ", err)
}
defer conn.Close()

req := &pb.ArithRequest{9, 2}

res, err := conn.Multiply(req)
if err != nil {
log.Fatalln("arith error: ", err)
}
fmt.Printf("%d * %d = %d\n", req.GetA(), req.GetB(), res.GetPro())

res, err = conn.Divide(req)
if err != nil {
log.Fatalln("arith error ", err)
}
fmt.Printf("%d / %d, quo is %d, rem is %d\n", req.A, req.B, res.Quo, res.Rem)
}

参考:

https://studygolang.com/articles/14336