正文共:2794 字 5 图

预计阅读时间:7 分钟

RPC(三)_数据

每日分享

One's destination is never a place but rather a new way of looking at things.

一个人的目的地永远不是一个地方,而是一种看待事物的新方式。

小闫语录​:

物质永远不是最终归宿,精神才是奋斗的最终目标。


RPC(三)_字符串_02

RPC

前面文章导航:

​RPC(一)​

​RPC(二)​

没有看过之前文章的小伙伴,直接点击传送门(上面的标题即链接,跳转对应内容即可)。

1.消息协议设计

接着上一篇文章,我们继续讲解消息协议相关内容。最近因为各种原因,又间断了几天,也许以后还会间断,哈哈哈,但是RPC相关内容不会断掉。大家也可以休息一段时间,在某个周末集中学习,我一有时间就会继续更新。所有的RPC相关文章会为大家汇总到『.py笔记』下的『分类合集2』中,大家可以在专栏中找到。那么我们切入正题,开始今天的学习吧。

消息协议设计的时候,我们分成两部分内容,一部分是​调用请求消息​(也就是发起调用的时候,请求数据如何组织),一部分是​调用返回消息​(即调用完成之后,返回的数据如何组织)。

1.1调用请求消息

还是接着上篇文章除法的小案例继续讲解今天的内容,首先看第一个要传递的数据--divide。这是一个字符串,它可以 采用二进制的方式进行传递,将其每个字符所占用的字节传输即可。那么怎么做呢?我们这样处理:

比如divide占6个字节,为了系统识别出来divide,我们需要考虑边界问题,告诉系统从哪开始,从哪结束。按照长度声明的方式,需要在前面加一个数字,表示divide数据的长度。6是整数,按照在计算机中4个字节表达整数的方式,表示为 ​0110​。这样一来,系统先识别到4个字节,然后将其转换为整数类型,知道divide占6个字节,然后直接往后读取6个字节。到此为止,divide这个方法名读取完毕。关于字符串的数据以后就按照上述方式进行传输。接下来我们再看第一个参数num1,为整数类型。我们仍然采用4个字节进行表达传输。但是有一个问题,就是divide字符串和num1是作为整体传输的,计算机怎么知道divide后面的数字是num1还是num2参数的值?也许你会想,这个简单啊,我们直接在前面跟一个参数名字,用上面讲到的字符串传输方式不就好了吗?可以是可以,但是你没发现数据因此变得很大,会造成浪费吗?比如我的参数名叫做 ​divide_num1_value​,然后参数的值为1。为了传输一个1,你需要多浪费多少字节?这样是不是不利于节省传输时间?那么怎么办呢?容我慢慢道来。我们可以通过指定一个数字,比如num1我们用1指定,num2用2来指定。这样,读取完divide之后,先读取到1,就把接下来的数据放到参数1的位置,读取到2,就把下个数据放到参数2的位置。这样就节省了一定的空间。到此为止,参数的传输也完成了。我们在传递数据的时候,只需要将方法名、参数1和参数2整合到一起传递即可。

我们在指定参数的时候,不需要占用4个字节了。因为一个方法中,参数往往不是很多。一个字节8位,也就是256个数,足以表示。

有人会问了,你骗人,那个默认值你还没有讲呢。小伙子,记性不错嘛,接下来我们就考虑一下默认值的情况。

由于参数2设有默认值,可传递也可不传,所以整体消息的长度就不能确定了,那么传递过去,计算机怎么知道消息是不是传完了呢?其实这种情况很好解决,可传递可不传递,我们仍然采用长度声明法,在整体消息中放置一个消息长度,用4个字节的整数表示。读取到长度为多少后,先读方法名,在读参数1,如果到达了边界就说明消息读完了。没有到达边界,我们就继续读取参数2。那么整体消息的长度放在哪呢?有两种方案:

方案一:

放置在消息最前面,先读取四个字节整体消息长度,然后再读取4个字节方法名消息长度,然后读取方法名,再读取参数1,如果没到达边界,再读取参数2.

方案二:

放置在方法名字符串后面,先读取四个字节方法名长度,读完方法名后,读取参数的长度,先读取参数1,读完如果没到达边界,再读取消息2.

我们后面的案例中,采用的正是方案二。

1.2调用返回消息

接下来,我们说一下返回消息怎么设计。返回消息按照之前的案例,有两种情况:一种情况是正常(返回一个float类型),一种情况是异常(由于我们之前自定义了异常,异常只有一个值便是message,它是字符串类型)。两种情况怎么传输呢?结合上面的例子,大家动脑想一下。

好了,不卖关子了,直接说结果。float类型,我们还是采用4个字节进行表示,异常采用字符串的处理方式。那么计算机怎么知道消息是float类型,还是字符串呢?我们可以在前面消息前面再加一个字节,比如用1表示正常,2表示异常,这样的话计算机能区分开了,我们也尽可能的精简了数据大小。

有人会有疑问,用不用加长度声明,解决边界问题呢?其实不需要了。我们之前长度声明是因为数据整体的大小不确定,消息有长有短。而现在我们在前面添加的1和2既表示了正常和异常的情况,同时也声明了长度。正常就是float类型,4个字节;异常就是字符串,直接读取就好了。

2.struct模块

我们已经知道消息协议如何设计了,接下来就需要考虑到如何实现的问题。在此我们引出struct模块,它是Python标准库提供的二进制编码解码库,允许我们将各种不同类型的变量转换为bytes字节类型,或者反向转换。有了它我们实现就容易的多了。

2.1将其他类型转换为bytes类型

In [1]: import struct
In [2]: struct.pack("!I", 6)
Out[2]: b'\x00\x00\x00\x06'

我们是使用 ​pack​方法进行转换的。它接收两个参数,参数一为按照什么格式进行转换,参数二为数据。输出的结果为bytes类型。

一:b打头代表的就是bytes类型数据。其中 ​\x00​类型的东西代表的是一个字节,可以看到有4个字节。其中前三个字节一个0代表4位,两个0正好代表8位。 ​\x06​代表的是,我们传输的数据为6。二:pack中第一个参数 ​!I​里面有两个符号,分别代表如下内容:

!:表示适用于网络传输的字节顺序。

I:表示无符号4字节整数。


2.2将bytes类型转换为其他类型

In [4]: struct.unpack("!I", b'\x00\x00\x00\x06')Out[4]: (6,)

我们使用 ​unpack​方法进行转换。里面参数同上一部分说明。需要注意的是,返回的结果是一个元组,我们取值的时候可以通过下标来进行。



RPC(三)_ide_03


RPC(三)_ide_04