前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go 语言网络编程系列(八)—— RPC 编程篇:使用 JSON 对传输数据进行编解码

Go 语言网络编程系列(八)—— RPC 编程篇:使用 JSON 对传输数据进行编解码

作者头像
学院君
发布2019-11-21 10:49:43
1.5K0
发布2019-11-21 10:49:43
举报
文章被收录于专栏:学院君的专栏学院君的专栏

一、自定义编解码接口实现原理

上篇教程我们介绍了 Go 语言内置的数据序列化工具 —— Gob,但是 Gob 只能在 Go 语言内部使用,不支持跨语言 RPC 调用,如果要实现这一功能,就需要对 RPC 接口的编解码实现进行自定义。

Go 的 net/rpc 实现很灵活,它在数据传输前后实现了编码解码器的接口定义,这意味着,开发者可以自定义数据的传输方式以及 RPC 服务端和客户端之间的交互行为。PRC 客户端和服务端提供的编码解码器接口如下:

代码语言:javascript
复制
type ClientCodec interface { 
    WriteRequest(*Request, interface{}) error
    ReadResponseHeader(*Response) error 
    ReadResponseBody(interface{}) error
    Close() error
}

type ServerCodec interface {
    ReadRequestHeader(*Request) error 
    ReadRequestBody(interface{}) error 
    WriteResponse(*Response, interface{}) error
    Close() error
}
代码语言:javascript
复制

接口 ClientCodec 定义了 RPC 客户端如何在一个 RPC 会话中发送请求和读取响应。客户端程序通过 WriteRequest() 方法将一个请求写入到 RPC 连接中,并通过 ReadResponseHeader()ReadResponseBody() 读取服务端的响应信息。当整个过程执行完毕后,再通过 Close() 方法来关闭该连接。

接口 ServerCodec 定义了 RPC 服务端如何在一个 RPC 会话中接收请求并发送响应。服务端程序通过 ReadRequestHeader()ReadRequestBody() 方法从一个 RPC 连接中读取请求信息,然后再通过 WriteResponse() 方法向该连接中的 RPC 客户端发送响应。当完成该过程后,通过 Close() 方法来关闭连接。

通过实现上述接口,我们可以自定义数据传输前后的编码解码方式,而不仅仅局限于 Gob。实际上,Go 标准库提供的 net/rpc/jsonrpc 包,就是一套实现了 rpc.ClientCodecrpc.ServerCodec 接口的 JSON-RPC 模块。

二、基于 jsonrpc 包对传输数据进行编解码

接下来,我们就来演示如何基于内置 jsonrpc 包通过 JSON 对 RPC 传输数据进行编解码。

1、参数定义

我们创建一个 utils.go 来定义请求和响应类,以便在 RPC 客户端和服务端中使用:

代码语言:javascript
复制
代码语言:javascript
复制
package main

type Item struct {
  Id   int `json:"id"`      Name string `json:"name"`
}

type Response struct {
  Ok  bool `json:"ok"`
  Id  int `json:"id"`
  Message string `json:"msg"`
}

这里我们对参数字段进行额外的描述,这样,jsonrpc 包会在序列化 JSON 时,将该聚合字段命名为指定的字符串。

2、PRC 服务端

首先我们创建一个 server.go 来定义 RPC 服务端代码:

代码语言:javascript
复制
package main

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

// 服务端的rpc处理器
type ServiceHandler struct {}

func (serviceHandler *ServiceHandler) GetName(id int, item *Item) error {
  log.Printf("receive GetName call, id: %d", id)
  item.Id = id
  item.Name = "学院君"
  return nil
}

func (serviceHandler *ServiceHandler) SaveName(item Item, resp *Response) error {
  log.Printf("receive SaveName call, Item: %v", item)
  resp.Ok = true
  resp.Id = item.Id
  resp.Message = "存储成功"
  return nil
}

func main()  {
  // 初始化 RPC 服务端
  server := rpc.NewServer()

  // 监听端口 8080
  listener, err := net.Listen("tcp", ":8080")
  if err != nil {
    log.Fatalf("监听端口失败:%v", err)
  }
  defer listener.Close()

  log.Println("Start listen on port localhost:8080")

  // 初始化服务处理器
  serviceHandler := &ServiceHandler{}
  // 注册处理器
  err = server.Register(serviceHandler)
  if err != nil {
    log.Fatalf("注册服务处理器失败:%v", err)
  }

  // 等待并处理客户端连接
  for {
    conn, err := listener.Accept()
    if err != nil {
      log.Fatalf("接收客户端连接请求失败: %v", err)
    }

    // 自定义 RPC 编码器:新建一个 jsonrpc 编码器,并将该编码器绑定给 RPC 服务端处理器
    go server.ServeCodec(jsonrpc.NewServerCodec(conn))
  }
}

注意到,这里我们修改了服务端启动逻辑,重点关注这段代码:

代码语言:javascript
复制
go server.ServeCodec(jsonrpc.NewServerCodec(conn))

这里通过协程启动 RPC 服务端,并且每次拿到新的客户端连接 conn 后,通过 jsonrpc.NewServerCodec(conn) 对其进行封装,以便在处理接收到的请求数据和发送响应数据时通过 JSON 对数据进行编码和解码,然后将这个编解码器通过 server.ServeCodec 分配给 RPC 服务端,从而完成对数据编解码工具的自定义。

上上篇教程中,默认是通过 server.serveConn 方法启动的 RPC 服务端:

代码语言:javascript
复制
代码语言:javascript
复制
func (server *Server) ServeConn(conn io.ReadWriteCloser) {
  buf := bufio.NewWriter(conn)
  srv := &gobServerCodec{
    rwc:    conn,
    dec:    gob.NewDecoder(conn),
    enc:    gob.NewEncoder(buf),
    encBuf: buf,
  }
  server.ServeCodec(srv)
}

可以看到,这里没有指定编解码器,使用的是默认的 Gob 对数据进行编解码。

3、RPC 客户端

接下来我们创建一个 client.go 来定义客户端调用服务端代码的逻辑:

代码语言:javascript
复制
package main

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

func main() {
  conn, err := net.DialTimeout("tcp", "localhost:8080", 30 * time.Second) // 30秒超时时间
  if err != nil {
    log.Fatalf("客户端发起连接失败:%v", err)
  }
  defer conn.Close()

  client := jsonrpc.NewClient(conn)
  var item Item
  client.Call("ServiceHandler.GetName", 1, &item)
  log.Printf("ServiceHandler.GetName 返回结果:%v\n", item)

  var resp Response
  item = Item{2, "学院君2"}
  client.Call("ServiceHandler.SaveName", item, &resp)
  log.Printf("ServiceHandler.SaveName 返回结果:%v\n", resp)
}
代码语言:javascript
复制

这里我们以同步方式发起客户端请求,和服务端类似,我们通过 jsonrpc.NewClient(conn) 封装连接实例,这样,在发起请求和接收响应时,底层就会通过 JSON 格式对数据进行编码和解码。

4、测试 JSON-RPC 调用

最后我们来简单测试下,JSON-RPC 的调用,先打开一个终端窗口,启动 RPC 服务端:

代码语言:javascript
复制
go run server.go utils.go
代码语言:javascript
复制

然后新开一个终端窗口,运行客户端调用代码:

返回响应数据符合我们的预期,服务端也会打印客户端请求数据:

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 极客书房 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、自定义编解码接口实现原理
  • 二、基于 jsonrpc 包对传输数据进行编解码
  • 1、参数定义
    • 2、PRC 服务端
      • 3、RPC 客户端
        • 4、测试 JSON-RPC 调用
        相关产品与服务
        文件存储
        文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档