JSON-RPC是一个无状态且轻量级的远程过程调用传送协议,传输内容使用JSON编解码。
Golang官方提供了net/rpc/jsonrpc
库实现了JSON-RPC,net/rpc/jsonrpc
包实现了JSON-RPC协议,实现了net/rpc
包的ClientCodec
和ServerCodec
接口,增加了对JSON数据的序列化和反序列化。
-
jsonrpc
采用JSON对数据编解码,因而支持跨语言调用。 -
jsonrpc
库基于TCP实现,暂不支持HTTP进行数据传输。
import "net/rpc"
import "net/rpc/jsonrpc"
-
net/rpc
实现了Golang的RPC框架,net/rpc/jsonrpc
则具体实现了JSON-RPC协议,因此具有JSON数据的序列化和反序列化能力。
公共参数
- 客户端和服务端双方传递数据,数据结构必须让双方都能够处理。
请求参数 - 传输的参数数据
//参数
type Args struct{
UserId int `json:"userid"`
}
返回值 - 服务端返回的数据
//返回
type Reply struct{
Code int `json:"code"`
Message string `json:"message"`
Data string `json:"data"`
}
服务定义
- 服务定义时入参需遵循
jsonrpc
的定义规范,返回值需以指针方式传入。 - 符合约定的方法必须具备两个参数和一个
error
类型的返回值。 - 第一个参数为客户端调用RPC时交给服务端的数据,可以是指针也可以是实体。
- 第二个参数为服务端返回给客户端的数据,必须为指针类型。
//服务
type UserService struct{}
//RPC
func (s *UserService) Get(args Args, reply *Reply) error{
userId := args.UserId
reply.Code = 1
reply.Message = "success"
reply.Data = fmt.Sprintf("userid = %d", userId)
return nil
}
服务端
- 注册和启动服务,监听端口接受请求。
func serve(){
//创建服务
rcvr := new(UserService)
//注册服务
err := rpc.Register(rcvr)
if err!=nil{
log.Fatal(err)
}
//监听端口
listener,err := net.Listen("tcp", "127.0.0.1:7002")
if err!=nil{
log.Fatal(err)
}
defer listener.Close()
log.Println("jsonrpc server 127.0.0.1:7002 start...")
//获取连接
for{
//等待客户端Socket
conn,err := listener.Accept()
if err!=nil{
log.Println(err)
continue
}
//并发处理连接
go func(conn net.Conn){
log.Println("client in coming")
jsonrpc.ServeConn(conn)
}(conn)
}
}
jsonrpc.ServeConn
-
ServeConn
函数用于在单个连接上执行DefaultServer
. -
ServeConn
会发生阻塞,直到服务端挂起。 - 调用者应另开
goroutine
调用ServeConn
方法 -
jsonrpc.ServeConn(conn)
在指定conn
连接上使用JSON格式进行编解码
func ServeConn(conn io.ReadWriteCloser) {
rpc.ServeCodec(NewServerCodec(conn))
}
注意jsonrpc.ServeConn(conn)
实际上等价于
// 创建服务端编解码器
codec := jsonrpc.NewServerCodec(conn)
// 编解码处理
rpc.ServeCodec(codec)
客户端
客户端必须确保服务端在传输数据中所使用的结构体。首先,可通过net
包使用TCP协议连接至服务器,同时可设置超时时间。然后通过之前的TCP连接建立客户端实例。
func call(){
//连接服务端
client,err := jsonrpc.Dial("tcp", "127.0.0.1:7002")
if err!=nil {
log.Fatalln(err)
}
//发起远程调用
var reply Reply
args := &Args{UserId:1}
err = client.Call("UserService.Get", args, &reply)
if err!=nil{
log.Fatalln(err)
}
log.Printf("code:%d, message:%s, data:%s\n", reply.Code, reply.Message, reply.Data)
}
jsonrpc.Dial
- Dial函数通过指定的网络和地址连接JSON-RPC服务端,返回RPC客户端实例。
func Dial(network, address string) (*rpc.Client, error) {
conn, err := net.Dial(network, address)
if err != nil {
return nil, err
}
return NewClient(conn), err
}
jsonrpc.NewClient
- NewClient返回一个全新的
rpc.Client
用来管理连接对端服务的请求
func NewClient(conn io.ReadWriteCloser) *rpc.Client {
return rpc.NewClientWithCodec(NewClientCodec(conn))
}
例如:
conn,err := net.Dial("tcp", "127.0.0.1:7002")
if err!=nil{
log.Fatalln(err)
}
defer conn.Close()
client := jsonrpc.NewClient(conn)
请求响应
jsonrpc
传输的是单一的对象,序列化为JSON格式。
jsonrpc
请求对象会包含三个属性
属性 | 描述 |
---|---|
method | 请求调用的方法 |
params | 数组,表示传给方法的参数数组。 |
id | 请求ID,任意类型,接受响应后据此判断对应的请求。 |
jsonrpc
响应对象包含三个属性
属性 | 描述 |
---|---|
result | 方法返回的对象,若error 非空则为null 。 |
error | 调用是否出错 |
id | 对应请求的ID |
另外还定义了一种通知类型,除了id
属性为null
外,通知对象的属性与请求对象完全一样。
同步异步
对于net/rpc
客户端,在远程调用时具有同步(Synchronous)和异步(Asynchronous)两种方式,不论哪种方式,源码中,请求总是在一个新的goroutine
中执行,并使用一个通道(chan)来存放服务端返回值。
- 使用同步方式调用,调用方法内部会等待通道的数据,并一致阻塞到远程服务器返回。
- 使用异步方式调用,客户端调用方法会直接将通道返回,这样可适时的处理数据而不会影响到当前的
goroutine
。
client.Call
- 同步调用
func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error {
call := <-client.Go(serviceMethod, args, reply, make(chan *Call, 1)).Done
return call.Error
}
client.Go
- 异步调用
func (client *Client) Go(serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call {
call := new(Call)
call.ServiceMethod = serviceMethod
call.Args = args
call.Reply = reply
if done == nil {
done = make(chan *Call, 10) // buffered.
} else {
// If caller passes done != nil, it must arrange that
// done has enough buffer for the number of simultaneous
// RPCs that will be using that channel. If the channel
// is totally unbuffered, it's best not to run at all.
if cap(done) == 0 {
log.Panic("rpc: done channel is unbuffered")
}
}
call.Done = done
client.send(call)
return call
}
jsonrpc.Call
type Call struct {
ServiceMethod string // The name of the service and method to call.
Args interface{} // The argument to the function (*struct).
Reply interface{} // The reply from the function (*struct).
Error error // After completion, the error status.
Done chan *Call // Receives *Call when Go is complete.
}
有疑问加站长微信联系(非本文作者)