rpc-远程过程调用 : 是一种计算机通信协议,允许调用不同进程空间的程序。
http的Restful与rpc协议相比的缺点:
- Restful 接口需要额外的定义,无论是客户端还是服务端,都需要额外的代码来处理,而 RPC 调用则更接近于直接调用。
- 基于 HTTP 协议的 Restful 报文冗余,承载了过多的无效信息,而 RPC 通常使用自定义的协议格式,减少冗余报文。
- RPC 可以采用更高效的序列化协议,将文本转为二进制传输,获得更高的性能。
- 因为 RPC 的灵活性,所以更容易扩展和集成诸如注册中心、负载均衡等功能
一个rpc框架需要解决的问题:
- 传输协议
- 报文编码 3.可用性问题:连接超时、是否支持异步和并发 4.注册中心、负载均衡
消息的序列化与反序列化
典型的rpc调用:
err = client.Call("Arith.Multiply", args, &reply)
其中Arith.Multiply和args为请求的参数,reply和err为服务器的响应。可以把请求的参数和响应的回复抽象为body,剩余的抽象为请求头。这样就抽象出来了一个消息体。请求头的数据接口可以这样定义。
//请求头
type Header struct {
ServiceMethod string //服务名和方法名
Seq uint64 //请求序列号或请求id
Error string //错误信息
}
这时还需要实现对消息体进行编解码的方法。此时考虑定义一个接口,因为可以采取不同的方式对消息体进行编解码。
//对消息编码的接口,以便实现一个不同的编码方式
type Codec interface {
io.Closer //用于包装基本的关闭方法
ReadHeader(*Header) error
ReadBody(interface{}) error
Write(*Header, interface{}) error
}
type NewCodecFunc func(io.ReadWriteCloser) Codec
//定义消息序列化类型
type Type string
const (
GobType Type = "application/gob"
JsonType Type = "application/json" // not implemented
)
var NewCodecFuncMap map[Type]NewCodecFunc
//初始化服务器中消息序列化类型
func init() {
NewCodecFuncMap = make(map[Type]NewCodecFunc)
NewCodecFuncMap[GobType] = NewGobCodec
}
上述定义了两种消息编解码方式,这里实现gob方式。
type GobCodec struct {
conn io.ReadWriteCloser //链接实例 通常是通过TCP或者Unix建立的socket连接实例
buf *bufio.Writer //缓冲writer
dec *gob.Decoder //管理从远端读取的类型和数据信息的解释
enc *gob.Encoder //管理将数据和类型信息编码后发送到远端的操作
}
var _ Codec = (*GobCodec)(nil)
func NewGobCodec(conn io.ReadWriteCloser) Codec {
buf := bufio.NewWriter(conn)
return &GobCodec{
conn: conn,
buf: buf,
dec: gob.NewDecoder(conn),
enc: gob.NewEncoder(buf),
}
}
//读取请求header
func (c *GobCodec) ReadHeader(h *Header) error {
return c.dec.Decode(h)
}
//读取请求body
func (c *GobCodec) ReadBody(body interface{}) error {
return c.dec.Decode(body)
}
//向返回中写入header和body
func (c *GobCodec) Write(h *Header, body interface{}) (err error) {
defer func() {
_ = c.buf.Flush()
if err != nil {
_ = c.Close()
}
}()
if err = c.enc.Encode(h); err != nil {
log.Println("rpc: gob error encoding header:", err)
return
}
if err = c.enc.Encode(body); err != nil {
log.Println("rpc: gob error encoding body:", err)
return
}
return
}
//关闭连接
func (c *GobCodec) Close() error {
return c.conn.Close()
}
上述定义了消息体和消息的编解码,还需要实现消息通信
通信过程: 一般来说客户端与服务器之间实现通信需要协商内容,例如http报文分位header和body两部分,服务器通过读取header中的content-type和content-length就知道怎么从body中解析数据。但是对于我们目前实现的rpc框架来说需要协商的唯一内容就是消息编解码的方式。我们将这部分信息放在option中进行承载。
const MagicNumber = 0x3bef5c
type Option struct {
MagicNumber int // 标识为一个rpc请求
CodecType codec.Type // 客户端选择不同的方式编码传递的信息
}
var DefaultOption = &Option{
MagicNumber: MagicNumber,
CodecType: codec.GobType,
}
一般设计协商的部分信息应该要用固定字节来传输,但是为了简单,目前的传递的消息格式为:
| Option{MagicNumber: xxx, CodecType: xxx} | Header{ServiceMethod ...} | Body interface{} |
| <------ 固定 JSON 编码 ------> | <------- 编码方式由 CodeType 决定 ------->|
通信过程已经清楚,那么就可以实现一个服务端 server
type Server struct{}
func NewServer() *Server {
return &Server{}
}
//默认server实例
var DefaultServer = NewServer()
// 服务器接监听接口 等待连接进入 并开启新协程处理连接
func (server *Server) Accept(lis net.Listener) {
for {
conn, err := lis.Accept()
if err != nil {
log.Println("rpc server: accept error:", err)
return
}
go server.ServeConn(conn)
}
}
// 用defaultserver处理连接
func Accept(lis net.Listener) { DefaultServer.Accept(lis) }
处理连接: 验证是否为rpc请求,并得到对应编解码实例,进行进一步处理。
// 处理连接
func (server *Server) ServeConn(conn io.ReadWriteCloser) {
defer func() { _ = conn.Close() }()
//获得option实例
var opt Option
if err := json.NewDecoder(conn).Decode(&opt); err != nil {
log.Println("rpc server: options error: ", err)
return
}
if opt.MagicNumber != MagicNumber {
log.Printf("rpc server: invalid magic number %x", opt.MagicNumber)
return
}
//得到对应消息解码器
f := codec.NewCodecFuncMap[opt.CodecType]
if f == nil {
log.Printf("rpc server: invalid codec type %s", opt.CodecType)
return
}
server.serveCodec(f(conn))
}
进一步处理:
// 三步: 1、读取请求 2、处理请求 3、回复请求
func (server *Server) serveCodec(cc codec.Codec) {
sending := new(sync.Mutex) // 加锁确保回复逐个发送
wg := new(sync.WaitGroup) // 确保处理所有请求
for {
req, err := server.readRequest(cc)
if err != nil {
if req == nil {
break // 解析请求失败时,结束循环
}
req.h.Error = err.Error()
server.sendResponse(cc, req.h, invalidRequest, sending)
continue
}
wg.Add(1)
//并发处理请求
go server.handleRequest(cc, req, sending, wg)
}
wg.Wait()
_ = cc.Close()
}
解析对应请求:readRequest方法。
func (server *Server) readRequestHeader(cc codec.Codec) (*codec.Header, error) {
var h codec.Header
if err := cc.ReadHeader(&h); err != nil {
if err != io.EOF && err != io.ErrUnexpectedEOF {
log.Println("rpc server: read header error:", err)
}
return nil, err
}
return &h, nil
}
func (server *Server) readRequest(cc codec.Codec) (*request, error) {
h, err := server.readRequestHeader(cc)
if err != nil {
return nil, err
}
req := &request{h: h}
// TODO: now we don't know the type of request argv
// day 1, just suppose it's string
req.argv = reflect.New(reflect.TypeOf(""))
if err = cc.ReadBody(req.argv.Interface()); err != nil {
log.Println("rpc server: read argv err:", err)
}
return req, nil
}
解析请求失败时回复:
func (server *Server) sendResponse(cc codec.Codec, h *codec.Header, body interface{}, sending *sync.Mutex) {
sending.Lock()
defer sending.Unlock()
if err := cc.Write(h, body); err != nil {
log.Println("rpc server: write response error:", err)
}
}
处理请求:
func (server *Server) handleRequest(cc codec.Codec, req *request, sending *sync.Mutex, wg *sync.WaitGroup) {
// TODO, should call registered rpc methods to get the right replyv
// day 1, just print argv and send a hello message
defer wg.Done()
log.Println(req.h, req.argv.Elem())
req.replyv = reflect.ValueOf(fmt.Sprintf("geerpc resp %d", req.h.Seq))
server.sendResponse(cc, req.h, req.replyv.Interface(), sending)
}
上述就实现了一个服务器的雏形。对上述内容进行简单应用:
func startServer(addr chan string) {
l, err := net.Listen("tcp", ":6060")
if err != nil {
log.Fatal("network err:", err)
}
log.Println("start rpc server on", l.Addr())
addr <- l.Addr().String()
rpcdemo.Accept(l)
}
func main() {
addr := make(chan string)
// 启动服务器、监听地址:6060
go startServer(addr)
conn, _ := net.Dial("tcp", <-addr) //网络上连接地址address 并返回一个conn接口 建立连接
defer func() { _ = conn.Close() }()
time.Sleep(time.Second)
//发送option进行协议交换
_ = json.NewEncoder(conn).Encode(rpcdemo.DefaultOption)
cc := codec.NewGobCodec(conn)
//发送五次请求
for i := 0; i < 5; i++ {
h := &codec.Header{
ServiceMethod: "Foo.Sum",
Seq: uint64(i),
}
_ = cc.Write(h, fmt.Sprintf("rpc req %d", h.Seq))
_ = cc.ReadHeader(h)
var reply string
_ = cc.ReadBody(&reply)
log.Println("reply:", reply)
}
}
总结:上述内容实现了一个rpc框架的消息解编码器-消息的序列化与反序列化,客户端与服务器端简单的协议交换,和服务器的一个简单雏形。