网络字节顺序、大端法、小端法

在网络传输时,将long类型先转化为byte数组,步骤如下:

long l;

byte[] b;

b[0]=(byte)(l>>>24);

b[1]]=(byte)(l>>>16);

b[2]]=(byte)(l>>>8);

b[3]]=(byte)(l);

此时,b[]中就按照网络字节顺序(大端法,即l的高位数据存放在byte[]的低位地址,因为地址是
从低向高发展的)存放着4个bytes的数据
使用OutputStream的public void write(byte[] b,int off,int len)方法来向Socket写字节流
,写byte[0]至byte[3]的字节。

 

java.io
Class OutputStream

write

public abstract void write(int b)
                    throws IOException
Writes the specified byte to this output stream. The general contract for write is that one byte is written to the output stream. The byte to be written is the eight low-order bits of the argument b. The 24 high-order bits of b are ignored.

Subclasses of OutputStream must provide an implementation for this method.

 

Parameters:
b - the byte.
Throws:
IOException - if an I/O error occurs. In particular, an IOException may be thrown if the output stream has been closed.

write

public void write(byte[] b,
                  int off,
                  int len)
           throws IOException
Writes len bytes from the specified byte array starting at offset off to this output stream. The general contract for write(b, off, len) is that some of the bytes in the array b are written to the output stream in order; element b[off] is the first byte written and b[off+len-1] is the last byte written by this operation.

The write method of OutputStream calls the write method of one argument on each of the bytes to be written out. Subclasses are encouraged to override this method and provide a more efficient implementation.

If b is null, a NullPointerException is thrown.

If off is negative, or len is negative, or off+len is greater than the length of the array b, then an IndexOutOfBoundsException is thrown.

 

Parameters:
b - the data.
off - the start offset in the data.
len - the number of bytes to write.
Throws:
IOException - if an I/O error occurs. In particular, an IOException is thrown if the output stream is closed.

------关于网络、主机字节顺序的文章

http://www-128.ibm.com/developerworks/cn/java/l-datanet/index.html

 

主机和网络字节序的转换

最近使用C#进行网络开发,需要处理ISO8583报文,由于其中有些域是数值型的,于是在传输的时候涉及到了字节序的转换。字节顺序是指占内存多于一个字节类型的数据在内存中的存放顺序,通常有两种字节顺序,根据他们所处的位置我们分别称为主机节序和网络字节序。

通常我们认为网络字节序为标准顺序,封包的时候,将主机字节序转换为网络字节序,拆包的时候要将网络字节序转换为主机字节序。原以为还要自己写函数,其实网络库已经提供了。

主机到网络:short/int/long IPAddress.HostToNetworkOrder(short/int/long)

网络到主机:short/int/long IPAddress.NetworkToHostOrder(short/int/long)

 

主机字节序指低字节数据存放在内存低地址处,高字节数据存放在内存高地址处,如:

int x=1;    //此时x为主机字节序:[1][0][0][0] 低位到高位

int y=65536 //此时y为主机字节序:[0][0][1][0] 低位到高位

我们通过主机到网络字节序的转换函数分别对xy进行转换得到他们对应的网络字节序值,网络节序是高字节数据存放在低地址处,低字节数据存放在高地址处,如:

int m=IPAddress.HostToNetworkOrder(x);

//此时m为主机字节序:[0][0][0][1] 高位到低位

int n=IPAddress.HostToNetworkOrder(y);

//此时n为主机字节序:[0][1][0][0] 高位到低位

 

经过转换以后,我们就可以通过

byte[]btValue=BitConverter.GetBytes(m);

得到一个长度为4byte数组,然后将这个数组设置到报文的相应位置发送出去即可。

同样,收到报文后,可以将报文按域拆分,得到btValue,使用

int m=BitConverter.ToInt32(btValue,0);//btValue的第0位开始转换

得到该域的值,此时还不能直接使用,应该再用网络到主机字节序的转换函数进行转换:

int x=IPAddress.NetworkToHostOrder(m);

这时得到的x才是报文中的实际值。

 

 

 

---------------------------------------------------

也谈字节序问题

http://bigwhite.blogbus.com/logs/2005/09/

一次Sun SPARC到Intel X86的平台移植让我们的程序遭遇了“字节序问题”,既然遇到了也就不妨深入的学习一下。

一、字节序定义
字节序,顾名思义字节的顺序,再多说两句就是大于一个字节类型的数据在内存中的存放顺序(一个字节的数据当然就无需谈顺序的问题了)。

其实大部分人在实际的开发中都很少会直接和字节序打交道。唯有在跨平台以及网络程序中字节序才是一个应该被考虑的问题。

在所有的介绍字节序的文章中都会提到字节序分为两类:Big-Endian和Little-Endian。引用标准的Big-Endian和Little-Endian的定义如下:
a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
c) 网络字节序:TCP/IP各层协议将字节序定义为Big-Endian,因此TCP/IP协议中使用的字节序通常称之为网络字节序。

其实我在第一次看到这个定义时就很糊涂,看了几个例子后也很是朦胧。什么高/低地址端?又什么高低位?翻阅了一些资料后略有心得。

二、高/低地址与高低字节
首先我们要知道我们C程序映像中内存的空间布局情况:在《C专家编程》中或者《Unix环境高级编程》中有关于内存空间布局情况的说明,大致如下图:
----------------------- 最高内存地址 0xffffffff
 | 栈底
 .
 .              栈
 .
  栈顶
-----------------------
 |
 |
/|/

NULL (空洞) 

/|/
 |
 |
-----------------------
                堆
-----------------------
未初始化的数据
----------------(统称数据段)
初始化的数据
-----------------------
正文段(代码段)
----------------------- 最低内存地址 0x00000000

以上图为例如果我们在栈上分配一个unsigned char buf[4],那么这个数组变量在栈上是如何布局的呢[注1]?看下图:
栈底 (高地址)
----------
buf[3]
buf[2]
buf[1]
buf[0]
----------
栈顶 (低地址)

现在我们弄清了高低地址,接着我来弄清高/低字节,如果我们有一个32位无符号整型0x12345678(呵呵,恰好是把上面的那4个字节buf看成一个整型),那么高位是什么,低位又是什么呢?其实很简单。在十进制中我们都说靠左边的是高位,靠右边的是低位,在其他进制也是如此。就拿 0x12345678来说,从高位到低位的字节依次是0x12、0x34、0x56和0x78。

高低地址和高低字节都弄清了。我们再来回顾一下Big-Endian和Little-Endian的定义,并用图示说明两种字节序:
以unsigned int value = 0x12345678为例,分别看看在两种字节序下其存储情况,我们可以用unsigned char buf[4]来表示value:
Big-Endian: 低地址存放高位,如下图:
栈底 (高地址)
---------------
buf[3] (0x78) -- 低位
buf[2] (0x56)
buf[1] (0x34)
buf[0] (0x12) -- 高位
---------------
栈顶 (低地址)

Little-Endian: 低地址存放低位,如下图:
栈底 (高地址)
---------------
buf[3] (0x12) -- 高位
buf[2] (0x34)
buf[1] (0x56)
buf[0] (0x78) -- 低位
---------------
栈顶 (低地址)

在现有的平台上Intel的X86采用的是Little-Endian,而像Sun的SPARC采用的就是Big-Endian。

三、例子
测试平台: Sun SPARC Solaris 9和Intel X86 Solaris 9
我们的例子是这样的:在使用不同字节序的平台上使用相同的程序读取同一个二进制文件的内容。
生成二进制文件的程序如下:
/* gen_binary.c */
int main() {
        FILE    *fp = NULL;
        int     value = 0x12345678;
        int     rv = 0;

        fp = fopen("temp.dat", "wb");
        if (fp == NULL) {
                printf("fopen error/n");
                return -1;
        }

        rv = fwrite(&value, sizeof(value), 1, fp);
        if (rv != 1) {
                printf("fwrite error/n");
                return -1;
        }

        fclose(fp);
        return 0;
}

读取二进制文件的程序如下:
int main() {
        int             value   = 0;
        FILE         *fp     = NULL;
        int             rv      = 0;
        unsigned        char buf[4];

        fp = fopen("temp.dat", "rb");
        if (fp == NULL) {
                printf("fopen error/n");
                return -1;
        }

        rv = fread(buf, sizeof(unsigned char), 4, fp);
        if (rv != 4) {
                printf("fread error/n");
                return -1;
        }

        memcpy(&value, buf, 4); // or value = *((int*)buf);
        printf("the value is %x/n", value);

        fclose(fp);
        return 0;
}

测试过程:
(1) 在SPARC平台下生成temp.dat文件
在SPARC平台下读取temp.dat文件的结果:
the value is 12345678

在X86平台下读取temp.dat文件的结果:
the value is 78563412

(1) 在X86平台下生成temp.dat文件
在SPARC平台下读取temp.dat文件的结果:
the value is 78563412

在X86平台下读取temp.dat文件的结果:
the value is 12345678

[注1]
buf[4]在栈的布局我也是通过例子程序得到的:
int main() {
        unsigned char buf[4];

        printf("the buf[0] addr is %x/n", buf);
        printf("the buf[1] addr is %x/n", &buf[1]);

        return 0;
}
output:
SPARC平台:
the buf[0] addr is ffbff788
the buf[1] addr is ffbff789
X86平台:
the buf[0] addr is 8047ae4
the buf[1] addr is 8047ae5

两个平台都是buf[x]所在地址高于buf[y] (x > y)。

<think> 嗯,用户再次聚焦于字节序的基本概念,特别是对"字节本身是否属于大端小端"这个本质问题存在困惑。从对话历史看,用户已经接触过大端小端的定义(引用1)、C语言中的实践意义(引用2)、系统层面的影响(引用3)和检测方(引用4),但似乎对最基础的单位界定还不清晰。 用户可能混淆了"字节"和"多字节数据"的区别。需要明确强调:字节作为最小寻址单位不存在端序问题,端序是描述字节之间排列顺序的概念。这就像问"单个字母是否有书写顺序"一样——顺序只存在于多个字母组成的单词中。 上次回答提到32位整数0x12345678的示例很能说明问题,这次可以更彻底地解剖:当把这个整数拆成四个独立字节0x12,0x34,0x56,0x78时,每个字节内部8个比特的存储顺序才是真正的"位端序"问题(但现代计算机基本不涉及),而用户关心的字节序特指这四个字节的排列顺序。 注意到用户提供的引用2提到"经常搞反,记不清楚",说明用户需要形象记忆。除了经典的"高对低是大端"规则外,或许可以补充:想象内存地址像Excel表格,大端模式就像把数字1234写成"一千两百三十四",小端则是"四十三百二十一"。不过这个类比要谨慎使用,避免造成新的混淆。 对于检测方,用户引用4的代码其实有误导性:它通过判断int的第一个字节是否为0x01来检测,但实际上应该检测最低地址字节的值。更稳妥的方案是展示union检测,并强调这是可移植性更高的做(因为指针类型转换可能违反严格别名规则)。 最后考虑用户可能的延伸需求:嵌入式开发中直接读写硬件寄存器时,端序错误会导致灾难性后果。比如UART接收的16位数据寄存器,小端CPU读取时若按大端解析会得到完全错误的值。这个案例可以放在应用部分强化认知。 </think> ### 字节序的本质与字节存储原理 #### 核心概念 **字节本身不存在大小端属性**。大小端描述的是**多字节数据类型**(如 `int`, `float`, `short` 等)在内存中的存储顺序。单个字节(8位)作为最小寻址单元,其内部比特顺序通常固定(LSB 0位为最低位),无需端序区分[^1][^2]。 #### 大小端工作原理(以4字节整数为例) 设32位整数 `0x12345678`: | 存储模式 | 内存地址递增方向 | 说明 | |-----------|---------------------------|--------------------------| | **大端** | `0x12` → `0x34` → `0x56` → `0x78` | 最高位字节 `0x12` 在最低地址 | | **小端** | `0x78` → `0x56` → `0x34` → `0x12` | 最低位字节 `0x78` 在最低地址 | > 数学表达: > 大端:地址偏移量 $i$ 存储第 $i$ 个字节 > 小端:地址偏移量 $i$ 存储第 $(n-1-i)$ 个字节 > ($n$ 为总字节数) #### 系统字节序规则 1. **Linux/Windows系统** - 所有32位/64位 x86/x64架构(Intel/AMD CPU)均为**小端模式** - ARM架构可配置大小端,但Android/Linux默认**小端模式**[^1][^3] 2. **网络通信** - TCP/IP协议强制使用**大端字节序**(网络字节序) - 编程需转换:`htonl()`(主机→网络), `ntohl()`(网络→主机) #### 检测方(C语言) ```c #include <stdio.h> int main() { int num = 1; // 十六进制: 0x00000001 char *p = (char*)# // 指向首字节 if (*p == 1) { printf("小端模式: 首字节为0x01\n"); // 低位在低地址 } else { printf("大端模式: 首字节为0x00\n"); // 高位在低地址 } return 0; } ``` **原理**:小端模式下,整数 `1` 的最低字节 `0x01` 存储在最低地址[^4]。 #### 典型应用场景 1. **文件解析** - JPEG/PNG等格式使用大端序,读取时需转换 ```c uint32_t read_int(FILE *f) { uint32_t val; fread(&val, 4, 1, f); // 直接读取 #if BIG_ENDIAN_SYSTEM return val; // 大端系统无需转换 #else return __builtin_bswap32(val); // 小端系统需翻转字节 #endif } ``` 2. **硬件交互** - 嵌入式系统中,传感器数据可能采用大端序,需通过移位操作转换: ```c // 大端数据转小端CPU可读 uint32_t big_to_little(uint8_t *data) { return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; } ``` 3. **网络协议开发** - 必须显式使用网络字节序函数: ```c uint32_t ip = 0xC0A80101; // 192.168.1.1 send(socket, &(htonl(ip)), 4, 0); // 转换为大端序发送 ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值