网络协议——RPC协议综述

拿最简单的场景,客户端调用一个加法函数,将两个整数加起来,返回它们的和。

如果放在本地调用,那是简单的不能再简单了,。但是一旦变成了远程调用,门槛一下子就上去了。

如何解决这五个问题?

问题一:如何规定远程调用的语法?

客户端如何告诉服务端,我是一个加法,而另一个是乘法。我是用字符串“add”传给你,还是传给你一个整数,比如 1 表示加法,2 表示乘法?服务端该如何告诉客户端,我的这个加法,目前只能加整数,不能加小数,不能加字符串;而另一个加法“add1”,它能实现小数和整数的混合加法。那返回值是什么?正确的时候返回什么,错误的时候又返回什么?

问题二:如果传递参数?

是先传两个整数,后传一个操作符“add”,还是先传操作符,再传两个整数?是不是像咱们数据结构里一样,如果都是 UDP,想要实现一个逆波兰表达式,放在一个报文里面还好,如果是 TCP,是一个流,在这个流里面,如何将两次调用进行分界?什么时候是头,什么时候是尾?别这次的参数和上次的参数混了起来,TCP 一端发送出去的数据,另外一端不一定能一下子全部读取出来。所以,怎么才算读完呢?

问题三:如何表示数据?

在这个简单的例子中,传递的就是一个固定长度的 int 值,这种情况还好,如果是变长的类型,是一个结构体,甚至是一个类,应该怎么办呢?如果是 int,不同的平台上长度也不同,该怎么办呢?

在网络上传输超过一个 Byte 的类型,还有大端 Big Endian 和小端 Little Endian 的问题。

假设我们要在 32 位四个 Byte 的一个空间存放整数 1,很显然只要一个 Byte 放 1,其他三个 Byte 放 0 就可以了。那问题是,最后一个 Byte 放 1 呢,还是第一个 Byte 放 1 呢?或者说 1 作为最低位,应该是放在 32 位的最后一个位置呢,还是放在第一个位置呢?

最低位放在最后一个位置,叫作 Little Endian,最低位放在第一个位置,叫作 Big Endian。TCP/IP 协议栈是按照 Big Endian 来设计的,而 X86 机器多按照 Little Endian 来设计的,因而发出去的时候需要做一个转换。

问题四:如何知道一个服务端都实现了哪些远程调用?从哪个端口可以访问这个远程调用?

假设服务端实现了多个远程调用,每个可能实现在不同的进程中,监听的端口也不一样,而且由于服务端都是自己实现的,不可能使用一个大家都公认的端口,而且有可能多个进程部署在一台机器上,大家需要抢占端口,为了防止冲突,往往使用随机端口,那客户端如何找到这些监听的端口呢?

问题五:发生了错误、重传、丢包、性能等问题怎么办?

本地调用没有这个问题,但是一旦到网络上,这些问题都需要处理,因为网络是不可靠的,虽然在同一个连接中,我们还可通过 TCP 协议保证丢包、重传的问题,但是如果服务器崩溃了又重启,当前连接断开了,TCP 就保证不了了,需要应用自己进行重新调用,重新传输会不会同样的操作做两遍,远程调用性能会不会受影响呢?

协议约定问题 

本地调用函数里有很多问题,比如词法分析、语法分析、语义分析等等,这些编译器本来都能帮你做了。但是在远程调用中,这些问题你都需要重新操心。 

 当客户端的应用想发起一个远程调用时,它实际是通过本地调用本地调用方的 Stub。它负责将调用的接口、方法和参数,通过约定的协议规范进行编码,并通过本地的 RPCRuntime 进行传输,将调用网络包发送到服务器。服务器端的 RPCRuntime 收到请求后,交给提供方 Stub 进行解码,然后调用服务端的方法服务端执行方法,返回结果,提供方 Stub 将返回结果编码后,发送给客户端,客户端的 RPCRuntime 收到结果,发给调用方 Stub 解码得到结果返回给客户端。这里面的三个层次,对于客户端和服务端,都显示本地调用一样,专注于业务逻辑的处理就可以了。对于 Stub 层,处理双方约定好的语法、语义、封装、解封装。对于 RPCRuntime,主要处理高性能的传输,以及网络的错误和异常

NFS(Network File System)就是网络文件系统。要使 NFS 成功运行,要启动两个服务端,一个是 mountd,用来挂载文件路径;一个是 nfsd,用来读写文件。NFS 可以在本地 mount 一个远程的目录到本地的一个目录,从而本地的用户在这个目录里面写入、读出任何文件的时候,其实操作的是远程另一台机器上的文件

操作远程和远程调用的思路是一样的,就像操作本地一样。所以 NFS 协议就是基于 RPC 实现的。而无论是什么RPC,底层都是socket编程 XDR(External Data Representation,外部数据表示法)是一个标准的数据压缩格式,可以表示基本的数据类型,也可以表示结构体。

这里是几种基本的数据类型。

 

 在 RPC 的调用过程中,所有的数据类型都要封装成类似的格式。而且 RPC 的调用和结果返回,也有严格的格式

  • XID 唯一标识一对请求和回复。请求为 0,回复为 1

  • RPC 有版本号,两端要匹配 RPC 协议的版本号。如果不匹配,就会返回 Deny,原因就是 RPC_MISMATCH。

  • 程序有编号。如果服务端找不到这个程序,就会返回 PROG_UNAVAIL。

  • 程序有版本号。如果程序的版本号不匹配,就会返回 PROG_MISMATCH。

  • 一个程序可以有多个方法,方法也有编号,如果找不到方法,就会返回 PROC_UNAVAIL。

  • 调用需要认证鉴权,如果不通过,则 Deny。

  • 最后是参数列表,如果参数无法解析,则返回 GABAGE_ARGS。

 

为了可以成功调用 RPC,在客户端和服务端实现 RPC 的时候,首先要定义一个双方都认可的程序、版本、方法、参数等。

如果还是上面的加法,则双方约定为一个协议定义文件,同理如果是 NFS、mount 和读写,也会有类似的定义。

 有了协议定义文件,ONC RPC 会提供一个工具,根据这个文件生成客户端和服务器端的 Stub 程序。

 最下层的是 XDR 文件,用于编码和解码参数。这个文件是客户端和服务端共享的,因为只有双方一致才能成功通信。

在客户端,会调用 clnt_create 创建一个连接,然后调用 add_1,这是一个 Stub 函数,感觉是在调用本地一样。其实是这个函数发起了一个 RPC 调用,通过调用 clnt_call 来调用 ONC RPC 的类库,来真正发送请求

服务端也有一个 Stub 程序,监听客户端的请求,当调用到达的时候,判断如果是 add,则调用真正的服务端逻辑,也即将两个数加起来。服务端将结果返回服务端的 Stub,这个 Stub 程序发送结果给客户端,客户端的 Stub 程序正在等待结果,当结果到达客户端 Stub,就将结果返回给客户端的应用程序,从而完成整个调用过程。

传输问题

但是错误、重传、丢包、性能等问题还没有解决,这些问题我们统称为传输问题。这个就不用 Stub 操心了,而是由 ONC RPC 的类库来实现

 在这个类库中,为了解决传输问题,对于每一个客户端,都会创建一个传输管理层,而每一次 RPC 调用,都会是一个任务,在传输管理层,可以看到熟悉的队列机制、拥塞窗口机制等。

由于在网络传输的时候,经常需要等待,因而同步的方式往往效率比较低,因而也就有 Socket 的异步模型。为了能够异步处理,对于远程调用的处理,往往是通过状态机来实现的。只有当满足某个状态的时候,才进行下一步,如果不满足状态,不是在那里等,而是将资源留出来,用来处理其他的 RPC 调用。

 

首先,进入起始状态,查看 RPC 的传输层队列中有没有空闲的位置,可以处理新的 RPC 任务。如果没有,说明太忙了,或直接结束或重试。如果申请成功,就可以分配内存,获取服务的端口号,然后连接服务器

连接的过程要有一段时间,因而要等待连接的结果,会有连接失败,或直接结束或重试。如果连接成功,则开始发送 RPC 请求,然后等待获取 RPC 结果,这个过程也需要一定的时间;如果发送出错,可以重新发送;如果连接断了,可以重新连接;如果超时,可以重新传输;如果获取到结果,就可以解码,正常结束。

这里处理了连接失败、重试、发送失败、超时、重试等场景。不是大牛真写不出来,因而实现一个 RPC 的框架,其实很有难度。

服务发现问题

传输问题解决了,我们还遗留一个问题,就是问题四“如何找到 RPC 服务端的那个随机端口”。这个问题我们称为服务发现问题。在 ONC RPC 中,服务发现是通过 portmapper 实现的。

 portmapper 会启动在一个众所周知的端口上,RPC 程序由于是用户自己写的,会监听在一个随机端口上,但是 RPC 程序启动的时候,会向 portmapper 注册。客户端要访问 RPC 服务端这个程序的时候,首先查询 portmapper,获取 RPC 服务端程序的随机端口,然后向这个随机端口建立连接,开始 RPC 调用。从图中可以看出,mount 命令的 RPC 调用,就是这样实现的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值