通信大小端字节序相关问题总结

[备注:本文主要是将最新box项目中,遇到的问题做以总结梳理,便于加深印象和后续查阅。本文相关资料主要来源于网络,包括转载的一些资料]

遇到问题:

1、何为大、小端存储。

2、若协议中提及消息传输采用大端模式(Big-Endian)。在一个 Byte 中, bit7 MSB bit0 LSB ”,那么该协议数据传输模式是?

3、如何区分大小端存储,那种存储类型比较常用?

4、X86体系CPUKeil编译软件,分别是那种存储方式?

5、如何用代码测试某系统的存储模式?

 

关于大小端字节序问题的一些总结


1、大小端是不同的对于数据在内存地址中的存放方式,不同的处理器(平台)的数据存储方式是不同的。如果实现跨平台通信则大小端是不能忽视的问题。
大端模式:数据的高位存储在内存的低字节。ARM/PowerPC等处理器采用大端模式
小端模式:数据的地位存储在内存的低字节。Intel架构处理器采用小端模式。
如一个数据:0x12345678;对应内纯地址是0x00~0x03
大端模式: 小端模式:
地址:0x00 0x01 0x02 0x03 地址:0x00 0x01 0x02 0x03
数据:0x12 0x34 0x56 0x78 数据:0x78 0x56 0x34 0x12

2、字节序对应着内存的存储(组织)模式,如网络字节序采用大端模式。

3、字节序只是对内置的数据类型而言(intshortdoublelong....char)而对于char而言由于其本身只有一个字节则字节序和存储模式对其不影响。(因此字符串在跨平台传输时不用考虑字节序)

4、单字节内纯中的比特为不受字节序的影响,大于1个字节的数据类型,字节序才对其有影响。
引申:网络通讯中字节序的影响。
在网络通讯中对端口和IP地址进行了字节序的转换而未对发送的数据进行字节序的转换的一些思考:
对于同平台的网络通信而言发送的数据是不需要进行字节类型转换的,字节始终是以二进制的形式存放不管是在何种平台下都是1个字节8BIT,而字节序所影响的只是对我们的读取数据的影响,如果在同一平台下通讯则字节序不会对我们的读取有影响。如在进行网络通信是网络只是数据的传输介质而不需要对数据进行解读,对数据进行解读的是我们数据的接收方。所以在同平台中的网络通信中不必要对传输的数据进字节序的转换。当然跨平台中进行通信时是需要对数据的字节序进行转换的(可以在接收方也可以在发送方)。
在基于TCP/UDP进行网络通信时对端口和IP地址进行的字节转换只是在网络通信中这两个数据是给网络通信的数据只有经过字节序转换之后网络中的协议等才能够识别我们的数据。(个人认为如果在网络通信中通信两端都不进行端口或IP)的字节序转换时通信也是能够完成的只是此时网络中识别的端口和IP和我们实际想设定的端口和IP是不一致的)。

5、如何测试某系统的数据存储模式?

 

 

通信数据的大小端字节序问题总结

1名词说明:

MSBMost Significant Bit.    “最高有效位”

LSBLeast Significant Bit.    “最低有效位”

大端模式:Big Endian.

小端模式:Little Endian.

关于MSBLSB:MSB指一个数据的二进制表示的最高有效位,LSB指数据的最低有效位,举个栗子看:

1(MSB)001 1011(LSB)

2为何会有大小端存储模式

因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于 8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在如何将多个字节安排的问题。

因此就导致了大端存储模式和小端存储模式。

例如一个16bit的short型x,在内存中的地址为0x0010,x的值为0x1122,那么0x11为高字节,0x22为低字节。

对于大端模式,就将0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。小端模式,刚好相反。

我们常用的X86结构是小端模式,而KEIL C51则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以随时在程序中(在ARM Cortex 系列使用REV、REV16、REVSH指令[1]  )进行大小端的切换。

3大小端模式概念:

下面详细介绍:

大端模式:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。

小端模式:是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。

 

下面画图表示:


详解二:

大端模式:一个多字节数据的高字节在前,低字节在后,以数据 0x1234ABCD 看例子:

     低地址   --------------------->   高地址

      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-

     |    12    |    34    |    AB    |    CD    |

     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-

小端模式:一个多字节数据的低字节在前,高字节在后,仍以 0x1234ABCD 看:

     低地址   --------------------->   高地址

     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-

     |    CD    |    AB   |    34    |    12    |

     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-

 

大端模式会感觉大端模式比较符合人类的习惯啊,存储的顺序和直观理解的顺序是一样的。X86架构的CPU是小端模式的....

在项目编程中,我个人接触的都是小端模式,在传输数据的时候,组帧时都是把低字节数据放在数组的前边的。

协议中如果写LSB first” ,这就是指数据传输时采用小端模式。

4、如何测试当前系统属于那种存储模式

可以通过1的高字节和低字节分别与0比较来判断。

通过程序,有两种方式可以测出当前系统的大小端存储模式

3.1,通过联合体

[cpp] view plain copy

1. #include <stdio.h>  

2. typedef union Un  

3. {  

4.     char ch;  

5.     short a;  

6. }Un;  

7.   

8. int main()  

9. {  

10.   

11.     Un un;  

12.     un.a = 1;  

13.   

14.     if (1 == (un.ch&1))  

15.         printf("Little Endian\n");  

16.     else  

17.         printf("Big Endian\n");  

18.   

19.     return 0;  

20. }  

 

联合体的数据元素共用内存,所以你给int元素赋个1,如果是小端模式,它的存储就是 “ 01 00 00 00 ” 这样的;如果是大端模式,就是 “ 00 00 00 01 ” 这样的,所以通过判断char元素的值(共用内存,所以这个值占用的是第一个字节的地址)就可以得到了。

 

3.2,通过指针判断方法

1. BOOL IsBigEndian()  

2. {  

3.     int a = 0x1234;  

4.     char b =  *(char *)&a;  //通过将int强制类型转换成char单字节,通过判断起始存储位置。即等于 取b等于a的低地址部分  

5.     if( b == 0x12)  

6.     {  

7.         return TRUE;  

8.     }  

9.     return FALSE;  

10. }

 

 

5双字内部大端存储格式应用
其实,不仅仅有这两种模式。我们的安全芯片大数模运算加速器(用于 RSA, ECC 等密码算法),采用的是双字内部大端格式,双字之间(整个的串)小端模式的所谓“内部格式”。

例如,大端数据 00112233 44556677 8899AABB CCDDEEFF,内部格式就是 CCDDEEFF 8899AABB 44556677 00112233。这完全是为了加速硬件操作而设计的。
实际上,在芯片外部的数据传递过程中,大多数的标准还是规定为大端格式。即最高比特最先发送。看一下各种接口标准就知道了。

<think>我们正在讨论网络通信中自动字节序检测与转换的实现方法。根据引用[2],网络字节序统一为大序(Big-Endian),因此通常的转换逻辑是将发送的数据转换为大序(如果主机是小序),接收接收到数据后,再根据自身的字节序转换为小序(如果需要)。但是,如果通信双方的字节序不同,则必须进行转换;如果相同,则无需转换。 要实现自动字节序检测与转换,通常需要以下步骤: 1. 通信双方在建立连接时交换字节序信息(例如,发送一个特定的测试值,然后接收方根据已知的测试值解析并判断对方的字节序)。 2. 根据双方字节序的差异,决定是否进行转换。 然而,在实际的网络通信中,通常采用的是固定使用网络字节序(大序)的方式。发送方将数据转换为网络字节序(大),接收方将网络字节序转换为主机字节序(可能是大或小)。这种方式避免了动态检测的过程,因为网络字节序是固定的。 但是,如果我们需要设计一个可以自动检测并转换的协议,可以按照以下思路: ### 方法一:通过固定标记检测 1. 在通信初始化阶段,双方交换一个已知的标记值(例如,一个16位的整数0x1234)。 2. 接收方收到这个标记值后,检查其值: - 如果收到的值为0x1234,则说明发送方的字节序与接收方相同(因为0x1234在内存中的存储方式:大为[0x12,0x34],小为[0x34,0x12])。如果接收方是大,收到0x1234则说明发送方也是大;如果接收方是小,收到0x3412(即0x1234的字节交换)则说明发送方也是小(因为发送方发送的是0x1234,但小发送方实际发送的是[0x34,0x12],接收方如果是小,会将其解释为0x3412,但这不是我们期望的0x1234)。因此,这种方法需要接收方知道自己的字节序,并且根据收到的值判断发送方的字节序。 实际上,我们可以这样设计: - 发送方发送一个固定的值(例如0xAABB),接收方收到后,将这个值按字节拆开。如果第一个字节是0xAA,第二个字节是0xBB,那么发送方就是大;如果第一个字节是0xBB,第二个字节是0xAA,那么发送方就是小。然后接收方记录下发送方的字节序类型。 3. 后续通信中,如果接收方发现发送方与自己字节序不同,则在接收数据后进行字节序转换;否则不转换。 ### 方法二:使用固定网络字节序(推荐) 大多数网络协议(如TCP/IP)都规定使用大序作为网络字节序。因此,发送方无论主机字节序是什么,都转换为大序发送;接收方接收后,如果主机字节序是小序,则转换回小序,如果是大序则不用转换。这样就不需要在通信中检测对方的字节序。 但是,如果我们的应用层协议需要支持自动检测(比如在非网络环境下,如串行通信、自定义协议等),可以采用方法一。 ### 实现示例 #### 步骤1:定义字节序检测函数 我们可以先检测主机自身的字节序: ```iecst FUNCTION F_IsLittleEndian : BOOL VAR wTest : WORD := 16#0001; pByte : POINTER TO BYTE; END_VAR pByte := ADR(wTest); F_IsLittleEndian := (pByte^ = 16#01); // 如果第一个字节是0x01,则是小 END_FUNCTION ``` #### 步骤2:在通信握手阶段交换字节序信息 假设我们设计一个握手协议: - 发送方发送一个固定的16位整数(例如0xAABB)。 - 接收方收到后,根据收到的两个字节判断发送方的字节序: - 如果第一个字节(低地址)是0xAA,第二个字节(高地址)是0xBB,则发送方是大。 - 如果第一个字节是0xBB,第二个字节是0xAA,则发送方是小。 同时,接收方知道自己的字节序(通过F_IsLittleEndian函数)。这样,接收方就知道了双方字节序是否一致。 #### 步骤3:根据检测结果决定是否进行转换 在后续接收数据时,如果接收方发现发送方字节序与自身不同,则对接收到的多字节数据(如INT, WORD, DWORD, REAL等)进行字节序转换。 ### 自动转换函数设计 我们可以设计一个通用的转换函数,根据检测到的发送方字节序与自身字节序的比较结果,决定是否调用之前提供的字节交换函数(如F_SwapWordBytes)。 例如,对于16位数据: ```iecst FUNCTION F_ConvertByteOrderIfNeeded : WORD VAR_INPUT wData : WORD; // 接收到的原始数据 bSenderIsLittleEndian : BOOL; // 发送方是否为小 END_VAR VAR bReceiverIsLittleEndian : BOOL; END_VAR bReceiverIsLittleEndian := F_IsLittleEndian(); // 如果发送方和接收方字节序一致,则不需要转换 IF bSenderIsLittleEndian = bReceiverIsLittleEndian THEN F_ConvertByteOrderIfNeeded := wData; ELSE // 否则进行字节交换 F_ConvertByteOrderIfNeeded := F_SwapWordBytes(wData); END_IF END_FUNCTION ``` 对于32位数据(DWORD)或浮点数,同样需要提供相应的交换函数(因为浮点数在内存中也是多字节存储,同样受字节序影响,转换方法与整数的字节交换类似,但需要交换4个字节)。 ### 浮点数的大小端转换 浮点数的转换与整数类似,注意浮点数在内存中也是按照字节存储的。因此,我们可以使用类似联合体或指针操作的方法来交换字节。 例如,使用联合体转换浮点数: ```iecst TYPE U_FloatSwap : UNION fValue: REAL; // 32位浮点数 byBytes: ARRAY[0..3] OF BYTE; // 4字节数组 END_UNION END_TYPE FUNCTION F_SwapRealBytes : REAL VAR_INPUT fInput: REAL; END_VAR VAR uSwap: U_FloatSwap; byTemp: BYTE; i: INT; END_VAR uSwap.fValue := fInput; // 交换字节:0<->3, 1<->2 FOR i := 0 TO 1 DO byTemp := uSwap.byBytes[i]; uSwap.byBytes[i] := uSwap.byBytes[3-i]; uSwap.byBytes[3-i] := byTemp; END_FOR F_SwapRealBytes := uSwap.fValue; END_FUNCTION ``` 然后在自动转换函数中,根据情况调用这个函数。 ### 注意事项 1. 字符串的转换:字符串通常不需要进行字节序转换,因为字符串是以字节为单位传输的。但是,如果字符串中包含了多字节字符(如UTF-16),那么每个字符(两个字节)就需要进行字节序转换(即前面提到的字符串交换方法)。 2. 性能:在数据传输过程中,如果需要进行大量的字节序转换,可能会影响性能。因此,在协议设计时,应尽量减少转换次数,或者采用统一的字节序(如网络字节序)。 ### 总结 自动字节序检测与转换的实现步骤: 1. 在初始化阶段交换字节序信息(发送一个测试值)。 2. 接收方根据测试值判断发送方的字节序。 3. 接收方记录发送方字节序,并与自身比较。 4. 后续接收数据时,如果字节序不一致,则对多字节数据类型进行字节交换。 然而,在标准的网络通信中,通常采用固定的网络字节序(大)来避免动态检测,发送方总是转换为大,接收方总是从大转换为主机字节序。 §§相关问题§§ 1. 如何在嵌入式系统中优化字节序转换的性能? 2. 对于自定义通信协议,如何设计字节序标记交换机制? 3. 除了整数和浮点数,还有哪些数据类型需要考虑字节序转换? </think># 网络通信中自动字节序检测与转换的实现方法 在网络通信中正确处理字节序至关重要,根据引用[2],网络字节序统一采用大序(Big-Endian),不同字节序系统通信会导致数据解析错误[^2]。下面介绍完整的自动检测与转换解决方案: ## 一、核心实现原理 字节序自动转换系统基于以下数学关系: $$ \text{Swap}(x) = (x \gg 8) \lor (x \ll 8) \quad (\text{适用于16位数据}) $$ $$ \text{Swap}(y) = \sum_{i=0}^{3} \left( \big(y \gg (8 \times i)\big) \& 0xFF \right) \times 256^{(3-i)} \quad (\text{适用于32位数据}) $$ ## 二、自动检测与转换实现 ### 1. 字节序检测函数 ```iecst FUNCTION F_DetectEndianness : BOOL (* 检测当前系统是否为小序 *) VAR wTest : WORD := 16#0001; pByte : POINTER TO BYTE; END_VAR pByte := ADR(wTest); F_DetectEndianness := (pByte^ = 16#01); // 返回TRUE表示小系统 END_FUNCTION ``` ### 2. 通用字节序转换函数 ```iecst FUNCTION F_AutoSwap : DWORD VAR_INPUT dwData : DWORD; // 输入数据 bIsSourceLittleEndian: BOOL; // 数据源是否为小 END_VAR VAR bIsTargetLittleEndian: BOOL; END_VAR bIsTargetLittleEndian := F_DetectEndianness(); // 当源和目标字节序不同时进行转换 IF bIsSourceLittleEndian <> bIsTargetLittleEndian THEN F_AutoSwap := F_SwapDWord(dwData); ELSE F_AutoSwap := dwData; // 字节序相同无需转换 END_IF END_FUNCTION FUNCTION F_SwapDWord : DWORD (* 32位数据字节序转换 *) VAR_INPUT dwInput : DWORD; END_VAR VAR uSwap : U_DWordSwap; byTemp : BYTE; END_VAR uSwap.dwValue := dwInput; // 交换字节序:0⇔3, 1⇔2 byTemp := uSwap.byBytes[0]; uSwap.byBytes[0] := uSwap.byBytes[3]; uSwap.byBytes[3] := byTemp; byTemp := uSwap.byBytes[1]; uSwap.byBytes[1] := uSwap.byBytes[2]; uSwap.byBytes[2] := byTemp; F_SwapDWord := uSwap.dwValue; END_FUNCTION ``` ### 3. 通信协议握手阶段实现 ```iecst FUNCTION FB_EndianHandshake VAR_INPUT tTimeout : TIME := T#5S; // 握手超时时间 END_VAR VAR_OUTPUT bRemoteIsLittleEndian : BOOL; // 远程系统字节序 bHandshakeSuccess : BOOL; // 握手是否成功 END_VAR VAR wTestValue : WORD := 16#AABB; // 已知测试值 wReceived : WORD; tStart : TIME; END_VAR // 发送测试值 SendData(ADR(wTestValue), SIZEOF(wTestValue)); // 接收响应(带超时) tStart := NOW; WHILE (NOW - tStart < tTimeout) AND (NOT DataAvailable()) DO // 等待响应 END_WHILE IF DataAvailable() THEN ReceiveData(ADR(wReceived), SIZEOF(wReceived)); // 解析响应:接收值应为原值或交换值 IF wReceived = 16#AABB THEN bRemoteIsLittleEndian := FALSE; // 大系统 ELSIF wReceived = 16#BBAA THEN bRemoteIsLittleEndian := TRUE; // 小系统 ELSE bHandshakeSuccess := FALSE; // 无效响应 RETURN; END_IF bHandshakeSuccess := TRUE; ELSE bHandshakeSuccess :=
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值