网络通信之大小端模式及字节序转换函数解析

前言

在网络编程中,我们经常会遇到不同主机间通信时,由于主机CPU架构不同而需要约定传输大小端格式问题。
造成大小端问题的主要原因是不同主机的CPU存储数据的方式不同。
例如:2字节无符号短整型数字1可用2进制表示如下:
00000000 00000001
有些CPU按照以上顺序将数据存储到内存中,而有些数据则以倒序的顺序存储数据,如下所示:
00000001 00000000
如果在网络编程中,不考虑内存存储方式问题,直接将该无符号短整型数字拷贝到发送缓存区中发送,若对方主机CPU存储方式与本机CPU存储方式相同,则不会出现异常情况;若对方主机存储方式与本机存储方式不同,则会解析出现问题。

一、大端序和小端序

从上述中可知,CPU向内存中保存数据的方式有两种:
大端序(Big Endian):高位字节存放到低地址位置(即高字节在前,低字节在后)
例如:短整型数值0X1122,存储到内存地址(例如内存地址为:0x5001,0x5002)中时,按照大端存储方式存储为:
0X11(地址为0x5001),0X22(地址为0x5002);
小端序(Little Endian):高位字节存储在高位地址位置(即低字节在前,高字节在后)
还以上述例子,按照小端存储方式存储为:0X22(地址为0x5001),0X11(地址为0x5002)。
通俗的说法为:大端的存储时,字节间的顺序没有发生改变;小端存储时,字节间的顺序发生了改变。
另外,大小端问题只出现在数据类型跨字节时,数据类型为单个字节时不存在大小端问题。

二、网络字节序

网络字节序统一为大端序。
在网络编程过程中,双方只需要约定好,是按照大端序传输,还是按照小端序传输。
若按照大端传输:
当主机CPU存储方式为小端时,则需要将跨字节数据类型部分转换成大端序放进发送缓存区;
当主机CPU存储方式为大端时,则不需要对跨字节数据类型进行处理,直接放入发送缓存区即可。
反之,如果按照小端传输:
当主机CPU存储方式为大端时,则需要将跨字节数据类型部分转换成小端序放进发送缓存区;
当主机CPU存储方式为小端时,则不需要对跨字节数据类型进行处理,直接放入发送缓存区即可。

三、网络字节序转换函数

根据上述分析,操作系统也提供了一些常用字节序转换函数。
字节序转换函数头文件:
#include <arpa/inet.h>
常用的字节序转换函数有:
htons():将短整型(short)数据从主机字节序(大/小端序)转换成网络字节序(大端序);
ntohs():将短整型(short)数据从网络字节序(大端序)转换成主机字节序(大/小端序);
htonl():将长整型(long int)数据从主机字节序(大/小端序)转换成网络字节序(大端序);
ntohl():将长整型(long int)数据从网络字节序(大端序)转换成主机字节序(大/小端序);

从上述分析可知:当主机存储方式为大端序时,调用这些函数时,返回值和输入值相同,即函数不对数据进行转换操作。为让大家更好的明白这些函数的原理,在此,简单实现一遍这些函数。

typedef unsigned short uint16;
typedef unsigned int   uint32;

//短整型大小端互换
#define BigLittleSwap16(A) ((((uint16)(A) & 0XFF00) >> 8) | \
                           (((uint16)(A) & 0X00FF) << 8))
//整型大小端互换
#define BigLittleSwap32(A) ((((uint32)(A) & 0XFF000000) >> 24) | \
                           (((uint32)(A) & 0X00FF0000) >> 8) | \
                           (((uint32)(A) & 0X0000FF00) << 8) | \
                           (((uint32)(A) & 0X000000FF) << 24))
//判断本机CPU字节序,大端返回true,小端返回false
bool checkCPUendian() {
  union{
    unsigned int i;
    unsigned char s[4];
  }c;
  c.i =0X12345678;
  return (0X12 == c.s[0]);
} 
/**
 * 以下函数执行逻辑均如下:
 * 如果本机字节序为大端,则与网络字节序相同,直接返回结果;
 * 如果本机字节序为小端,则转换字节序为大端,再返回结果。
 */

//本机字节序转网络字节序
unsigned int  t_htonl(unsigned int val) {
  return checkCPUendian() ? val : BigLittleSwap32(val);
}
//网络字节序转本机字节序
unsigned int t_ntohl(unsigned int val) {
  return checkCPUendian() ? val : BigLittleSwap32(val);
}
//本机字节序转换成网络字节序
unsigned short t_htons(unsigned short val) {
  return checkCPUendian() ? val : BigLittleSwap16(val);
}
//网络字节序转本机字节序
unsigned short t_ntohs(unsigned short val) {
  return checkCPUendian() ? val : BigLittleSwap16(val);
}

即在每个函数中,都会调用一下判断本机存储方式的函数,如果本机时大端序,则不对输入进行处理,如果本机为小端序时,则对数据进行变换。

因此,在双方约定为大端传输时,可以调用htons()、ntohs()、htonl()、ntohl()这些函数对数据进行处理。
但是,如果双方约定为小端传输时,则不可以调用这些函数,需要另外实现相关功能的函数,这些函数的主要功能是:当主机为小端序时,不对数据进行处理,直接返回结果;当主机为大端序时,对数据中字节顺序进行旋转。

这是在网络编程中总结的一些心得,分享出来希望对大家有用,如有错误请大家指正。

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在 Linux 内核编程中,经常需要处理网络数据,因此需要进行网络字节序转换。网络字节序是指在网络上传输数据时使用的字节序,它与主机字节序(本地字节序)不同。 在 x86 架构的计算机中,主机字节序是小端序(即低位字节存储在内存的低地址处),而网络字节序是大端序(即高位字节存储在内存的低地址处)。因此,在进行网络数据传输时,需要进行字节序转换Linux 内核提供了一些函数用于进行字节序转换,如: - htons() 和 ntohs() 函数用于将 16 位整数在主机字节序和网络字节序之间转换。 - htonl() 和 ntohl() 函数用于将 32 位整数在主机字节序和网络字节序之间转换。 这些函数都定义在头文件 <arpa/inet.h> 中。例如,将一个 16 位整数从主机字节序转换为网络字节序,可以使用以下代码: ```c #include <arpa/inet.h> uint16_t host_number = 12345; uint16_t network_number = htons(host_number); ``` 同样地,将一个 32 位整数从主机字节序转换为网络字节序,可以使用以下代码: ```c #include <arpa/inet.h> uint32_t host_number = 123456789; uint32_t network_number = htonl(host_number); ``` 反之,将一个从网络中接收到的数据从网络字节序转换为主机字节序,可以使用以下代码: ```c #include <arpa/inet.h> uint16_t network_number = 0x1234; uint16_t host_number = ntohs(network_number); ``` ```c #include <arpa/inet.h> uint32_t network_number = 0x12345678; uint32_t host_number = ntohl(network_number); ``` 需要注意的是,在进行字节序转换时,必须使用适当的数据类型(如 uint16_t 和 uint32_t),否则可能会出现意外的错误。 ### 回答2: 在Linux内核编程中,网络字节序转换是一个常见的操作,因为网络传输中使用的是大端字节序(也称为网络字节序),而计算机内部一般使用的是小端字节序。 在进行网络通信时,需要将数据从主机字节序转换为网络字节序,以确保数据的正确传输和解析。同样,在接收到网络数据后,也需要将数据从网络字节序转换为主机字节序,以便正确地处理和使用这些数据。 在Linux内核中,提供了一系列的函数来进行字节序转换。其中,主要使用的函数为`htonl()`、`htons()`、`ntohl()`和`ntohs()`。它们分别代表主机到网络长整型、主机到网络短整型、网络到主机长整型和网络到主机短整型的转换。 这些函数的命名规则是根据网络字节序的缩写和数据类型的缩写组合而成的。其中,h代表主机字节序(host),n代表网络字节序(network),l代表长整型(long),s代表短整型(short)。 使用这些函数进行字节序转换非常简单。例如,要将一个32位整型数从主机字节序转换为网络字节序,可以使用`htonl()`函数,如下所示: ``` uint32_t value = 12345678; uint32_t network_value = htonl(value); ``` 同样地,要将一个网络字节序的32位整型数转换为主机字节序,可以使用`ntohl()`函数,如下所示: ``` uint32_t network_value = 0x1020304; uint32_t host_value = ntohl(network_value); ``` 总之,在Linux内核编程中,网络字节序转换是非常常见的操作,可以使用`htonl()`、`htons()`、`ntohl()`和`ntohs()`这些函数来实现。这些函数提供了方便、简单和可靠的方式来进行字节序转换,以确保网络数据的正确传输和处理。 ### 回答3: 在Linux内核编程中,网络字节序转换是一个非常重要的概念。网络字节序,也被称为大端字节序,是一种统一的字节序,用于在网络中传输数据。而在计算机内部,一般使用的是主机字节序,也就是与处理器相关的字节序。为了在不同主机之间进行网络通信,就需要进行字节序转换Linux内核提供了一系列函数用于进行字节序转换,这些函数主要集中在`linux/in.h`和`linux/tcp.h`头文件中。其中最常用的函数是`htonl`、`htons`、`ntohl`和`ntohs`。它们分别用于将主机字节序转换为网络字节序和将网络字节序转换为主机字节序。 这些函数的原理是通过位操作和移位运算来实现字节序转换。例如,`htonl`函数可以将32位的整数从主机字节序转换为网络字节序,它将高位字节和低位字节进行位置交换。而`ntohl`函数则在将网络字节序转换为主机字节序时,执行与之相反的操作。 在实际的内核编程中,我们需要根据具体情况选择合适的字节序转换函数。比如,在编写网络设备驱动程序时,需要将设备收到的网络数据转换为主机字节序进行处理,这时就可以使用`ntohl`和`ntohs`函数。而在将数据发送到网络上时,则需要将主机字节序转换为网络字节序,这时可以使用`htonl`和`htons`函数。 总而言之,通过Linux内核提供的网络字节序转换函数,我们可以方便地进行主机字节序和网络字节序之间的转换,从而实现跨主机的网络通信

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值