Go jsonrpc

JunChow520 · · 1308 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

JSON-RPC是一个无状态且轻量级的远程过程调用传送协议,传输内容使用JSON编解码。

JSON-RPC

Golang官方提供了net/rpc/jsonrpc库实现了JSON-RPC,net/rpc/jsonrpc包实现了JSON-RPC协议,实现了net/rpc包的ClientCodecServerCodec接口,增加了对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.
}

有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:JunChow520

查看原文:Go jsonrpc

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1308 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传