RPC 协议

Hprose 远程过程调用(RPC)协议支持批量调用,引用参数传递,同步和异步通讯。

Hprose 远程过程调用(RPC)协议非常简单。它由几段数据组成。每段数据包括至少一个字节的标记。下面是Hprose 远程过程调用(RPC)协议的标记列表:

  • 0x46('F'): 函数列表
  • 0x43('C'): RPC 调用
  • 0x52('R'): RPC 结果
  • 0x41('A'): RPC 参数
  • 0x45('E'): RPC 错误
  • 0x7A('z'): 结束

    函数列表

远程过程调用(RPC)服务器可以发布一个或多个函数/方法。每个函数/方法拥有一个名字。函数/方法名以字符串的形式表示。函数列表是一个函数方法名的 List。

如果客户端只发送一个结束标记 'z' 到服务器端,而没有其它数据。那么服务器端应该返回函数列表。

Hprose 远程过程调用(RPC)协议是动态弱类型的。它支持动态类型参数,变长参数函数和方法。所以函数列表只是一个函数/方法名列表,而没有参数和结果的相关信息。

例如,如果服务器端发布了两个函数,一个是 'hello',另一个是 'MD5',那么函数列表应该以如下形式返回:

  1. Fa2{s5"hello"s3"MD5"}z

Hprose 远程过程调用(RPC)服务器支持发布缺失的函数/方法。这表示如果客户端调用一个服务器端不存在的函数/方法,它可以被一个统一的处理器所处理。如果服务器端设置有这样的一个统一的处理器。那么函数列表中需要有一个 '*' 项来表示它。

例如:

  1. Fa1{s1"*"}z

函数/方法名和缺失函数/方法统一处理器可以被一起发布。

例如:

  1. Fa3{s1"*"s5"hello"s3"MD5"}z

Hprose 客户端可以忽略该实现,但是服务器端必须实现它。

RPC 调用

RPC 调用由客户端发起,例如:

  1. Cs5"hello"a1{s5"world"}z # result = client.hello("world");

你可以传递 0 个或多个参数,例如:

  1. Cs3"sum"a3{012}z # result = client.sum(0, 1, 2);

如果没有参数,参数列表可以省略,例如:

  1. Cs9"deleteAll"z # client.deleteAll();

你可以进行引用参数传递,例如:

  1. Cs4"sort"a1{a10{2465318790}}tz # void Sort(ref int[] a); // defined in server
  2. # int[] a = new int[] {2, 4, 6, 5, 3, 1, 8, 7, 9, 0};
  3. # client.sort(ref a);

hprose 发布的函数/方法名是不区分大小写的。例如上面的例子中,服务器端定义的函数/方法名是 "Sort",但是在客户端,可以使用 'sort','Sort' 或 'SORT' 来调用它,这都没问题。

在标记 'C' 之后,标记 's' 表示函数/方法名,标记 'a' 表示参数列表,标记 't' 表示引用参数传递。不要用标记 'f' 表示值参数传递,因为这是默认行为,服务器端不需要处理 'f' 标记。标记 'z' 表示调用结束。

函数/方法名和参数列表是独立的对象序列化。因此它们具有独立的整数引用编号。

Hprose 支持批量调用,例如:

  1. Cs5"hello"a1{s5"world"}Cs3"sum"a3{012}z # client.beginBatch();
  2. # client.hello("world");
  3. # client.sum(0, 1, 2);
  4. # results = client.endBatch();

批量调用是 hprose 客户端的可选特征,客户端可以不必实现的。

RPC 应答

RPC 应答由服务器端返回,例如:

  1. string hello(string str) {
  2. return "Hello " + str + "!";
  3. }
  4.  
  5. int sum(int a, int b, int c) {
  6. return a + b + c;
  7. }
  1. Cs5"hello"a1{s5"world"}z # 客户端调用
  2.  
  3. Rs12"Hello world!"z # 服务器端应答
  4.  
  5. Cs3"sum"a3{012}z # 客户端调用
  6.  
  7. R3z # 服务器端应答

返回值可以是 hprose 支持的任意类型。

如果发布的函数/方法不具有返回值,则返回值为 Null。

如果是引用参数传递的话,在返回值后面将紧跟着返回参数列表,例如:

  1. void Sort(ref int[] a) {
  2. Array.Sort(a);
  3. }
  1. Cs4"sort"a1{a10{2465318790}}tz # 客户端调用
  2.  
  3. RnAa1{a10{0123456789}}z # 服务器端应答

在标记 'R' 之后的是序列化的结果。在标记 'A' 之后的是序列化的参数列表。在应答的末尾是标记 'z'。

函数/方法的结果和参数列表也是独立的对象序列化,所以它们也是具有各自独立的整数引用编号。

在上面的例子中,服务器端函数的参数其实无需定义成 ref 参数。事实上,Array.Sort 可以直接发布。当然,这涉及到协议的实现,这里我们不过多的讨论它。

对于批量调用的应答,这里有个例子:

  1. Cs5"hello"a1{s5"world"}Cs3"sum"a3{012}z # 客户端批量调用
  2.  
  3. Rs12"Hello world!"R3z # 服务器端应答

RPC 错误

如果在远程函数/方法执行过程中发生错误,hprose 服务器应该以如下格式返回错误信息:

  1. E<error_message>z

<error_message> 是序列化的错误信息字符串。例如:

  1. void errorExample() {
  2. throw new Exception("This is a error example.");
  3. }
  1. Cs12"errorExample"z # 客户端调用
  2.  
  3. Es24"This is a error example."z # 服务器端应答

如果在批处理调用中发生错误,批处理调用可以被中断。例如:

  1. Cs5"hello"a1{s5"world"}Cs12"errorExample"Cs3"sum"a3{012}z # 客户端批处理调用
  2.  
  3. Rs12"Hello world!"Es24"This is a error example."z # 服务器端应答

也可以不被中断,例如:

  1. Cs5"hello"a1{s5"world"}Cs12"errorExample"Cs3"sum"a3{012}z # 客户端批处理调用
  2.  
  3. Rs12"Hello world!"Es24"This is a error example."R3z # 服务器端应答