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 }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。