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

来源:互联网 发布:adhd 知乎 编辑:程序博客网 时间:2024/05/05 01:47

在国内的4种短信协议的协议头部分,都定义了4个字节长度的message length字段,字段的数据类型为无符号整形(也就是说,这个字段的范围是0-2^16-1);而在java语言中,没有无符号整形这种数据类型(如果 用int类型来表示,由于java中int型是有符号数,则会发送溢出),我设想将message length存入long类型中,将数字的大小控制在0-2^16-1范围之内,当超过此范围归零重新开始。

在网络传输时,将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)。

原创粉丝点击