[go 语言学习笔记] 7天用Go从零实现GeeRPC 「持续更新中」

说明

本文是学习 go 语言过程中的思考笔记, 文中涉及的代码都是在文本编辑器中敲出来的 伪代码, 并不能直接运行, 如有需要可以参考原教程.

本文是对原教程的思考与总结, 原教程链接可以访问7天用Go从零实现GeeRpc

原教程 和 本文适合对 RPC 的概念有过深入了解的读者.

由于本人有过 Java 的使用经验, 很多思考内容都会结合 Java 相关知识来表达.

第一天 服务端与消息编码

一、RPC 的核心

说白了, RPC 的本质就是两个 不同的进程 之间进行信息的交换, 两个进程有可能分布在 不同的机器 上, 在这种情况下, 进程之间要想进行信息交流, 通常要基于 网络通信.

对于我们程序员来讲, 网络编程最简单的方式是基于 HTTP, 我们可以利用 现有的 web 框架 很轻松的完成 json 格式消息, xml 格式消息的传输工作.

其次是 tcp, 它的缺点是我们需要自定义双方的信息协议,优点是传输效率高(直接这样讲可能不太严谨, 关于 http 和 rpc 的比较可以自行了解.).

关于协议, 我们引用一下 Dubbo 官方 的一段说明:

协议是 RPC 的核心,它规范了数据在网络中的传输内容和格式。除必须的请求、响应数据外,通常还会包含额外控制数据,如单次请求的序列化方式、超时时间、压缩方式和鉴权信息等。

协议的内容包含三部分

  • 数据交换格式: 定义 RPC 的请求和响应对象在网络传输中的字节流内容,也叫作序列化方式
  • 协议结构: 定义包含字段列表和各字段语义以及不同字段的排列方式
  • 协议通过定义规则、格式和语义来约定数据如何在网络间传输。一次成功的 RPC 需要通信的两端都能够按照协议约定进行网络字节流的读写和对象转换。如果两端对使用的协议不能达成一致,就会出现鸡同鸭讲,无法满足远程通信的需求。

二、消息格式的约定

消息长度

如果是基于 TCP 通信, 通信双方就要约定好协议格式, 比如客户端需要告诉服务端, 我发送的消息长度是多少, 不然服务端很可能拿到就是一个不完整的消息.

就拿 HTTP 来说, 我们需要在请求头放置一个 Content-Length 的请求头, 或者 Transfer-Encoding 的请求头, 这样服务端才能确保拿到的信息是完整准确的.

具体实现原理我们不深究, 感兴趣可以参考以下两篇文章:

超文本传输协议(HTTP/1.1):报文格式与路由

HTTP 协议中的 Transfer-Encoding

消息类型

我们继续拿 HTTP 举例, 如果服务端拿到消息长度, 就能拿到消息内容, 但是此时的消息内容还是字节数组类型, 我们需要把内容转成对应的 json 或者 xml 等对象(也就是反序列化过程). 所以我们还需要客户端在 请求头 的描述中告诉服务端消息内容转换的格式, 比如 服务端接收到 Content-Type: application/json这个 HTTP 请求头, 就会把字节数组转化为 Json 对象.

三、服务端代码的具体实现

作为 服务端, 至少要负责以下几件事情:

  1. 创建并启动服务
  2. 监听并接收客户端连接
  3. 处理客户端请求
    1. 解码客户端请求信息
    2. 反射调用本地方法
    3. 对返回给客户端的信息进行编码, 并响应

我们按照上面的指责, 定义相应的结构体和方法.

1. 创建并启动服务
// 定义一个服务类型
type struct Server {}

// 创建 Server 
func NewServer() (s *Server) {
  return &Server{}
}
2. 监听并接收客户端连接

服务端启动后, 就会开启一个端口, 一直监听 来自客户端的连接请求, 处理客户端请求需要异步进行, 如果是同步处理, 会造成大量的请求阻塞在那里.

func (s *Server) Accept(lis net.Listener) {
  for {
    // 一直监听目标端口
    conn, error := lis.Accept()
    // 拿到客户端的连接, 就应该异步去处理.
    go s.serveConnection(conn)
  }
}
3. 处理客户端请求
func (s *Server) serveConnection(conn io.ReadWriteCloser) {
  // decode serveCodec
  
}
3.1 解析请求信息

服务端接收到客户端请求信息, 首先需要解码, 解码工作需要交给 解码器 去处理, 根据我们前面讲的, 信息的格式可以是多样的.所以我们的编解码器抽象成一个接口, 如果是 json 格式的信息, 就需要定义一个 json 类型的解码器.

根据我们前面所讲, 解析请求信息时, 需要分别从 请求头请求体 读取相应的内容, 所以需要在接口中定义一个 读取请求头读取请求体 的方法.

type Codec interface {
	ReadHeader(*Header) error
	ReadBody(interface{}) error
}
func (s *Server) serveCodec(cc *Codec) {
  // 解析请求
}
3.2 调用服务端方法

上面解析工作完成后, 已经可以拿到可读的请求信息了, 即: 接口名称方法名称, 方法参数 等, 接下来服务端需要做的就是根据这些信息找到本地的方法去执行.

这个过程我们先省略.

3.3 返回执行结果

对于方法的执行结果, 我们同样需要 编码, 然后响应给调用方.

既然涉及到编码, 我们既要在上面的编码器中, 新增一个编码的方法

type Codec interface {
	ReadHeader(*Header) error
	ReadBody(interface{}) error
  // 新增编码
  Write(*Header, interface{}) error
}

四、启动服务(测试)

func main() {
  // 1. 创建 Server 对象
  s := NewServer()
  // 2. 开启端口监听
  l := net.Listen("tcp", ":0")
  // 3. 接收
  s.Accept(l)
}

第二天 高性能客户端

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值