
字节顺序 ,又称 端序 或 尾序 (英语: Endianness )。在计算机科学计算机科学")领域中,是跨越多字节的程序对象的存储规则。
在几乎所有的机器上,多字节对象都被存储为连续的字节序列。例如在C语言中,一个类型为 int 的变量 x 地址为 0x100 ,那么其对应地址表达式 &x 的值为 0x100 。且 x 的四个字节将被存储在存储器的 0x100, 0x101, 0x102, 0x103 位置。
在计算机中一般讲字节序分为两类: Big-Endian (大端字节序) 和 Little-Endian 。
a) Little-Endian 高位字节在前,低位字节在后。
b) Big-Endian 低位字节在前,高位字节在后。
c) 网络字节序:TCP/IP各层协议将字节序定义为Big-Endian,因此TCP/IP协议中使用的字节序通常称之为网络字节序。
举个小例子:
整数127(十进制)在计算机(64位)中大/小端字节序
在x86的计算机中,一般采用的是小端字节序
输出
大端与小端如何存储数据大端
“数据的高字节,保存在内存的低地址中,数据的低字节,保存在内存的高地址中,而数据的取出使用是从低地址开始的,所以大端数据相当于从高字节取出使用
”
小端
“数据的低字节,保存在内存的低地址中,数据的高字节,保存在内存的高地址中,而数据的取出使用是从低地址开始的,所以小端数据相当于从低字节取出使用
”
为什么会有大小端模式之分
“因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于 8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式
”
用法
“Intel的80x86芯片是唯一还在坚持使用小端的芯片,所以使用此芯片的电脑采用小端存储,而网络上传输的数据采用的是大端,所以需要大小端转换
”
word length = 0x1234; //电脑上定义的变量length是小端存储 word len = swapWord(length); //通过方法swapWord()变成大端存储,len就是大端存储,它就可以在网络中使用
注意
大端小端是对于大于一个byte的数字才有的,如word,dword,int,long等,而byte是没有大小端之分的
Capl中定义的数字(大于byte)是小端,如果想在网络中使用,需要转换为大端
网络中取出的数字(大于byte)是大端,如果想在capl中使用(存储,显示),需要转换为小端
memcpy_h2n(byte dest[], struct source)函数可以实现结构体到byte数组的转换,这里会自动把小端转换成大端,所以不需要先在结构体中转换大小端大端排序的好处是接收数据的程序可以优先得到数据的最高位,以便快速反应。
比如我有一个控制温度的上位机程序,该程序接收大端方式编码的温度信号0x00fe,对比原来的温度值,假设是0x0135。那么在接受第一个字节0x00的时候,上位机就可以判断温度比原来下降了,可以立即发出指令打开加热器。而对于小端排序的方式,上位机只有在接收到完整的两个字节的时候才能做出反应。如果采用串行通信,用只对信号的每一个字节单独校验的话,波特率为9600时,大端编码下,上位机的响应时间为1ms,小端排序方式下,上位机响应时间为2ms。这时,大端编码就比小端排序更快。如果需要对完整的通信包进行校验,则没有区别。
在串行通信测试程序中,计算机显示的字节顺序一般就是接收顺序。如果用大端编码的话,测试程序直接就可以显示出从大到小排列好的数据。而小端排序的方向相反,可视性不好,容易看花眼掉。
结论是:1、串行通信(包括以太网、WIFI、串口、USB等)如果采用大端编码有时会使系统响应更快速。2、串行通信采用大端编码有利于调试。
小端排序下,选定一个数据的起点后,只需要重复进位加法就可以实现高精度加法计算。减法也是一样。数组的第0位固定是最低位。而大端方式下,如果高精度计算的精度可变,就很难确定数组的第0位到底代表多大。不同精度的计算还会产生数据对齐问题。比如早期的16位CPU中,int类型和long类型做加法,用小端排序就很容易从指针位置开始计算。而大端排序则非常复杂。加法运算是非常常用的运算,其性能直接影响程序的整体性能。所以CPU中要采用性能较好的小端排序。
由于CPU本身是小端排序,如果内存和文件也采用小端排序的话,就可以把文件中的数据直接存储到内存中,再直接把内存中的数据存储到CPU的寄存器。这样不仅提高计算机的性能,程序也变得简单。
结论是:所有直接与硬件有关的代码都适合按小端排序“
以下内容来源于AutoSar官网的AUTOSAR_PRS_SOMEIPProtocol文档
”
SOME/IP Payload
“
SOME/IP Payload由事件的数据元素或方法的参数组成 ,大小取决于所使用的传输层协议,对于UDP,payload介于0到1400个字节之间,而由于TCP支持payload分段,所以支持更大的长度
”
SOME/IP payload应以网络字节顺序编码,也就是 大端 规则传输
数据结构的序列化
“
将结构化的数据按照一定规则转换成byte字节流,然后封装到SOME/IP的payload里,发送到网络上,这就是数据结构的序列化
”
结构化的数据是并行的,而payload数据是串行的
数据结构的序列化就是 把并行的结构化数据序列化成串行数据
更进一步地说,结构化数据里的元素的数据类型有可能是字符串,数字,布尔值等等,而payload数据只可能是byte数组
那么怎样才能把包含不同数据类型的结构化数据序列化成byte数组呢,不同的项目有不同的规则
之前遇到的项目,是把结构化数据按照ASN1的格式进行定义的,那么序列化时也是按ASN1的规则编码成byte数组
“
Payload数据序列化基于接口规范定义的参数列表,接口规范定义了PDU中所有数据结构的确切位置,并且必须考虑内存对齐
”
所以SOME/IP payload数据一要根据项目规范,二还要考虑内存对齐
什么是内存对齐,之前在学习capl中的结构体时涉及过
计算机系统为了简化处理器与内存之间的传输,以及提升读取数据的速度,对数据在内存中的存储的位置进行了限制,要求是某个数k的倍数,这就是所谓的内存对齐
结构体中的元素由于大小不一,就需要设置这个k值,每个元素的长度必须是这个k值的倍数,如果不满足,就需要填充一定的内存空间以满足k值的倍数
设置对齐方式,并不意味着结构体的长度/大小发生了变化
而对于payload里的数据结构, 如果某个元素大小可变,且不是序列化数据流中的最后一个元素 ,则应通过在可变元素数据后插入填充位来实现数据对齐
“
固定长度数据元素后不应该有填充,如果非要填充,必须在规范中明确
”
“
可变长度元素后面的数据对齐应为1、2、4、8、16、32个byte
”
支持的数据类型
基本数据类型
布尔类型
无符号整数,包括8、16、32、64位
有符号整数,包括8、16、32、64位
浮点数,包括32位和64位
结构化数据类型(结构体)
结构体应按顺序序列化,还需考虑内存对齐
“
SOME/IP不能自动插入虚拟或填充数据
”
“
根据配置情况,可以在结构体前插入长度字段, 以表示这个结构体数据在SOME/IP传输时的长度 ,长度字段的大小为8、16或32位
”
“
如果长度字段大小大于结构体长度,则仅反序列化指定的字节,并根据长度字段跳过其他字节,如果长度字段大小小于结构体长度,且接收方无法在本地提供数据来替换,则中止反序列化,并将SOME/IP消息视为格式错误
”
反序列化是对序列化的反向 *** 作,接收方根据收到的SOME/IP payload进行的解析
带有标识符和可选成员的结构化数据类型和参数
“
为了兼容之前或之后的版本,可以为结构成员或方法参数添加数据ID,有了这个ID,接收方才会反序列化,这样就可以设置可选成功,同时可以在任意位置添加新的成员
”
“
每个数据ID在结构中是唯一的,不能重复,在不同的结构或方法中不需要唯一
”
“
结构中同一层级的成员,要么全部定义数据ID,要么全部都不定义,方法中所有参数要么全部定义数据ID,要么全部都不定义
”
一个元素的tag
通过上图,可以看到结构体中一个元素的组成
Wire Type
第一个byte的6-4位,它的值表示后面数据的类型
“
Wire Type 4时长度字段是静态配置的,而5、6、7是忽略静态配置,忽略长度字段的大小,根据Wire Type选择长度字段的大小,5是1个byte,6是2个byte,7是4个byte,可以看上图
”
Data ID
第一个byte的3-0位,和第二个byte的8位,共12位
“
如果结构的元素或方法的参数配置了Data ID,则应该在序列化字节流中插入tag,也就是下面这个东西
”
还有Strings、Arrays等数据类型
SOME/IP协议规范
SOME/IP支持TCP和UDP传输消息,选择哪种传输协议,后续会讲到
“
如果服务器和多个客户端进行同一个服务的SOME/IP通信,它会运行同一服务的不同实例,这些实例的消息是通过传输层协议端口映射到该实例上的
”
也就是说为了辨别不同的实例,就需要用实例的消息的传输层端口来区分
“
传输层payload里允许有多个SOME/IP消息,根据长度字段确定每个SOME/IP消息
”
“
每个SOME/IP payload都应该有自己的SOME/IP首部
”
一个服务实例可以用以下方式来进行所有方法、事件和通知的通信
最多一个TCP连接
最多一个UDP单播
最多一个UDP多播
UDP绑定
SOME/IP的UDP绑定通过UDP包传输SOME/IP消息来实现
“
SOME/IP协议不应限制UDP分片的使用
”
UDP payload里是SOME/IP消息,如果payload太大,在网络层会进行分片,而SOME/IP协议不能限制它的分片功能
“
对于配置为使用UDP单播通信的服务实例的所有方法、事件和通知,客户端和服务器应使用单个UDP单播连接
”
这句话的意思是一个单播UDP能实现所有配置为UDP单播通信的方法、事件和通知
“
一个多播UDP能实现所有配置为UDP多播通信的事件和通知
”
TCP绑定
SOME/IP的TCP绑定通过TCP报文传输SOME/IP消息来实现
“
一个单播TCP能实现所有配置为TCP单播通信的方法、事件和通知
”
这里有几个注意点
客户端在SOME/IP通信前首先需要建立TCP连接
客户端负责在失败时重新建立TCP连接
当需要断开连接时,由客户端发起断开连接请求
当使用TCP连接的所有服务不再可用(停止或超时)时,客户端应关闭TCP连接
服务器在停止所有服务时不应停止TCP连接,给客户端足够的时间来处理控制数据以关闭TCP连接本身
如果客户端没有主动关闭TCP连接,而服务器关闭TCP连接,那么客户端会尝试重新建立连接
上面讲的TCP连接与断开的逻辑只针对SOME/IP的TCP协议而言,并不是标准TCP协议的规范
SOME/IP-TP
记得在前面看到SOME/IP-TP时并不了解其含义,这里就涉及到了
“
通过UDP传输长度很长的SOME/IP消息
”
SOME/IP的UDP绑定只能传输直接适合IP数据包的SOME/IP消息
什么意思
“
就是SOME/IP消息的大小最好能在IP层不需要分片直接传输
”
如果需要通过UDP传输更大的SOME/IP消息,则应使用SOME/IP-TP
“
就是说如果SOME/IP消息太大,就要用SOME/IP-TP协议, 相当于在SOME/IP层对SOME/IP消息进行分片 ,这时候的SOME/IP Header就变成了SOME/IP-TP Header
”
SOME/IP消息太大而不能用UDP绑定直接传输的,称为“原始”SOME/IP消息
在SOME/IP-TP消息中传输的原始SOME/IP消息payload的“片段”称为segments
“
所有的SOME/IP-TP段必须携带原始消息的Session ID,因此,它们都具有相同的Session-ID
”
“
SOME/IP-TP segments应将消息类型的TP-Flag设置为1
”
就是其中有一位要设置为1,可以回顾下前面的文章
举例
原始SOME/IP消息为
如何分片
SOME/IP-TP消息
最后一条SOME/IP-TP消息
最后一条SOME/IP-TP的More Segment Flag为0,表示这是最后一条分片的SOME/IP消息
可以看出,SOME/IP-TP其实是对SOME/IP长消息的分片,SOME/IP-TP的首部相比较SOME/IP的首部,有两个不同点
Message Type字段的TP-Flag标志位要设置为1
多了4个字节的字段,用来表示offset、Res和M
发送方行为
“
发送方应仅对配置为分段的消息进行分段,且按顺序发送
”
“
发送方应把More Segment Flag设置为1的所有段都分片成相同的大小,也就是说除了最后一片,其他的分片大小都要相同,都是1392个byte
”
“
发送方不得发送重复的分片报文
”
接收方行为
“
接收方应根据配置的Message-ID、Protocol-Version、Interface-Version和Message-Type(w/o TP Flag)进行重组
”
“
Session ID用于检测下一个要重组的原始消息
”
这句话说明了什么
说明同一原始SOME/IP消息分片的Segment的Session ID相同,同时它还是接收方重组新的分片的标志
“
如果接收到具有不同Session-ID的Segment,则接收方应开始新的重组(并可能丢弃未成功重组的旧段)
”
这句话说明如果收到新的Session ID,而之前的重组还未完成,接收方需要开始新的重组,这时候就会导致之前的还未完成的重组的消息被丢弃
这种情况通常发生在新的消息的分片跑到了旧消息的分片的前面,被接收方收到
“
只有正确重组的消息才能传递给应用程序
”
“
消息的每个分片都有Return Code,只有最后一片的Return Code在重组时被使用
”
“
当检测到有丢失的分片时,应该取消之前的重组,这意味着不支持重新排序
”
“
接收方还支持覆盖重复的分片以正确重组消息
”
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)