网络字节转换

本文深入探讨了网络字节序在网络通信中的重要性,并详细介绍了如何通过分块转换实现64位数据的大端和小端格式互换,提供实用的C语言代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

做过socket的都知道网络字节转换的事情,网络中传输的数据是纯字节流,没有类型信息,从低地址开始传递;网络字节序通常为大端的,即先传递高字节,因此和大端的本地字节存储顺序一致,和小端的则截然相反。为了数据的一致性,就要把本地的数据转换成网络上使用的格式,然后发送出去,接收的时候也是一样的,经过转换然后才去使用这些数据。基本的库函数中提供了这样的可以进行字节转换的函数,如和htons( ) htonl( ) ntohs( ) ntohl( ),这里n表示network,h表示host,htons( ) htonl( )用于本地字节向网络字节转换的场合,s表示short,即对2字节操作,l表示long即对4字节操作。同样ntohs( )ntohl( )用于网络字节向本地格式转换的场合。随着c99标准的推行,我们伟大的c中增加了新的类型long long int ,unsigned long long int,都是64位的,怎么办?不转肯定是不行,就得自己想办法把它转了。当然有很多方法,我这里想使用一种类比的解决方法,看看如何举一反三。

 

一、字节序定义
字节序,顾名思义字节的顺序,再多说两句就是大于一个字节类型的数据在内存中的存放顺序。其实大部分人在实际的开发中都很少会直接和字节序打交道。唯有在跨平台以及网络程序中字节序才是一个应该被考虑的问题。一次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],那么这个数组变量在栈上是如何布局的呢?看下图:

栈底 (高地址)

----------

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。

 

三、网络字节序的转换
假设对于little endian的IA-32架构上面的Linux,首先考虑网络字节转换的结果与原来有什么不同,如 int a = 0x12345678,b = htonl(a),那么就应该是0x78563412。如果是 short c = 0x1234,short d = 0x5678,e = htons(c),f = htons(d),这样e=0x3412,f=0x7856,如果能把e和f调换一下组合放在一起,不就是一个整型a(a=0x12345678)转换之后的值么?实验的代码如下:

#include <stdio.h>

struct ST{

    short val1;

    short val2;

};

union U{

    int val;

    struct ST st;

};

 

int main(void)

{

    int a = 0;

    union U u1, u2;

 

    a = 0x12345678;

    u1.val = a;

    printf("u1.val is 0x%x/n", u1.val);

    printf("val1 is 0x%x/n", u1.st.val1);

    printf("val2 is 0x%x/n", u1.st.val2);

    printf("after first convert is: 0x%x/n", htonl(u1.val));

    u2.st.val2 = htons(u1.st.val1);

    u2.st.val1 = htons(u1.st.val2);

    printf("after second convert is: 0x%x/n", u2.val);

    return 0;

}

输出结果:

u1.val is 0x12345678

val1 is 0x5678

val2 is 0x1234

after first convert is: 0x78563412

after second convert is: 0x78563412

 

按照这种想法我们实现long long int(64bit)类型,把它分割成两个int(32bit),然后分别使用htonl(),分别转换,然后再将两种int交换顺序重新组合,即实现了整个64位的八个字节的翻转。

代码如下:

#include <stdio.h>

struct ST{

    int val1;

    int val2;

};

union test {

    long long int val;

    struct ST st;

};

 

int main(void)

{

    long long int a;

    union test u1, u2;

 

    a = 0x7654321087654321LL;

    u1.val = a;

    u2.st.val2 = htonl(u1.st.val1);

    u2.st.val1 = htonl(u1.st.val2);

    printf("val1 is 0x%x/n", u2.st.val1);

    printf("val2 is 0x%x/n", u2.st.val2);

    printf("u1.val     is    : 0x%llx/n", u1.val);

    printf("after convert is : 0x%llx/n", u2.val);

   

    return 0;

}

执行结果:

val1 is 0x10325476

val2 is 0x21436587

u1.val     is    : 0x7654321087654321

after convert is  : 0x2143658710325476

 

另外注意long long int 最大值是0x7fffffffffffffff,即7后面15个f(2的63次方减1) unsigned long long int 最大值是0xffffffffffffffff,16个f(2的64次方减1)。程序中long long int 可以简写为 long long,但是记住这是简写,就像long是long int的简写。

 

想看数据在内存中如何存储的,就用gdb吧!使用gdb中 x命令,如 x /xb &a表示要察看存储在变量a中的前一个字节(byte)中的数据(16进制)。x /xw &a 就是要察看变量a中前4个字节(word)数据(16进制)。x /xg &a 察看a开始8个字节的数据。

 

网络字节转换行数如果是win32则定义在winsock2.h内,否则,定义在netdb.h sys/socket.h netinet/in.h等3个头文件中

#ifdef __WIN32__

#include <winsock2.h>

#else

#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>

#endif

<think>嗯,用户问的是为什么字符串不需要网络字节转换,而传输的数据需要,以及什么时候需要进行这种转换。我需要先理清楚这些概念,然后逐步解释。 首先,得理解网络字节顺序,也就是大端序。网络传输中统一使用大端序,确保不同架构的设备能正确解析数据。那么,为什么字符串不需要转换呢?可能因为字符串本身是以字节为单位存储的,每个字符的字节顺序在单字节编码下没有影响。比如ASCII字符,每个字符占一个字节,不存在多字节的排列问题。但如果是像UTF-16这样的多字节编码,可能会有字节序的问题,这时候可能需要考虑BOM(字节顺序标记)。不过通常网络传输中的字符串可能更多使用UTF-8,它是单字节编码,所以不需要转换。 然后是传输的数据需要字节转换的情况。比如整数、浮点数这些多字节数据类型,不同系统可能使用不同的字节序(大端或小端),所以在传输前需要转换网络字节序,接收方再转换回来。例如,一个16位整数0x1234,大端是12 34,小端是34 12,不转换的话接收方可能解析错误。 什么时候需要网络字节转换呢?应该是在处理多字节的数据类型时,如int、short、long、float、double等。此外,结构体或协议头中的字段也需要转换,比如IP地址、端口号。 可能用户在实际编程中遇到了数据传输的问题,或者在学习网络编程时对字节序的概念不太清楚。需要明确区分文本数据和二进制数据的不同处理方式。字符串作为文本,如果使用单字节编码,不需要转换;而二进制数据中的数值必须转换。 另外,可能需要提醒用户注意编码问题,比如如果字符串使用多字节编码(如UTF-16),是否要考虑字节序。不过通常UTF-8是推荐的,因为它避免了这个问题。所以,大多数情况下字符串不需要处理,但具体情况还要看编码方式。 总结起来,回答要分点说明字符串不需要转换的原因,数据传输需要转换的情况,以及何时需要进行网络字节转换。同时,举例说明不同的数据类型,帮助用户理解。</think>好的,我来逐步解释这个问题: 一、为什么字符串不需要网络字节转换? 1. **字符的本质**:字符串本质是字符序列,在单字节编码(如ASCII、UTF-8)中,每个字符存储为1个字节,不存在字节顺序问题。 - 例如:字符'A'的ASCII码是`0x41`,无论大端/小端系统,存储和传输时都是`41`这一个字节。 2. **编码的独立性**:现代网络协议(如HTTP)默认使用UTF-8编码字符串,UTF-8是单字节流编码,不需要考虑字节序问题。 3. **文本协议特性**:像HTTP头、JSON等文本协议直接传输字符流,接收方按字符解析即可。 二、为什么其他数据需要网络字节转换? 1. **多字节数据的字节序问题**:整数、浮点数等多字节数据类型在不同系统中存储方式不同。 - 例如:16位整数`0x1234` - 大端(网络字节序):内存布局为`12 34` - 小端(x86架构):内存布局为`34 12` 2. **跨平台兼容性**:网络传输需要统一用大端序(网络字节序),避免接收方解析错误。 三、何时需要网络字节转换? 1. **传输多字节数值时**: - 整数(int16/int32/int64) - 浮点数(float/double) - 示例代码(C语言): ```c uint32_t host_num = 0x12345678; uint32_t network_num = htonl(host_num); // 主机序转网络序 ``` 2. **处理网络协议头时**: - IP地址(IPv4的32位整数) - 端口号(16位整数) - TCP/UDP包头中的字段 3. **序列化二进制数据时**: - 结构体直接传输前需转换内部字段 - 自定义二进制协议中的数值字段 四、特例说明 1. 如果字符串使用UTF-16等多字节编码,则需要考虑字节序,此时UTF-16会包含BOM(Byte Order Mark)标识字节序。 2. 二进制协议中的字符串如果包含长度前缀(如`int32 + char[]`),长度值需要转换字节序。 五、总结对比表 | 数据类型 | 是否需要转换 | 原因 | |-----------------|--------------|-------------------------------| | ASCII/UTF-8字符串 | 否 | 单字节字节序问题 | | 整数/浮点数 | 是 | 多字节存储依赖系统字节序 | | 协议头字段 | 是 | 标准强制要求网络字节序 | | UTF-16字符串 | 是 | 多字节编码需明确字节序 | 理解这个问题的关键是:**字节序问题只存在于多字节数据的存储方式中**,而字符串在单字节编码场景下天然规避了这个问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

如意唐伯虎

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值