网络字节序与主机字节序的转换

Posted wonxxx

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络字节序与主机字节序的转换相关的知识,希望对你有一定的参考价值。

前言

端口号和IP地址都是以网络字节序存储的,不是主机字节序。网络字节序都是大端模式,而我们常用的机器都是小端模式。要把主机字节序和网络字节序相互对应起来,需要对这两个字节存储优先顺序进行相互转化。其实这个转换实质是:字节“搬家”。

先分析一下我们平时使用ntohlntohshtonlhtons函数是怎么实现的,然后在本文最后写一个判断机器是大/小端的函数。

函数追踪

函数定义的地方:
/usr/include/netinet/in.h

# if __BYTE_ORDER == __BIG_ENDIAN
/* The host byte order is the same as network byte order,
   so these functions are all just identity.  */
# define ntohl(x)   (x)
# define ntohs(x)   (x)
# define htonl(x)   (x)
# define htons(x)   (x)
# else
#  if __BYTE_ORDER == __LITTLE_ENDIAN
#   define ntohl(x) __bswap_32 (x)
#   define ntohs(x) __bswap_16 (x)
#   define htonl(x) __bswap_32 (x)
#   define htons(x) __bswap_16 (x)                                                                                                  
#  endif
# endif
#endif


可以看到ntohlhtonl使用的是同一个函数__bswap_32ntohshtons使用的是同一个函数__bswap_16,在这里只对__bswap_32函数的实现进行追踪:
/usr/include/i386-linux-gnu/bits/byteswap.h

/* Swap bytes in 32 bit value.  */
#define __bswap_constant_32(x) \\
     ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >>  8) |           \\
      (((x) & 0x0000ff00) <<  8) | (((x) & 0x000000ff) << 24))

#ifdef __GNUC__
# if __GNUC__ >= 2
/* To swap the bytes in a word the i486 processors and up provide the
   `bswap' opcode.  On i386 we have to use three instructions.  */
#  if !defined __i486__ && !defined __pentium__ && !defined __pentiumpro__ \\
      && !defined __pentium4__ && !defined __k8__ && !defined __athlon__ \\
      && !defined __k6__ && !defined __nocona__ && !defined __core2__ \\
      && !defined __geode__ && !defined __amdfam10__
#   define __bswap_32(x)                              \\
     (__extension__                               \\
      ( register unsigned int __v, __x = (x);                    \\
     if (__builtin_constant_p (__x))                      \\
       __v = __bswap_constant_32 (__x);                   \\
     else                                     \\
       __asm__ ("rorw $8, %w0;"                       \\
            "rorl $16, %0;"                       \\
            "rorw $8, %w0"                        \\
            : "=r" (__v)                          \\
            : "0" (__x)                           \\
            : "cc");                              \\
     __v; ))
#  else
#   define __bswap_32(x) \\
     (__extension__                               \\
      ( register unsigned int __v, __x = (x);                    \\
     if (__builtin_constant_p (__x))                      \\
       __v = __bswap_constant_32 (__x);                   \\
     else                                     \\
       __asm__ ("bswap %0" : "=r" (__v) : "0" (__x));             \\
     __v; ))
#  endif
# else
#  define __bswap_32(x) \\
     (__extension__                               \\
      ( register unsigned int __x = (x); __bswap_constant_32 (__x); ))
# endif
#else
static __inline unsigned int
__bswap_32 (unsigned int __bsx)
                                        
  return __bswap_constant_32 (__bsx);

#endif

这里有对不同的处理器有不同的处理情况,还有汇编指令,这里先不对这部分做深究,所以最终的实现为:

/* Swap bytes in 32 bit value.  */
#define __bswap_constant_32(x) \\
     ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >>  8) |           \\
      (((x) & 0x0000ff00) <<  8) | (((x) & 0x000000ff) << 24))


虽然没有对应的64bit的转换函数接口,但在文件bits/byteswap.h中还是有实现:

#if defined __GNUC__ && __GNUC__ >= 2
/* Swap bytes in 64 bit value.  */
# define __bswap_constant_64(x) \\
     (__extension__ ((((x) & 0xff00000000000000ull) >> 56)            \\
             | (((x) & 0x00ff000000000000ull) >> 40)              \\
             | (((x) & 0x0000ff0000000000ull) >> 24)              \\
             | (((x) & 0x000000ff00000000ull) >> 8)           \\
             | (((x) & 0x00000000ff000000ull) << 8)           \\
             | (((x) & 0x0000000000ff0000ull) << 24)              \\
             | (((x) & 0x000000000000ff00ull) << 40)              \\
             | (((x) & 0x00000000000000ffull) << 56)))

# define __bswap_64(x) \\
     (__extension__                               \\
      ( union  __extension__ unsigned long long int __ll;           \\
         unsigned long int __l[2];  __w, __r;                \\
     if (__builtin_constant_p (x))                        \\
       __r.__ll = __bswap_constant_64 (x);                    \\
     else                                     \\
                                             \\
         __w.__ll = (x);                              \\
         __r.__l[0] = __bswap_32 (__w.__l[1]);                \\
         __r.__l[1] = __bswap_32 (__w.__l[0]);                \\
                                             \\
     __r.__ll; ))
#endif


对于__BYTE_ORDER:

/* i386 is little-endian.  */                                                                                                       

#ifndef _ENDIAN_H
# error "Never use <bits/endian.h> directly; include <endian.h> instead."
#endif

#define __BYTE_ORDER __LITTLE_ENDIAN

这里大小端模式竟然是直接定义好的,还以为会用使用数据存储来判断一下呢。。。那我们自己动手来写一下大小端的判断函数:

#include <stdio.h>

#define BIG_ENDIAN 1  
#define LITTLE_ENDIAN 0  

#define BOOL unsigned int

/*
 * 定义一个2个字节长度的数据,并赋值为1,则其16进制表示为0x0001 
 * 如果系统以“大端”存放数据,那么低字节存放的必定是0x00,高字节存放的必定是0x01 
 * 如果系统以“小端”存放数据,那么低字节存放的必定是0x01,高字节存放的必定是0x00 
 */  
static BOOL is_bigendian()
  
    const short n = 1;  
    if (*(char *)&n)  
       
        return LITTLE_ENDIAN;  
       
    return BIG_ENDIAN;  
 

int main()

    if (BIG_ENDIAN == is_bigendian())
       
        printf("Big Endian\\n");
       
    else
       
        printf("Little Endian\\n");
       

    return 0;
 


结语

知其然,也要知其所以然。

以上是关于网络字节序与主机字节序的转换的主要内容,如果未能解决你的问题,请参考以下文章

网络字节序与主机字节序的转换

网络字节序与主机字节序的转换

网络字节序&大小端存储

手把手写C++服务器(20):网络字节序与主机字节序大端小端与共用体

c# 主机和网络字节序的转换 关于网络字节序和主机字节序的转换

网络编程一主机字节序与网络字节序以及ip地址转换函数