应用场景
有些时候,我们需要在客户端之间相互调用,或者希望服务器端可以调用客户端发布的方法。如果 RPC 提供了反向调用的能力,就可以实现客户端和服务器端的双向调用了。
实现原理
在 Hprose 现有的 RPC 协议基础上,通过对参数和返回值的特殊约定,将调用和返回过程反转。
具体实现
首先跟推送类似,客户端首先发送一个请求给服务器,目的仅仅是让服务器可以反向传递请求,客户端请求格式如下:
Hm1{s2"id"s36"AFA7F4B1-A64D-46FA-886F-EF6432AD69A3"}Cu!z
H
部分是客户端传递给服务器端共享数据,其中可以包含多项内容,但是对于反向调用来说,id
部分是必选项,它表示客户端唯一 id。
C
部分原本是表示客户端对服务器的 RPC 调用请求,这里采用了一个特殊的方法名(!
)来表示这是一个反向调用请求。
当服务器没有对客户端进行反向调用时,该连接一直保持,直到服务器超时,或客户端超时。
如果服务器超时,服务器返回:
或
表示服务器端没有发起对客户的反向调用。客户端收到该响应后,重新发起前面的请求。
如果客户端超时,客户端也重新发起前面的请求。当服务器端收到客户端的第二个请求之后,对前一个请求返回响应:
客户端收到该响应后,直接忽略,不再重新发起请求,这样做可以避免产生无效的交替请求。
当服务器有对客户端进行反向调用时,服务器返回响应:
Ra<n>{<call-1><call-2>...<call-n>}z
也就是说,服务器端可以批量发起对客户端的调用,这在服务器短时间内对客户端进行高频调用的时候会很有用,这样可以有效的减少服务器和客户端直接的通信次数。
当服务器对客户端仅有一个反向调用时,响应为:
其中 <call>
跟前面批量调用中的 <call-1>
,<call-2>
, <call-n>
格式相同。<call>
的格式如下:
a3{<call-id><call-name><call-args>}
也就是说,<call>
是一个三元组。<call-id>
,<call-name>
,<call-args>
都是通过 Hprose 序列化之后的数据。
<call-id>
表示服务器端对客户端反向调用的编号,整数类型。因为服务器启动可能会对同一个客户端发起多个调用,而调用返回的顺序跟调用的顺序并不一定是一致的,调用和结果的传递可能在一个连接上,也可以不在一个连接上,因此无法通过连接来确认客户端返回结果跟服务器发起调用直接的关系,但是通过携带的这个编号,就可以将服务器反向调用跟客户端返回的结果进行一对一匹配了。
<call-name>
表示服务器对客户端反向调用的方法名。字符串类型。
<call-args>
表示服务器对客户端反向调用的参数。数组类型。
当客户端收到服务器端的反向调用后,在处理调用之前,再次发起前面的请求,这样服务器不需要等待前面的请求处理结束,就可以发起新的反向调用了。
客户端执行完反向调用的方法后,将结果以如下形式传递给服务器端:
H<header>Cu=a1{a<n>{<data-1><data-2>...<data-n>}}z
<header>
部分如前所述,不再重复。当仅有一个结果传递给服务器端时,请求格式就变成了:
H<header>Cu=a1{a1{<data>}}z
返回结果之所以被作为一个数组参数返回,是为了便于服务器端解析,如果是去掉最外层的 a1{...}
,在服务器端该参数就会变成变长参数,会增加服务器端编写的复杂度。为了降低实现的复杂度,便于在各种语言中都能方便实现,故而把它仅作为一个参数传递。
<data>
跟批量结果的 <data-1>
,<data-2>
,<data-n>
格式相同。<data>
的格式如下:
a3{<call-id><call-result>n}
当调用发生异常时,格式为:
a3{<call-id>n<call-error>}
<call-error>
为字符串类型,其值为 n
时表示没有错误,因此服务器端可以根据其值进行区分。
服务器端处理完之后,直接返回:
客户端收到该响应后,直接忽略。反向调用结束。
客户端跟服务器端的连接通过 !
请求一直保持,如果客户端想要主动停止对服务器端提供服务,需要发起一个 !!
请求,该请求同样没有参数,服务器端收到之后,返回 Rnz
。同时与该 id
对应的 !
请求也返回 Rnz
。客户端的 !
请求收到该响应之后,停止循环。客户端提供的反向调用服务结束。
注意:
服务器端向客户端发起反向调用时,在 R
标记之前,也可以跟随 H
标记来传递共享数据。
批量调用在 Hprose 3.0 中使用插件方式实现。