JSON-RPC,是一个无状态且轻量级的远程过程调用传送协议,其传递内容主要以JSON数据为主,相较于一般的 RESTFul 通过 URL 地址,如 GET /student 调用远程服务器,JSON-RPC直接在内容中定义了想要调用的方法名称如,{“id”: 123456, “method”: “Service.Division”, “params”: [{“A”: 88, “B”: 6}]},这也令开发者不会陷于该使用 PUT 还是 POST
的问题中。在RPC服务定义中主要定义一些数据结构及其相关的处理规则。在Golang中所有注册的RPC服务方法需要满足三个条件,第一方法有一个输入参数,第二方法有一个指针类型的输出参数,第三方法返回一个error类型的返回值,满足这三个条件即可注册为RPC服务方法。在Golang中如何使用请看以下内容…
示例项目目录结构
1
2
3
4
5
6
7
8
9
10
11
12
|
$ tree
.
├── README.md
├── client
│ └── demo
│ └── demo.go
├── server
│ └── demo
│ └── demo.go
└── services
└── demo
└── demo.go
|
定义RPC服务方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// services/demo/demo.go
package demo
import "errors"
// 定义RPC服务参数类型
type Args struct {
A, B int
}
// 定义RPC服务
type Service struct {}
// Division 计算args参数之除法运算
func (*Service) Division(args Args, result *float64) error {
if args.B == 0 {
return errors.New("division by zero")
}
*result = float64(args.A) / float64(args.B)
return nil
}
|
上面定义了一个名为 Service
的服务,注意这是在 services/demo
包中进行的定义的,这个服务有一个方法 Division
,这个方法就是进行简单的业务处理也就是进行除法运算,并把运算结果写入到 result
变量内。
注册RPC服务
编写RPC服务端程序如下:
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
|
// server/demo/demo.go
package main
import (
"GO-JSON-RPC/services/demo"
"log"
"net"
"net/rpc"
"net/rpc/jsonrpc"
)
func main() {
// Errors recover
defer func() {
if e := recover(); e != nil {
log.Printf("demo JSON RPC server has errors: %s\n", e)
}
}()
// 服务端注册RPC服务
err := rpc.Register(new(demo.Service))
if err != nil {
panic(err)
}
// 监听服务端口
listener, err := net.Listen("tcp", ":8859")
if err != nil {
panic(err)
}
// 接受连接并处理服务调用
for {
accept, err := listener.Accept()
if err != nil {
log.Printf("accpet has errors: %s\n", err)
continue
}
go jsonrpc.ServeConn(accept)
}
}
|
在这段程序中主要做了以下三件事情,第一就是注册RPC服务,第二就是通过 tcp 方式监听服务端口,以供外部调用之网络端口(说白了就是使用一个tcp网络协议8859端口提供RPC服务),第三就是通过Goroutine的方式等待客户端连接到JSON-RPC服务。
那么这时一个简单的JSON-RPC服务也构建完毕了,服务构建完成之后那就需要使用,一个不使用的服务存在是没有意义的,我们可以通过telnet工具对其RPC服务端程序进行验证,也可以通过在其它项目中对RPC服务进行调用,为了简单起见,将客户端程序定义到 client/demo/demo.go 中,请接着向下看~
调用RPC服务
调用RPC服务首先要将RPC服务程序运行,可以通过以下命令将其运行:
1
|
$ go run server/server.go
|
使用telnet工具进行RPC服务调用(也就是手动调用模式)
打开另一终端,输入telnet命令对RPC服务进行调用
1
2
3
4
|
$ telnet 127.0.0.1 8859
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
|
输入以JSON格式的参数数据,进行JSON-RPC服务调用:
1
|
{"id": 123456, "method": "Service.Division", "params": [{"A": 88, "B": 6}]}
|
关于JSON格式的参数数据说明如下:
id
: 字段为调用编号,调用结束后服务端会原样返回(可供调用者根据此ID进行其它的业务处理)。
method
: 字段为要调用的RPC服务方法。
params
: 字段为调用RPC服务方法需要传递的参数。
如果调用成功返回JSON格式的结果如下所示:
1
|
{"id":123456,"result":14.666666666666666,"error":null}
|
使用RPC客户端进行RPC服务调用(也就是自动调用模式)
对于RPC服务也不可能只是通过telnet进行简单的调用,更多的是面向其它的服务,举个例了,比如说在一个大型电商项目中,有很多服务如 订单
,派送
,短信
等等吧,这些服务的业务都是相对独立的,如用户下单是不是要生成订单,发送短信等等事件,那再一步说这些事件它肯定也不是同步的一个一个去完成,一般都会把下单之后的事件操作放入到消息中间件去异步处理,消息中间件再处理各个场景事件时再去调用其它的服务,这个时候就需要用到RPC调用,因为各个服务相对独立(如:不同地域服务,不同主机或端口号,不同服务器实例或Docker容器),在这个示例中以 client/demo/demo.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
|
package main
import (
"GO-JSON-RPC/services/demo"
"log"
"net"
"net/rpc/jsonrpc"
)
func main() {
// Errors recover
defer func() {
if e := recover(); e != nil {
log.Printf("demo JSON RPC client has errors: %s\n", e)
}
}()
// 连接到远程RPC服务
conn, err := net.Dial("tcp", ":8859")
if err != nil {
panic(err)
}
// 声明JSON-RPC调用回复结果变量
var reply float64
// 创建JSON-RPC客户端
client := jsonrpc.NewClient(conn)
// 调用RPC服务方法
err = client.Call("Service.Division", demo.Args{A: 88, B: 6}, &reply)
if err != nil {
panic(err)
}
log.Printf("call result: %f\n", reply)
}
|
运行客户端程序模拟其它项目对JSON-RPC服务调用:
1
|
$ go run client/demo/demo.go
|
调用结果如下所示:
1
|
2020/04/06 18:50:47 JSON-RPC call result: 14.666667
|
这便是Golang中JSON-RPC如何使用的简单示例,当然你也可以使用gRPC框架进行RPC服务开发~
示例代码
https://github.com/helloshaohua/Go-JSON-RPC