您现在的位置是:首页 >其他 >SOME/IP协议详解[7 SOME/IP序列化]网站首页其他
SOME/IP协议详解[7 SOME/IP序列化]
什么是序列化与反序列化?
- 序列化是指将数据结构或对象按定义的规则转换成二进制串的过程。
- 反序列化是指将二进制串依据相同规则重新构建成数据结构或对象的过程。
而本质就是一种编码规范。
在SOME/IP中使用序列化的目的和作用?
- 使数据按照固定格式进行编排成为字节序,实现数据在网络上的传输。
7.1 说明
在AUTOSAR中是指数据在PDU中的表达形式,可以理解为来自应用层的真实数据转换成固定格式的字节序,以实现数据在网络上的传输。软件组件将数据从应用层传递到RTE层,在RTE层调用SOME/IP Transformer,执行可配置的数据序列化(Serialize)或反序列化(Deserialize)。SOME/IP Serializer将结构体形式的数据序列化为线性结构的数据;SOME/IP Deserializer将线性结构数据再反序列化为结构体形式数据。在服务端,数据经过SOME/IP Serializer序列化后,被传输到服务层的COM模块;在客户端,数据从COM模块传递到SOME/IP Deserializer反序列化后再进入RTE层。如下图参考Autosar Com过程。
7.2 大小端问题
对于一个由2个字节组成的16位整数,在内存中存储这两个字节有两种方法:一种是将低序字节存储在起始地址,这称为小端(little-endian)字节序;另一种方法是将高序字节存储在起始地址,这称为大端(big-endian)字节序。
假如现有一32位int型数0x12345678,那么其MSB(Most Significant Byte,最高有效字节)为0x12,其LSB (Least Significant Byte,最低有效字节)为0x78,在CPU内存中有两种存放方式:(假设从地址0x4000开始存放)
总结:
- 大端是高字节存放到内存的低地址
- 小端是高字节存放到内存的高地址
假如有一个数据是0x12345678,直接用memcpy将这个数copy到下图中的Length里面来,如果是大端的话,((uint8)Length)[0]就等于0x12;如果是小端的话,就是0x78。
因为对于赋值的方便性来讲,大端是网络通信中常用的方式(例如TCP/IP),所以SOME/IP格式头也使用大端。Payload由于是用户自主定义的内容,所以用户可以自己决定大小端。
7.3 内存对齐与填充
SOME/IP协议通常使用4字节对齐方式进行数据传输。这意味着每个字段的长度应该是4的倍数。这种对齐方式的主要目的是提高传输效率,因为在很多处理器架构中,4字节对齐是最优的方式,可以在内存中更快地访问数据。此外,使用4字节对齐方式还可以确保字段的偏移量是整数,避免了在解析数据时出现未对齐数据的问题。
在进行SOME/IP序列化时,对于每个结构体中的字段,需要根据其数据类型和对齐要求计算其对齐偏移量。对于大多数数据类型,SOME/IP协议都要求其对齐偏移量是4的倍数。例如,对于一个8字节的double类型字段,其对齐偏移量应该是4的倍数,即0、4、8、12等。如果字段的大小不是4的倍数,那么需要在其后添加额外的填充字节,以便满足对齐要求。
在某些情况下,为了提高传输效率,可能需要使用不同的对齐方式。例如,在某些嵌入式系统中,可能需要使用2字节对齐或8字节对齐方式。在这种情况下,SOME/IP协议可以通过在消息头中包含对齐方式信息来指定所使用的对齐方式。但是,这需要在消息头中添加额外的信息,可能会导致消息大小增加,降低传输效率。因此,4字节对齐方式仍然是SOME/IP协议中最常用的对齐方式。(有些项目实际使用中用的是1字节对齐,即不对齐。因为1字节对齐是最简单的对齐方式,大多编译器很容易实现;并且采用一字节对齐,序列化后没有冗余数据,报文的有效负载段都是有意义的数据,所以总体传输效率得到了一定提升。)
通过在数据后插入填充元素来对齐数据的开头,以确保对齐的数据从特定的内存地址开始。对于有些处理器架构可以更高效地访问数据。
当可变元素不是序列化数据流中最后一个元素,应依据规则对可变元素进行位填充来实现数据对齐。
填充示例:
示例1.
示例2.
注:数据对齐填充应尽量以8、16、32、64、128或256长度长度进行。
但是!
对于不同的CPU,数据的存放有不同的对齐原则,有8、16、32甚至64位对齐(可以配置)。如果一个数据是按照CPU对齐的,那么在反序列化的时候会有一定的性能优势。但是SOME/IP序列化的时候只支持对动态数据类型自动添加填充位(即动态数组、动态字符串)。使用场景比较局限且序列化的时候还会消耗一些性能,还有一种场景是使用一条tcp/udp报文承载多个someip报文的时候,原本对齐的数据也可能被破坏。博主感觉比较鸡肋,很多时候都默认使用8bit对齐(也就是不对齐)。我们也举个例子简单讲讲:
假如我们设计的服务接口有两个参数,一个是uint8 arr[5],另一个是uint8 arr2[2],且假设两个数组都是动态数组。动态数组都是要加长度域的,以表示后面的数组的字节数,假设arr使用2bytes的长度域,arr2使用1byte的字节域。当前CPU是4字节对齐,那么序列化完arr的5个数据后,就不能立即序列化arr2。因为arr的长度域+数据域一共7bytes,不是4的整数倍,要填充1byte。而后面的arr2由于是该someip报文的所有元素的末尾元素,虽然其也是动态数组,但是不用填充(因为后面没有数据了,不会影响后面数据的反序列化性能)。
7.4 都有哪些数据类型
拿C语言举例,能用到的数据类型有:
- 基础数据类型:就是C语言中的保留字能直接使用的类型及其重命名类型。如uint8,short,long和float64等
- 复杂数据类型:就是C语言中需要通过基础数据类型进行组合的新类型。如struct,union,array和string等
需要强调的一点是:string和动态array这样的类型在C语言中是不存在的,但是string可以通过array模拟;动态array也可以通过struct模拟。在CP协议中,可以识别这些模拟出来的类型,并序列化成string和动态array。下面列举一下someip所支持的所有可序列化的数据类型。
需要注意的是:
- someip不支持指针的直接序列化,因为没有任何意义,通信双方的内容地址和存放的数据都是不同的,直接传地址是去不到对应数据的。
- someip支持使用TLV(Tag Length Value)格式传输数据,需要配置打开,后续会有一章专门讲解。
7.5 基础数据类型序列化(Basic Datatypes)
本章比较简单,就是对基础数据类型的序列化规则,下面详细列举了所有someip支持传输的基础数据类型,看懂下面的表就可以了。
还有一点是,大于8bit的数据都有大小端问题,7.1章也讲过,Payload里的数据是支持用户配置大小端的。所以只要通信双方协商一致,就没有问题。
7.6 序列化:结构体(Struct)
上一章讲解的基础数据类型的序列化说简单一点就是挨个存放到buffer里,而负责数据类型的序列化是有一定规则的,下面要讲解的便是这些规则。
7.6.1 结构体序列化
结构体是由其他数据类型组合成的一个新的数据类型。单论结构体自身是没有任何意义的,也不能携带数据;只有结构体里面的元素才能存放数据。所以最终将结构体序列化后存放到buffer里玩外发的就是这些元素的值。
结构体也可以嵌套设计(这是应用上常用的方式,甚至可以结构体里有数组元素,数组元素的类型又是结构体等方式),总之c语言能定义出来的排列组合的数据类型,someip都能支持序列化。
下面是3个结构体嵌套的举例:
- struct1里有三个元素,最后一个元素的数据类型是struct2
- struct2里也有三个元素,最后一个元素的数据类型是struct3
- struct3以此类推
嵌套的结构体就形成了一个树,我们做序列化就是在做遍历这颗树,然后存放到buffer中。而someip序列化遍历都是使用的深度遍历法,即在一层结构体的各个元素,要先进入元素内部遍历下一层,而不是遍历到下一个元素。如果按照下图来说明:
深度遍历的顺序是:aàbàefàd
广度遍历的顺序是:aàbàdàef
还有一点是上述序列化到buffer中都是连续的,不能插入一些无用数据。
7.6.2 结构体可选长度域
结构体序列化的时候,还可以添加一个长度域,用来表示后面多长是这个结构体的数据。这个length可以配置占有0/8/16/32位,且这个length里面的长度值的计算不包含length本身的长度(按byte数计算)。比如下图中的length的值就应该是24(6*4byte)。
7.7 序列化:字符串(String)
7.7.1 字符串序列化
为了更好的兼容所有平台,someip规定对string的定义要基于标准的utf-8/16BE/16LE。
- utf-8:字符串的单个字符要按照8bit编码;字符串序列化前面需要有3 bytes的BOM(byte order mark,大家可以理解为一个必要的string格式的标识符,具体可以谷歌一下,这里就不细讲了);字符串必须以 (0x00,注意这里占用1字节)结尾
- utf-16:字符串的单个字符要按照16bit编码;字符串序列化前面需要有2 bytes的BOM(16BE和16LE的2 bytes的BOM顺序刚好相反);字符串必须以 (0x0000,占用2字节)结尾
从utf-16的定义上不难看出,其字节数必定是偶数。一般我们常用的是utf-8,但是如果说要传输中文字符串,那必然要选择utf-16,不过实际应用中很少遇到。
7.7.2 静态字符串
像c语言这样的嵌入式语言没有直接对string类型的定义,一般是通过数组模拟出来。特别是在嵌入式系统中,一般不允许动态开辟内存,所以都会给string一个长度上限。而在序列化的时候,只会序列化到 的地方,后面的数据就不会继续序列化了。
还有值得一提的是,某些车厂仍然将string序列化成array的形式,这种形式不是未来安全所提倡的趋势,someip中有开关可以控制,但是大家尽量使用我们讲解的标准someip的序列化形式。
7.7.3 动态字符串
动态字符串与静态最大的区别是在字符串头部添加了长度域,该长度域的长度值等于后续从BOM开始到