兄弟连区块链技术培训以太坊源码分析51rpc源码分析.docx
《兄弟连区块链技术培训以太坊源码分析51rpc源码分析.docx》由会员分享,可在线阅读,更多相关《兄弟连区块链技术培训以太坊源码分析51rpc源码分析.docx(41页珍藏版)》请在冰豆网上搜索。
兄弟连区块链技术培训以太坊源码分析51rpc源码分析
兄弟连Go语言+区块链技术培训以太坊源码分析(51)rpc源码分析
##RPC包的官方文档
Packagerpcprovidesaccesstotheexportedmethodsofanobjectacrossanetwork
orotherI/Oconnection.Aftercreatingaserverinstanceobjectscanberegistered,
makingitvisiblefromtheoutside.Exportedmethodsthatfollowspecific
conventionscanbecalledremotely.Italsohassupportforthepublish/subscribe
pattern.
rpc包提供这样一种能力,可以通过网络或者其他I/O连接,可以访问对象被导出的方法。
创建一个服务器之后,对象可以注册到服务器上,然后可以让外界访问。
通过脂肪方式导出的方法可以被远程调用。
同时还支持发布/订阅模式。
Methodsthatsatisfythefollowingcriteriaaremadeavailableforremoteaccess:
- objectmustbeexported
- methodmustbeexported
- methodreturns0,1(responseorerror)or2(responseanderror)values
- methodargument(s)mustbeexportedorbuiltintypes
- methodreturnedvalue(s)mustbeexportedorbuiltintypes
符合以下标准的方法可用于远程访问:
- 对象必须导出
- 方法必须导出
- 方法返回0,1(响应或错误)或2(响应和错误)值
- 方法参数必须导出或是内置类型
- 方法返回值必须导出或是内置类型
Anexamplemethod:
func(s*CalcService)Add(a,bint)(int,error)
Whenthereturnederrorisn'tnilthereturnedintegerisignoredandtheerroris
sendbacktotheclient.Otherwisethereturnedintegerissendbacktotheclient.
当返回的error不等于nil的时候,返回的整形值被忽略,error被发送回客户端。
否则整形的会返回被发送回客户端。
Optionalargumentsaresupportedbyacceptingpointervaluesasarguments.E.g.
ifwewanttodotheadditioninanoptionalfinitefieldwecanacceptamod
argumentaspointervalue.
通过提供指针类型的参数可以使得方法支持可选参数。
后面有点看不懂了。
func(s*CalService)Add(a,bint,mod*int)(int,error)
ThisRPCmethodcanbecalledwith2integersandanullvalueasthirdargument.
Inthatcasethemodargumentwillbenil.Oritcanbecalledwith3integers,
inthatcasemodwillbepointingtothegiventhirdargument.Sincetheoptional
argumentisthelastargumenttheRPCpackagewillalsoaccept2integersas
arguments.ItwillpassthemodargumentasniltotheRPCmethod.
RPC方法可以通过传两个integer和一个null值作为第三个参数来调用。
在这种情况下mod参数会被设置为nil。
或者可以传递三个integer,这样mod会被设置为指向第三个参数。
尽管可选的参数是最后的参数,RPC包任然接收传递两个integer,这样mod参数会被设置为nil。
TheserverofferstheServeCodecmethodwhichacceptsaServerCodecinstance.Itwill
readrequestsfromthecodec,processtherequestandsendstheresponsebacktothe
clientusingthecodec.Theservercanexecuterequestsconcurrently.Responses
canbesentbacktotheclientoutoforder.
server提供了ServerCodec方法,这个方法接收ServerCodec实例作为参数。
服务器会使用codec读取请求,处理请求,然后通过codec发送回应给客户端。
server可以并发的执行请求。
response的顺序可能和request的顺序不一致。
//AnexampleserverwhichusestheJSONcodec:
typeCalculatorServicestruct{}
func(s*CalculatorService)Add(a,bint)int{
returna+b
}
func(s*CalculatorServiceDiv(a,bint)(int,error){
ifb==0{
return0,errors.New("dividebyzero")
}
returna/b,nil
}
calculator:
=new(CalculatorService)
server:
=NewServer()
server.RegisterName("calculator",calculator")
l,_:
=net.ListenUnix("unix",&net.UnixAddr{Net:
"unix",Name:
"/tmp/calculator.sock"})
for{
c,_:
=l.AcceptUnix()
codec:
=v2.NewJSONCodec(c)
goserver.ServeCodec(codec)
}
Thepackagealsosupportsthepublishsubscribepatternthroughtheuseofsubscriptions.
Amethodthatisconsideredeligiblefornotificationsmustsatisfythefollowingcriteria:
- objectmustbeexported
- methodmustbeexported
- firstmethodargumenttypemustbecontext.Context
- methodargument(s)mustbeexportedorbuiltintypes
- methodmustreturnthetupleSubscription,error
该软件包还通过使用订阅来支持发布订阅模式。
被认为符合通知条件的方法必须满足以下条件:
- 对象必须导出
- 方法必须导出
- 第一个方法参数类型必须是context.Context
- 方法参数必须导出或内置类型
- 方法必须返回元组订阅,错误
Anexamplemethod:
func(s*BlockChainService)NewBlocks(ctxcontext.Context)(Subscription,error){
...
}
Subscriptionsaredeletedwhen:
- theusersendsanunsubscriberequest
- theconnectionwhichwasusedtocreatethesubscriptionisclosed.Thiscanbeinitiated
bytheclientandserver.Theserverwillclosetheconnectiononanwriteerrororwhen
thequeueofbufferednotificationsgetstoobig.
订阅在下面几种情况下会被删除
- 用户发送了一个取消订阅的请求
- 创建订阅的连接被关闭。
这种情况可能由客户端或者服务器触发。
服务器在写入出错或者是通知队列长度太大的时候会选择关闭连接。
##RPC包的大致结构
网络协议channels和Json格式的请求和回应的编码和解码都是同时与服务端和客户端打交道的类。
网络协议channels主要提供连接和数据传输的功能。
json格式的编码和解码主要提供请求和回应的序列化和反序列化功能(Json->Go的对象)。
!
[image](picture/rpc_1.png)
##源码解析
###server.go
server.go主要实现了RPC服务端的核心逻辑。
包括RPC方法的注册,读取请求,处理请求,发送回应等逻辑。
server的核心数据结构是Server结构体。
services字段是一个map,记录了所有注册的方法和类。
run参数是用来控制Server的运行和停止的。
codecs是一个set。
用来存储所有的编码解码器,其实就是所有的连接。
codecsMu是用来保护多线程访问codecs的锁。
services字段的value类型是service类型。
service代表了一个注册到Server的实例,是一个对象和方法的组合。
service字段的name代表了service的namespace,typ实例的类型,callbacks是实例的回调方法,subscriptions是实例的订阅方法。
typeserviceRegistrymap[string]*service//collectionofservices
typecallbacksmap[string]*callback//collectionofRPCcallbacks
typesubscriptionsmap[string]*callback
typeServerstruct{
servicesserviceRegistry
runint32
codecsMusync.Mutex
codecs*set.Set
}
//callbackisamethodcallbackwhichwasregisteredintheserver
typecallbackstruct{
rcvrreflect.Value//receiverofmethod
methodreflect.Method//callback
argTypes[]reflect.Type//inputargumenttypes
hasCtxbool//method'sfirstargumentisacontext(notincludedinargTypes)
errPosint//errreturnidx,of-1whenmethodcannotreturnerror
isSubscribebool//indicationifthecallbackisasubscription
}
//servicerepresentsaregisteredobject
typeservicestruct{
namestring//nameforservice
typreflect.Type//receivertype
callbackscallbacks//registeredhandlers
subscriptionssubscriptions//availablesubscriptions/notifications
}
Server的创建,Server创建的时候通过调用server.RegisterName把自己的实例注册上来,提供一些RPC服务的元信息。
constMetadataApi="rpc"
//NewServerwillcreateanewserverinstancewithnoregisteredhandlers.
funcNewServer()*Server{
server:
=&Server{
services:
make(serviceRegistry),
codecs:
set.New(),
run:
1,
}
//registeradefaultservicewhichwillprovidemetainformationabouttheRPCservicesuchastheservicesand
//methodsitoffers.
rpcService:
=&RPCService{server}
server.RegisterName(MetadataApi,rpcService)
returnserver
}
服务注册server.RegisterName,RegisterName方法会通过传入的参数来创建一个service对象,如过传入的rcvr实例没有找到任何合适的方法,那么会返回错误。
如果没有错误,就把创建的service实例加入serviceRegistry。
//RegisterNamewillcreateaserviceforthegivenrcvrtypeunderthegivenname.Whennomethodsonthegivenrcvr
//matchthecriteriatobeeitheraRPCmethodorasubscriptionanerrorisreturned.Otherwiseanewserviceis
//createdandaddedtotheservicecollectionthisserverinstanceserves.
func(s*Server)RegisterName(namestring,rcvrinterface{})error{
ifs.services==nil{
s.services=make(serviceRegistry)
}
svc:
=new(service)
svc.typ=reflect.TypeOf(rcvr)
rcvrVal:
=reflect.ValueOf(rcvr)
ifname==""{
returnfmt.Errorf("noservicenamefortype%s",svc.typ.String())
}
//如果实例的类名不是导出的(类名的首字母大写),就返回错误。
if!
isExported(reflect.Indirect(rcvrVal).Type().Name()){
returnfmt.Errorf("%sisnotexported",reflect.Indirect(rcvrVal).Type().Name())
}
//通过反射信息找到合适的callbacks和subscriptions方法
methods,subscriptions:
=suitableCallbacks(rcvrVal,svc.typ)
//如果这个名字当前已经被注册过了,那么如果有同名的方法就用新的替代,否者直接插入。
//alreadyapreviousserviceregisterundergivensname,mergemethods/subscriptions
ifregsvc,present:
=s.services[name];present{
iflen(methods)==0&&len(subscriptions)==0{
returnfmt.Errorf("Service%Tdoesn'thaveanysuitablemethods/subscriptionstoexpose",rcvr)
}
for_,m:
=rangemethods{
regsvc.callbacks[formatName(m.method.Name)]=m
}
for_,s:
=rangesubscriptions{
regsvc.subscriptions[formatName(s.method.Name)]=s
}
returnnil
}
svc.name=name
svc.callbacks,svc.subscriptions=methods,subscriptions
iflen(svc.callbacks)==0&&len(svc.subscriptions)==0{
returnfmt.Errorf("Service%Tdoesn'thaveanysuitablemethods/subscriptionstoexpose",rcvr)
}
s.services[svc.name]=svc
returnnil
}
通过反射信息找出合适的方法,suitableCallbacks,这个方法在utils.go里面。
这个方法会遍历这个类型的所有方法,找到适配RPCcallback或者subscriptioncallback类型标准的方法并返回。
关于RPC的标准,请参考文档开头的RPC标准。
//suitableCallbacksiteratesoverthemethodsofthegiventype.Itwilldetermineifamethodsatisfiesthecriteria
//foraRPCcallbackorasubscriptioncallbackandaddsittothecollectionofcallbacksorsubscriptions.Seeserver
//documentationforasummaryofthesecriteria.
funcsuitableCallbacks(rcvrreflect.Value,typreflect.Type)(callbacks,subscriptions){
callbacks:
=make(callbacks)
subscriptions:
=make(subscriptions)
METHODS:
form:
=0;m method:
=typ.Method(m)
mtype:
=method.Type
mname:
=formatName(method.Name)
ifmethod.PkgPath!
=""{//methodmustbeexported
continue
}
varhcallback
h.isSubscribe=isPubSub(mtype)
h.rcvr=rcvr
h.method=method
h.errPos=-1
fir