- 实现一个支持异步和并发的高性能客户端
- 一次RPC调用需要哪些信息
- 如何支持异步调用
一、 客户端
1. client.go
- 对于一个客户端来说,接收响应、发送请求是最重要的2个功能
- 创建连接、发出请求、接收响应
- 封装结构体Call,承载一次RPC调用所需的信息
type Call struct{ Seq uint64 ServiceMethod string Args interface{} Reply interface{} Error error Done chan *Call // 用于支持异步调用 } func (call *Call) done(){ call.Done <- call }
- RPC客户端最核心的部分 Client
type Client struct{ cc codec.Codec // 消息的编解码器,用来序列化将要发送出去的请求,以及反序列化收到的响应 opt *Option sending sync.Mutex // 一个互斥锁,为了保证请求的有序发送,即防止出现多个请求报文混淆 header codec.Header // 消息头,只有在请求发送时才需要,而请求发送是互斥的,因此每个客户端只需要一个,声明在Client结构体中可以复用 mu sync.Mutex seq uint64 // 用于给发送的请求编号,每个请求拥有唯一编号 pending map[uint64]*Call // 存储未处理完的请求,键是编号,值是Call实例 closing bool // 表示Client处于不可用状态。用户主动关闭的 shutdown bool // 表示Client处于不可用状态。一般是有错误发生 } var _ io.Closer = (*Client)(nil) var ErrShutdown = errors.New("connection is shut down") func (client *Client) Close() error{ client.mu.Lock() defer client.mu.Unclock() if client.closing{ return ErrShutdown } client.closing = true return client.cc.Close() } func (client *Client) IsAvailable() bool{ client.mu.Lock() defer client.mu.Unlock() return &client.shutdown && !client.closing } // 与call相关的三个方法 // 将参数call添加到client.pending中,并更新client.seq func (client *Client) registerCall(call *Call)(uint64, error){} // 根据seq,从client.pending中移除对应的call并返回 func (client *Client) removeCall(call *Call)(uint64, error){} // 服务端或客户端错误时调用,将shutdown设置为true,并将错误信息通知所有pending状态的call func (client *Client) terminateCalls(call *Call)(uint64, error){}
二、 待分析思考
参考
- https://geektutu.com/post/geerpc-day2.html