Go实现简易RPC框架的方法步骤
本文旨在讲述RPC框架设计中的几个核心问题及其解决方法,并基于Golang反射技术,构建了一个简易的RPC框架。
项目地址:Tiny-RPC
RPC
RPC(RemoteProcedureCall),即远程过程调用,可以理解成,服务A想调用不在同一内存空间的服务B的函数,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。
服务端
RPC服务端需要解决2个问题:
- 由于客户端传送的是RPC函数名,服务端如何维护函数名与函数实体之间的映射
- 服务端如何根据函数名实现对应的函数实体的调用
核心流程
- 维护函数名到函数的映射
- 在接收到来自客户端的函数名、参数列表后,解析参数列表为反射值,并执行对应函数
- 对函数执行结果进行编码,并返回给客户端
方法注册
服务端需要维护RPC函数名到RPC函数实体的映射,我们可以使用map数据结构来维护映射关系。
typeServerstruct{
addrstring
funcsmap[string]reflect.Value
}
//Registeramethodvianame
func(s*Server)Register(namestring,finterface{}){
if_,ok:=s.funcs[name];ok{
return
}
s.funcs[name]=reflect.ValueOf(f)
}
执行调用
一般来说,客户端在调用RPC时,会将函数名和参数列表作为请求数据,发送给服务端。
由于我们使用了map[string]reflect.Value来维护函数名与函数实体之间的映射,则我们可以通过Value.Call()来调用与函数名相对应的函数。
packagemain
import(
"fmt"
"reflect"
)
funcmain(){
//Registermethods
funcs:=make(map[string]reflect.Value)
funcs["add"]=reflect.ValueOf(add)
//Whenreceivesclient'srequest
req:=[]reflect.Value{reflect.ValueOf(1),reflect.ValueOf(2)}
vals:=funcs["add"].Call(req)
varrsp[]interface{}
for_,val:=rangevals{
rsp=append(rsp,val.Interface())
}
fmt.Println(rsp)
}
funcadd(a,bint)(int,error){
returna+b,nil
}
具体实现
由于篇幅的限制,此处没有贴出服务端实现的具体代码,细节请查看项目地址。
客户端
RPC客户端需要解决1个问题:
- 由于函数的具体实现在服务端,客户端只有函数的原型,客户端如何通过函数原型调用其函数实体
核心流程
- 对调用者传入的函数参数进行编码,并传送给服务端
- 对服务端响应数据进行解码,并返回给调用者
生成调用
我们可以通过reflect.MakeFunc为指定的函数原型绑定一个函数实体。
packagemain
import(
"fmt"
"reflect"
)
funcmain(){
add:=func(args[]reflect.Value)[]reflect.Value{
result:=args[0].Interface().(int)+args[1].Interface().(int)
return[]reflect.Value{reflect.ValueOf(result)}
}
varaddptrfunc(int,int)int
container:=reflect.ValueOf(&addptr).Elem()
v:=reflect.MakeFunc(container.Type(),add)
container.Set(v)
fmt.Println(addptr(1,2))
}
具体实现
由于篇幅的限制,此处没有贴出客户端实现的具体代码,细节请查看项目地址。
数据传输格式
我们需要定义服务端与客户端交互的数据格式。
typeDatastruct{
Namestring//servicename
Args[]interface{}//request'sorresponse'sbodyexcepterror
Errstring//remoteservererror
}
与交互数据相对应的编码与解码函数。
funcencode(dataData)([]byte,error){
varbufbytes.Buffer
encoder:=gob.NewEncoder(&buf)
iferr:=encoder.Encode(data);err!=nil{
returnnil,err
}
returnbuf.Bytes(),nil
}
funcdecode(b[]byte)(Data,error){
buf:=bytes.NewBuffer(b)
decoder:=gob.NewDecoder(buf)
vardataData
iferr:=decoder.Decode(&data);err!=nil{
returnData{},err
}
returndata,nil
}
同时,我们需要定义简单的TLV协议(固定长度消息头+变长消息体),规范数据的传输。
//Transportstruct
typeTransportstruct{
connnet.Conn
}
//NewTransportcreatesatransport
funcNewTransport(connnet.Conn)*Transport{
return&Transport{conn}
}
//Senddata
func(t*Transport)Send(reqData)error{
b,err:=encode(req)//Encodereqintobytes
iferr!=nil{
returnerr
}
buf:=make([]byte,4+len(b))
binary.BigEndian.PutUint32(buf[:4],uint32(len(b)))//SetHeaderfield
copy(buf[4:],b)//SetDatafield
_,err=t.conn.Write(buf)
returnerr
}
//Receivedata
func(t*Transport)Receive()(Data,error){
header:=make([]byte,4)
_,err:=io.ReadFull(t.conn,header)
iferr!=nil{
returnData{},err
}
dataLen:=binary.BigEndian.Uint32(header)//ReadHeaderfiled
data:=make([]byte,dataLen)//ReadDataField
_,err=io.ReadFull(t.conn,data)
iferr!=nil{
returnData{},err
}
rsp,err:=decode(data)//Decoderspfrombytes
returnrsp,err
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。