利用 json 和 RPC 可以很方便的进行远程数据交换和程序调用,本文根据自身的经历对 Go 中使用 jsonrpc 库进行远程调用进行总结。
json-rpc
json-rpc 就是使用 json 的数据格式来进行 RPC 的调用,远程连接可以使用 TCP 或者 HTTP,简单易用。
请求数据结构:
1 2 3 4 5
| { "method": "getName", "params": ["1"], "id": 1 }
|
返回数据结构:
1 2 3 4 5 6
| {
"result": {"id": 1, "name": "name1"}, "error": null, "id": 1 }
|
result: 远程方法返回值
error: 错误信息
id: 调用时所传来的 id
Go 中的 json-rpc 使用
在 Go 中使用 json-rpc 非常简单,只需要几个常见的包就可以了:
net/rpc 包实现了最基本的 rpc 调用,它默认通过 HTTP 协议传输 gob 数据来实现远程调用。
服务端实现了一个 HTTP server,接收客户端的请求,在收到调用请求后,会反序列化客户端传来的 gob 数据,获取要调用的方法名,并通过反射来调用我们自己实现的处理方法,这个处理方法传入固定的两个参数,并返回一个 error 对象,参数分别为客户端的请求内容以及要返回给客户端的数据体的指针。
net/rpc/jsonrpc 包实现了 json-rpc 协议,即实现了 net/rpc 包的 ClientCodec 接口与 ServerCodec,增加了对 json 数据的序列化与反序列化来取代 gob 格式的数据,使得调用更加具有通用性,可以使用 Python 等利用 http 请求,直接发送 json 字符串来调用服务器上的应用程序。
Go 中的 json-rpc 示例
定义用于传输的数据结构
客户端与服务端双方传输数据,其中数据结构必须得让双方都能处理。首先定义 rpc 所传输的数据的结构,client 端与 server 端都得用到。
1 2 3 4 5 6 7 8 9 10 11 12
| type RpcObj struct { Id int `json:"id"` Name string `json:"name"` }
type ReplyObj struct { Ok bool `json:"ok"` Id int `json:"id"` Msg string `json:"msg"` }
|
定义服务端的处理器及其处理方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| type ServerHandler struct{}
func (sh *ServerHandler) GetName(id int, returnObj *RpcObj) error { log.Println("server\t-", "Receive GetName call, id:", id) returnObj.Id = id returnObj.Name = "peic" return nil }
func (sh *ServerHandler) SaveName(rpcObj RpcObj, returnObj *ReplyObj) error { log.Println("server\t-", "Receive SaveName call, RpcObj:", rpcObj) returnObj.Ok = true returnObj.Id = rpcObj.Id returnObj.Msg = "Save successfully" return nil }
|
ServerHandler 结构可以不需要什么字段,只需要有符合 net/rpcserver 端处理器约定的方法即可。
符合约定的方法必须具备两个参数和一个 error 类型的返回值
第一个参数 为 client 端调用 rpc 时交给服务器的数据,可以是指针也可以是实体。net/rpc/jsonrpc 的 json 处理器会将客户端传递的 json 数据解析为正确的 struct 对象。
第二个参数 为 server 端返回给 client 端的数据,必须为指针类型。net/rpc/jsonrpc 的 json 处理器会将这个对象正确序列化为 json 字符串,最终返回给 client 端。
ServerHandler 结构需要注册给 net/rpc 的 HTTP 处理器,HTTP 处理器绑定后,会通过反射得到其暴露的方法,在处理请求时,根据 json-rpc 协议中的 method 字段动态的调用其指定的方法。
设置开启服务器上的监听服务并处理相应调用请求
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
| func startServer() { server := rpc.NewServer()
listener, err := net.Listen("tcp", ":6666") if err != nil { log.Fatal("server\t-", "Listen error:", err.Error()) } defer listener.Close()
log.Println("server\t-", "Start listen on port 6666")
serverHandler := new(ServerHandler) server.Register(serverHandler)
for { conn, err := listener.Accept() if err != nil { log.Fatal("server\t-", "Accept error:", err.Error()) }
go server.ServeCodec(jsonrpc.NewServerCodec(conn)) } }
|
客户端调用请求
客户端可以采用同步或者异步的方法来进行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
| func callRpcBySynchronous() {
client, err := net.DialTimeout("tcp", "localhost:6666", 1000*1000*1000*10) if err != nil { log.Fatal("client\t-", err.Error()) } defer client.Close()
rpcClient := jsonrpc.NewClient(client)
var response1 RpcObj request1 := 1 log.Println("client\t-", "Call GetName method") rpcClient.Call("ServerHandler.GetName", request1, &response1) log.Println("client\t-", "Receive remote return:", response1)
var response2 ReplyObj request2 := RpcObj{2, "Synchronous"} log.Println("client\t-", "Call Save method") rpcClient.Call("ServerHandler.SaveName", request2, &response2) log.Println("client\t-", "Receive remote return:", response2) }
|
异步调用:
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
| func callRpcByAsynchronous() { client, err := net.DialTimeout("tcp", "localhost:6666", 1000*1000*1000*10) if err != nil { log.Fatal("client\t-", err.Error()) } defer client.Close()
rpcClient := jsonrpc.NewClient(client)
requestNum := 150 endChan := make(chan int, requestNum)
for i := 1; i <= requestNum; i++ { request := RpcObj{i, "Asynchronous"} log.Println("client\t-", "Call SaveName method")
divCall := rpcClient.Go("ServerHandler.SaveName", request, &ReplyObj{}, nil)
go func(num int) { reply := divCall.Done tmp := <-reply log.Println("client\t-", "Receive remote return by Asychronous", tmp.Reply) endChan <- num }(i) }
for i := 1; i <= requestNum; i++ { _ = <-endChan }
log.Println("Exit...") }
|
综合示例:
将上述代码放置在 go 文件中,运行即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package main
import ( "log" "net" "net/rpc" "net/rpc/jsonrpc" )
.......
func main() { go startServer() callRpcByAsynchronous() }
|
异步调用的结果如下:
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
| 2017/07/03 11:55:00 server - Start listen on port 6666 2017/07/03 11:55:00 client - Call SaveName method 2017/07/03 11:55:00 client - Call SaveName method 2017/07/03 11:55:00 client - Call SaveName method 2017/07/03 11:55:00 client - Call SaveName method 2017/07/03 11:55:00 client - Call SaveName method 2017/07/03 11:55:00 client - Call SaveName method 2017/07/03 11:55:00 client - Call SaveName method 2017/07/03 11:55:00 client - Call SaveName method 2017/07/03 11:55:00 client - Call SaveName method 2017/07/03 11:55:00 client - Call SaveName method 2017/07/03 11:55:00 server - Receive SaveName call, RpcObj: {1 Asynchronous} 2017/07/03 11:55:00 server - Receive SaveName call, RpcObj: {6 Asynchronous} 2017/07/03 11:55:00 server - Receive SaveName call, RpcObj: {4 Asynchronous} 2017/07/03 11:55:00 server - Receive SaveName call, RpcObj: {5 Asynchronous} 2017/07/03 11:55:00 server - Receive SaveName call, RpcObj: {10 Asynchronous} 2017/07/03 11:55:00 server - Receive SaveName call, RpcObj: {8 Asynchronous} 2017/07/03 11:55:00 server - Receive SaveName call, RpcObj: {7 Asynchronous} 2017/07/03 11:55:00 server - Receive SaveName call, RpcObj: {9 Asynchronous} 2017/07/03 11:55:00 server - Receive SaveName call, RpcObj: {2 Asynchronous} 2017/07/03 11:55:00 server - Receive SaveName call, RpcObj: {3 Asynchronous} 2017/07/03 11:55:00 client - Receive remote return by Asychronous &{true 1 Save successfully} 2017/07/03 11:55:00 client - Receive remote return by Asychronous &{true 4 Save successfully} 2017/07/03 11:55:00 client - Receive remote return by Asychronous &{true 10 Save successfully} 2017/07/03 11:55:00 client - Receive remote return by Asychronous &{true 5 Save successfully} 2017/07/03 11:55:00 client - Receive remote return by Asychronous &{true 2 Save successfully} 2017/07/03 11:55:00 client - Receive remote return by Asychronous &{true 8 Save successfully} 2017/07/03 11:55:00 client - Receive remote return by Asychronous &{true 7 Save successfully} 2017/07/03 11:55:00 client - Receive remote return by Asychronous &{true 3 Save successfully} 2017/07/03 11:55:00 client - Receive remote return by Asychronous &{true 9 Save successfully} 2017/07/03 11:55:00 client - Receive remote return by Asychronous &{true 6 Save successfully} 2017/07/03 11:55:00 Exit...
|
Go 中 json-rpc 的 Server 和 Client 处理示意图